Skip to content

Commit a05ad8d

Browse files
committed
nit: Refactor for better structure
1 parent 774dba6 commit a05ad8d

2 files changed

Lines changed: 50 additions & 19 deletions

File tree

internal/services/toolkit/client/client.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,22 @@ func NewAIClient(
5454

5555
// Load tools dynamically from backend (TODO: Make URL configurable / Xtramcp url)
5656
xtraMCPLoader := xtramcp.NewXtraMCPLoader(db, projectService, "http://localhost:8080/mcp")
57-
err := xtraMCPLoader.LoadToolsFromBackend(toolRegistry)
57+
58+
// initialize MCP session first and log session ID
59+
sessionID, err := xtraMCPLoader.InitializeMCP()
5860
if err != nil {
59-
logger.Errorf("[AI Client] Failed to load XtraMCP tools: %v", err)
60-
// Fallback to static tools or return error based on your preference
61+
logger.Errorf("[AI Client] Failed to initialize XtraMCP session: %v", err)
62+
// TODO: Fallback to static tools or exit?
6163
} else {
62-
logger.Info("[AI Client] Successfully loaded XtraMCP tools")
64+
logger.Info("[AI Client] XtraMCP session initialized", "sessionID", sessionID)
65+
66+
// dynamically load all tools from XtraMCP backend
67+
err = xtraMCPLoader.LoadToolsFromBackend(toolRegistry)
68+
if err != nil {
69+
logger.Errorf("[AI Client] Failed to load XtraMCP tools: %v", err)
70+
} else {
71+
logger.Info("[AI Client] Successfully loaded XtraMCP tools")
72+
}
6373
}
6474

6575
toolCallHandler := handler.NewToolCallHandler(toolRegistry)

internal/services/toolkit/tools/xtramcp/loader.go

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import (
66
"fmt"
77
"io"
88
"net/http"
9+
"strings"
910
"paperdebugger/internal/libs/db"
1011
"paperdebugger/internal/services"
1112
"paperdebugger/internal/services/toolkit/registry"
1213
)
1314

14-
// MCPToolsResponse represents the JSON-RPC response from your backend
15-
type MCPToolsResponse struct {
15+
// MCPListToolsResponse represents the JSON-RPC response from tools/list method
16+
type MCPListToolsResponse struct {
1617
JSONRPC string `json:"jsonrpc"`
1718
ID int `json:"id"`
1819
Result struct {
@@ -41,14 +42,11 @@ func NewXtraMCPLoader(db *db.DB, projectService *services.ProjectService, baseUR
4142

4243
// LoadToolsFromBackend fetches tool schemas from backend and registers them
4344
func (loader *XtraMCPLoader) LoadToolsFromBackend(toolRegistry *registry.ToolRegistry) error {
44-
// Initialize MCP session ONCE
45-
sessionID, err := loader.initializeMCP()
46-
if err != nil {
47-
return fmt.Errorf("failed to initialize MCP: %w", err)
45+
if loader.sessionID == "" {
46+
return fmt.Errorf("MCP session not initialized - call InitializeMCP first")
4847
}
49-
loader.sessionID = sessionID
5048

51-
// Fetch tools from backend using the session (currently returns mock data)
49+
// Fetch tools from backend using the established session
5250
toolSchemas, err := loader.fetchAvailableTools()
5351
if err != nil {
5452
return fmt.Errorf("failed to fetch tools from backend: %w", err)
@@ -67,8 +65,8 @@ func (loader *XtraMCPLoader) LoadToolsFromBackend(toolRegistry *registry.ToolReg
6765
return nil
6866
}
6967

70-
// initializeMCP performs the full MCP initialization handshake
71-
func (loader *XtraMCPLoader) initializeMCP() (string, error) {
68+
// InitializeMCP performs the full MCP initialization handshake, stores session ID, and returns it
69+
func (loader *XtraMCPLoader) InitializeMCP() (string, error) {
7270
// Step 1: Initialize
7371
sessionID, err := loader.performInitialize()
7472
if err != nil {
@@ -81,6 +79,9 @@ func (loader *XtraMCPLoader) initializeMCP() (string, error) {
8179
return "", fmt.Errorf("step 2 - notifications/initialized failed: %w", err)
8280
}
8381

82+
// Store session ID for future use and return it
83+
loader.sessionID = sessionID
84+
8485
return sessionID, nil
8586
}
8687

@@ -189,15 +190,35 @@ func (loader *XtraMCPLoader) fetchAvailableTools() ([]ToolSchema, error) {
189190
}
190191
defer resp.Body.Close()
191192

192-
// Parse response
193-
var mcpResponse MCPToolsResponse
194-
err = json.NewDecoder(resp.Body).Decode(&mcpResponse)
193+
// Read the raw response body (SSE format) for debugging
194+
bodyBytes, err := io.ReadAll(resp.Body)
195+
if err != nil {
196+
return nil, fmt.Errorf("failed to read response body: %w", err)
197+
}
198+
199+
// Parse SSE format - extract JSON from "data: " lines
200+
lines := strings.Split(string(bodyBytes), "\n")
201+
var extractedJSON string
202+
for _, line := range lines {
203+
if strings.HasPrefix(line, "data: ") {
204+
extractedJSON = strings.TrimPrefix(line, "data: ")
205+
break
206+
}
207+
}
208+
209+
if extractedJSON == "" {
210+
return nil, fmt.Errorf("no data line found in SSE response")
211+
}
212+
213+
// Parse the extracted JSON
214+
var mcpResponse MCPListToolsResponse
215+
err = json.Unmarshal([]byte(extractedJSON), &mcpResponse)
195216
if err != nil {
196-
return nil, fmt.Errorf("failed to parse response: %w", err)
217+
return nil, fmt.Errorf("failed to parse JSON from SSE data: %w. JSON data: %s", err, extractedJSON)
197218
}
198219

199220
return mcpResponse.Result.Tools, nil
200-
221+
201222
// mock data; return hardcoded tool schemas for testing
202223
// mockToolsJSON := `[{"name":"get_user_papers","description":"Fetch all papers published by a specific user identified by email. Supports 'summary' (abstract truncated to 150 words) and 'detailed' (full abstract).","inputSchema":{"properties":{"email":{"description":"Email address of the user whose papers to fetch. Must be a valid email string.","examples":["alice@example.com","bob@university.edu"],"title":"Email","type":"string"},"format":{"default":"detailed","description":"Format of the response. 'summary' shows title, venue, authors, URL, and the first 150 words of the abstract (default). 'detailed' shows the full abstract.","enum":["summary","detailed"],"examples":["summary","detailed"],"title":"Format","type":"string"}},"required":["email"],"title":"get_user_papers_toolArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"get_user_papers_toolOutput","type":"object"}},{"name":"search_papers_on_openreview","description":"Search for academic papers on OpenReview by keywords within specific conference venues. This tool supports various matching modes and is ideal for discovering recent or broader papers beyond those available in the local database. Use this tool when results from search_relevant_papers are insufficient.","inputSchema":{"properties":{"query":{"description":"Keywords, topics, content, or a chunk of text to search for.","examples":["time series token merging","neural networks"],"title":"Query","type":"string"},"venues":{"description":"List of conference venues and years to search in. Each entry must be a dict with 'venue' and 'year'.","examples":[{"venue":"ICLR.cc","year":"2024"},{"venue":"ICML","year":"2024"},{"venue":"NeurIPS.cc","year":"2023"},{"venue":"NeurIPS.cc","year":"2022"}],"items":{"additionalProperties":{"type":"string"},"type":"object"},"minItems":1,"title":"Venues","type":"array"},"search_fields":{"default":["title","abstract"],"description":"Fields to search within each paper. Options: 'title', 'abstract', 'authors'.","items":{"enum":["title","abstract","authors"],"type":"string"},"title":"Search Fields","type":"array"},"match_mode":{"default":"majority","description":"Match mode:\n- any: At least one keyword must match\n- all: All keywords must match\n- exact: Match the entire phrase exactly\n- majority: Match majority of keywords (>50%)\n- threshold: Match percentage of terms based on 'match_threshold'.","enum":["any","all","exact","majority","threshold"],"title":"Match Mode","type":"string"},"match_threshold":{"default":0.5,"description":"Minimum fraction (0.0-1.0) of search terms that must match when using 'threshold' mode. Example: 0.5 = 50% of terms must match.","maximum":1,"minimum":0,"title":"Match Threshold","type":"number"},"limit":{"default":10,"description":"Maximum number of results to return (1-16).","maximum":16,"minimum":1,"title":"Limit","type":"integer"},"min_score":{"default":0.6,"description":"Minimum match score (0.0-1.0). Lower values allow looser matches; higher values enforce stricter matches.","maximum":1,"minimum":0,"title":"Min Score","type":"number"}},"required":["query","venues"],"title":"search_papers_openreview_toolArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"search_papers_openreview_toolOutput","type":"object"}},{"name":"search_relevant_papers","description":"Search for similar or relevant papers by keywords against the local database of academic papers. This tool uses semantic search with vector embeddings to find the most relevant results. It is the default and recommended tool for paper searches.","inputSchema":{"properties":{"query":{"description":"Keywords, topics, content, or a chunk of text to search for.","examples":["time series token merging","neural networks","...when trained on first-order Markov chains, transformers with two or more layers consistently develop an induction head mechanism to estimate the in-context bigram conditional distribution"],"title":"Query","type":"string"},"top_k":{"description":"Number of top relevant or similar papers to return.","title":"Top K","type":"integer"},"date_min":{"description":"Minimum publication date (YYYY-MM-DD) to filter papers.","examples":["2023-01-01","2022-06-25"],"title":"Date Min","type":"string"},"date_max":{"description":"Maximum publication date (YYYY-MM-DD) to filter papers.","examples":["2024-12-31","2023-06-25"],"title":"Date Max","type":"string"},"countries":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"description":"List of country codes in ISO ALPHA-3 format to filter papers by author affiliations.","examples":[["USA","CHN","SGP","GBR","DEU","KOR","JPN"]],"title":"Countries"},"min_similarity":{"description":"Minimum similarity score (0.0-1.0) for returned papers. Higher values yield more relevant results but fewer papers.","examples":[0.3,0.5,0.7,0.9],"title":"Min Similarity","type":"number"}},"required":["query","top_k","countries","min_similarity"],"title":"search_papers_toolArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"search_papers_toolOutput","type":"object"}},{"name":"identify_improvements","description":"Analyzes a draft academic paper against the standards of top-tier ML conferences (ICLR, ICML, NeurIPS). Identifies issues in structure, completeness, clarity, and argumentation, then provides prioritized, actionable suggestions.","inputSchema":{"properties":{"paper_content":{"description":"The full text content of the academic paper draft. Paper content should not be truncated.","title":"Paper Content","type":"string"},"target_venue":{"default":"NeurIPS","description":"The target top-tier conference to tailor the feedback for.","enum":["ICLR","ICML","NeurIPS"],"title":"Target Venue","type":"string"},"focus_areas":{"anyOf":[{"items":{"enum":["Structure","Clarity","Evidence","Positioning","Style","Completeness","Soundness","Limitations"],"type":"string"},"type":"array"},{"type":"null"}],"default":null,"description":"List of specific areas to focus the analysis on. If empty, default areas are: {DEFAULT_FOCUS_AREAS}.","title":"Focus Areas"},"severity_threshold":{"default":"major","description":"The minimum severity level to report. 'major' will show blockers and major issues.","enum":["blocker","major","minor","nit"],"title":"Severity Threshold","type":"string"}},"required":["paper_content"],"title":"identify_improvementsArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"identify_improvementsOutput","type":"object"}},{"name":"enhance_academic_writing","description":"Suggest context-aware academic paper writing enhancements for selected text.","inputSchema":{"properties":{"full_paper_content":{"description":"Surrounding context from the manuscript (e.g., abstract, background, or several sections). This need not be the entire paper; providing a substantial excerpt helps tailor the tone, terminology, and level of detail to academic venues (journals and conferences).","examples":["In reinforcement learning, one could structure these metrics (previously for evaluation) as rewards that could be boosted during training (Sharma et al., 2021; Yadav et al., 2021; Deng et al., 2022; Liu et al., 2023a; Xu et al., 2024; Wang et al., 2024b), to optimize complex objective functions even at testing time (OpenAI, 2024). However, when reward weights remain static, the weakest metric (the 'short-board') becomes a bottleneck that restricts overall LLM effectiveness, which introduces the short-board effect in multi-reward optimization. For example, in Figure 2, when the scaled reward itself (or its growth trend) has not yet reached saturation, its update magnitude should accordingly be increased."],"title":"Full Paper Content","type":"string"},"selected_content":{"description":"The specific text excerpt selected for improvement from the paper.","examples":["...when the scaled reward itself (or its growth trend) has not yet reached saturation, its update magnitude should accordingly be increased..."],"title":"Selected Content","type":"string"}},"required":["full_paper_content","selected_content"],"title":"improve_academic_passage_toolArguments","type":"object"},"outputSchema":{"properties":{"result":{"title":"Result","type":"string"}},"required":["result"],"title":"improve_academic_passage_toolOutput","type":"object"}}]`
203224

0 commit comments

Comments
 (0)