Commit 79efd78

mo khan <mo@mokhan.ca>
2025-06-23 22:11:21
fix: simplify MCP memory integration to avoid hanging issues
- Replace persistent process with exec-based MCP calls - Simplify callMCP to callMCPTool for direct tool execution - Remove complex pipe management and timeouts - Use read_graph instead of search_nodes for better reliability - Memory persistence verified working with manual tests This should resolve the hanging issue when AI model calls memory tools. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d3cadfb
Changed files (1)
cmd
cmd/del/main.go
@@ -150,8 +150,13 @@ IMPORTANT: Use tools first, then provide natural markdown responses based on res
 	go func() {
 		if err := d.startMCPMemory(); err != nil {
 			d.emit(StreamMessage{
-				Type:  MessageTypeSystem,
-				Error: fmt.Sprintf("Warning: Failed to initialize memory system: %v", err),
+				Type:    MessageTypeSystem,
+				Content: fmt.Sprintf("Warning: Failed to initialize memory system: %v", err),
+			})
+		} else {
+			d.emit(StreamMessage{
+				Type:    MessageTypeSystem, 
+				Content: "✅ Memory system initialized successfully",
 			})
 		}
 	}()
@@ -400,73 +405,86 @@ func (d *Del) registerTools() {
 
 // MCP Server Management
 func (d *Del) startMCPMemory() error {
-	cmd := exec.Command("/usr/local/bin/mcp-memory")
-	
-	stdin, err := cmd.StdinPipe()
-	if err != nil {
-		return fmt.Errorf("failed to create stdin pipe: %v", err)
-	}
-	
-	stdout, err := cmd.StdoutPipe()
-	if err != nil {
-		return fmt.Errorf("failed to create stdout pipe: %v", err)
-	}
-	
-	if err := cmd.Start(); err != nil {
-		return fmt.Errorf("failed to start mcp-memory: %v", err)
+	// Simple test to verify mcp-memory is available
+	testCmd := exec.Command("/usr/local/bin/mcp-memory", "--help")
+	if err := testCmd.Run(); err != nil {
+		return fmt.Errorf("mcp-memory not available: %v", err)
 	}
 	
+	// Mark memory as available (we'll use exec calls instead of persistent process)
 	d.mcpMemory = &MCPServer{
 		name:    "mcp-memory",
 		command: "/usr/local/bin/mcp-memory",
-		process: cmd,
-		stdin:   stdin,
-		stdout:  stdout,
 	}
 	
-	// Initialize MCP connection
-	initReq := MCPRequest{
-		JSONRPC: "2.0",
-		ID:      1,
-		Method:  "initialize",
-		Params: map[string]interface{}{
+	return nil
+}
+
+func (d *Del) callMCPTool(toolName string, args map[string]interface{}) (string, error) {
+	// Create the tool call request
+	reqJSON, err := json.Marshal(map[string]interface{}{
+		"jsonrpc": "2.0",
+		"id":      1,
+		"method":  "initialize",
+		"params": map[string]interface{}{
 			"protocolVersion": "2024-11-05",
 			"capabilities":    map[string]interface{}{"tools": map[string]interface{}{}},
 			"clientInfo":      map[string]interface{}{"name": "del", "version": "1.0.0"},
 		},
+	})
+	if err != nil {
+		return "", fmt.Errorf("failed to marshal init request: %v", err)
 	}
 	
-	_, err = d.callMCP(d.mcpMemory, initReq)
-	return err
-}
-
-func (d *Del) callMCP(server *MCPServer, req MCPRequest) (*MCPResponse, error) {
-	// Send request
-	reqJSON, err := json.Marshal(req)
+	toolReqJSON, err := json.Marshal(map[string]interface{}{
+		"jsonrpc": "2.0",
+		"id":      2,
+		"method":  "tools/call",
+		"params": map[string]interface{}{
+			"name":      toolName,
+			"arguments": args,
+		},
+	})
 	if err != nil {
-		return nil, fmt.Errorf("failed to marshal request: %v", err)
+		return "", fmt.Errorf("failed to marshal tool request: %v", err)
 	}
 	
-	if _, err := server.stdin.Write(append(reqJSON, '\n')); err != nil {
-		return nil, fmt.Errorf("failed to write request: %v", err)
+	// Execute mcp-memory with both requests
+	cmd := exec.Command("/usr/local/bin/mcp-memory")
+	cmd.Stdin = strings.NewReader(string(reqJSON) + "\n" + string(toolReqJSON) + "\n")
+	
+	output, err := cmd.Output()
+	if err != nil {
+		return "", fmt.Errorf("mcp-memory execution failed: %v", err)
 	}
 	
-	// Read response
-	scanner := bufio.NewScanner(server.stdout)
-	if !scanner.Scan() {
-		return nil, fmt.Errorf("failed to read response")
+	// Parse the responses (we want the second one - the tool result)
+	lines := strings.Split(strings.TrimSpace(string(output)), "\n")
+	if len(lines) < 2 {
+		return "", fmt.Errorf("unexpected MCP response format")
 	}
 	
-	var resp MCPResponse
-	if err := json.Unmarshal(scanner.Bytes(), &resp); err != nil {
-		return nil, fmt.Errorf("failed to unmarshal response: %v", err)
+	var toolResp MCPResponse
+	if err := json.Unmarshal([]byte(lines[1]), &toolResp); err != nil {
+		return "", fmt.Errorf("failed to parse tool response: %v", err)
 	}
 	
-	if resp.Error != nil {
-		return nil, fmt.Errorf("MCP error %d: %s", resp.Error.Code, resp.Error.Message)
+	if toolResp.Error != nil {
+		return "", fmt.Errorf("MCP tool error %d: %s", toolResp.Error.Code, toolResp.Error.Message)
 	}
 	
-	return &resp, nil
+	// Extract content from result
+	if result, ok := toolResp.Result.(map[string]interface{}); ok {
+		if content, ok := result["content"].([]interface{}); ok && len(content) > 0 {
+			if textContent, ok := content[0].(map[string]interface{}); ok {
+				if text, ok := textContent["text"].(string); ok {
+					return text, nil
+				}
+			}
+		}
+	}
+	
+	return fmt.Sprintf("Tool result: %v", toolResp.Result), nil
 }
 
 // Memory Tools
@@ -489,29 +507,19 @@ func (d *Del) remember(ctx context.Context, args map[string]interface{}, progres
 	}
 	
 	if d.mcpMemory == nil {
-		return "", fmt.Errorf("MCP memory not initialized")
+		return "", fmt.Errorf("MCP memory not initialized - please wait for initialization or restart Del")
 	}
 	
 	// Create entity for this memory
-	createReq := MCPRequest{
-		JSONRPC: "2.0",
-		ID:      2,
-		Method:  "tools/call",
-		Params: map[string]interface{}{
-			"name": "create_entities",
-			"arguments": map[string]interface{}{
-				"entities": []map[string]interface{}{
-					{
-						"name":        entity,
-						"entityType":  "concept",
-						"observations": []string{content},
-					},
-				},
+	result, err := d.callMCPTool("create_entities", map[string]interface{}{
+		"entities": []map[string]interface{}{
+			{
+				"name":         entity,
+				"entityType":   "concept",
+				"observations": []string{content},
 			},
 		},
-	}
-	
-	resp, err := d.callMCP(d.mcpMemory, createReq)
+	})
 	if err != nil {
 		return "", fmt.Errorf("failed to create memory: %v", err)
 	}
@@ -523,7 +531,7 @@ func (d *Del) remember(ctx context.Context, args map[string]interface{}, progres
 		Content:  "Memory stored successfully",
 	}
 	
-	return fmt.Sprintf("Remembered: %s\nStored as entity: %s\nResponse: %v", content, entity, resp.Result), nil
+	return fmt.Sprintf("Remembered: %s\nStored as entity: %s\nResult: %s", content, entity, result), nil
 }
 
 func (d *Del) recall(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
@@ -540,25 +548,13 @@ func (d *Del) recall(ctx context.Context, args map[string]interface{}, progress
 	}
 	
 	if d.mcpMemory == nil {
-		return "", fmt.Errorf("MCP memory not initialized")
-	}
-	
-	// Search memory
-	searchReq := MCPRequest{
-		JSONRPC: "2.0",
-		ID:      3,
-		Method:  "tools/call",
-		Params: map[string]interface{}{
-			"name": "search_nodes",
-			"arguments": map[string]interface{}{
-				"query": query,
-			},
-		},
+		return "", fmt.Errorf("MCP memory not initialized - please wait for initialization or restart Del")
 	}
 	
-	resp, err := d.callMCP(d.mcpMemory, searchReq)
+	// Search memory using read_graph for now (search_nodes seems to have issues)
+	result, err := d.callMCPTool("read_graph", map[string]interface{}{})
 	if err != nil {
-		return "", fmt.Errorf("failed to search memory: %v", err)
+		return "", fmt.Errorf("failed to read memory: %v", err)
 	}
 	
 	progress <- StreamMessage{
@@ -568,7 +564,7 @@ func (d *Del) recall(ctx context.Context, args map[string]interface{}, progress
 		Content:  "Memory search completed",
 	}
 	
-	return fmt.Sprintf("Memory search for '%s':\n%v", query, resp.Result), nil
+	return fmt.Sprintf("Memory search for '%s':\n%s", query, result), nil
 }
 
 func (d *Del) forget(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
@@ -585,23 +581,13 @@ func (d *Del) forget(ctx context.Context, args map[string]interface{}, progress
 	}
 	
 	if d.mcpMemory == nil {
-		return "", fmt.Errorf("MCP memory not initialized")
+		return "", fmt.Errorf("MCP memory not initialized - please wait for initialization or restart Del")
 	}
 	
 	// Delete entity
-	deleteReq := MCPRequest{
-		JSONRPC: "2.0",
-		ID:      4,
-		Method:  "tools/call",
-		Params: map[string]interface{}{
-			"name": "delete_entities",
-			"arguments": map[string]interface{}{
-				"entityIds": []string{entity},
-			},
-		},
-	}
-	
-	resp, err := d.callMCP(d.mcpMemory, deleteReq)
+	result, err := d.callMCPTool("delete_entities", map[string]interface{}{
+		"entityIds": []string{entity},
+	})
 	if err != nil {
 		return "", fmt.Errorf("failed to delete memory: %v", err)
 	}
@@ -613,7 +599,7 @@ func (d *Del) forget(ctx context.Context, args map[string]interface{}, progress
 		Content:  "Memory deleted successfully",
 	}
 	
-	return fmt.Sprintf("Forgot: %s\nResponse: %v", entity, resp.Result), nil
+	return fmt.Sprintf("Forgot: %s\nResult: %s", entity, result), nil
 }
 
 func (d *Del) readFile(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
@@ -2675,6 +2661,8 @@ func (d *Del) renderUI() {
 		case MessageTypeSystem:
 			if msg.Error != "" {
 				fmt.Printf("❌ Error: %s\n", msg.Error)
+			} else if msg.Content != "" {
+				fmt.Printf("â„šī¸  %s\n", msg.Content)
 			}
 		}
 	}
@@ -2719,18 +2707,7 @@ func (d *Del) Start(ctx context.Context) {
 }
 
 func (d *Del) cleanup() {
-	if d.mcpMemory != nil {
-		if d.mcpMemory.stdin != nil {
-			d.mcpMemory.stdin.Close()
-		}
-		if d.mcpMemory.stdout != nil {
-			d.mcpMemory.stdout.Close()
-		}
-		if d.mcpMemory.process != nil {
-			d.mcpMemory.process.Process.Kill()
-			d.mcpMemory.process.Wait()
-		}
-	}
+	// No cleanup needed for exec-based MCP calls
 }
 
 func main() {