Commit d3cadfb
Changed files (1)
cmd
del
cmd/del/main.go
@@ -51,6 +51,7 @@ type Del struct {
thinking bool
thinkingMsg string
startTime time.Time
+ mcpMemory *MCPServer
}
type ToolFunc func(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error)
@@ -59,6 +60,34 @@ type ToolCall struct {
Args map[string]interface{}
}
+// MCP Integration types
+type MCPRequest struct {
+ JSONRPC string `json:"jsonrpc"`
+ ID int `json:"id"`
+ Method string `json:"method"`
+ Params interface{} `json:"params"`
+}
+
+type MCPResponse struct {
+ JSONRPC string `json:"jsonrpc"`
+ ID int `json:"id"`
+ Result interface{} `json:"result,omitempty"`
+ Error *MCPError `json:"error,omitempty"`
+}
+
+type MCPError struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+}
+
+type MCPServer struct {
+ name string
+ command string
+ process *exec.Cmd
+ stdin io.WriteCloser
+ stdout io.ReadCloser
+}
+
func NewDel(model string) *Del {
client, _ := api.ClientFromEnvironment()
@@ -80,6 +109,7 @@ Available tools:
โข PROJECT MANAGEMENT: todo_read, todo_write, exit_plan_mode
โข NOTEBOOKS: notebook_read, notebook_edit (Jupyter support)
โข WEB OPERATIONS: web_fetch, web_search
+โข MEMORY SYSTEM: remember, recall, forget (persistent memory across conversations)
KEY CAPABILITIES:
- Edit files with exact string replacement (edit_file) or multiple edits (multi_edit)
@@ -98,6 +128,8 @@ EXAMPLES:
- "run tests" โ use bash with command "npm test"
- "edit config.yaml" โ use edit_file to make precise changes
- "show todos" โ use todo_read
+- "remember that the user prefers tabs over spaces" โ use remember
+- "what did we learn about Go best practices?" โ use recall
FORMATTING: Always format responses using markdown:
- ## Headers for sections
@@ -113,6 +145,17 @@ IMPORTANT: Use tools first, then provide natural markdown responses based on res
}
d.registerTools()
+
+ // Initialize MCP memory (non-blocking)
+ go func() {
+ if err := d.startMCPMemory(); err != nil {
+ d.emit(StreamMessage{
+ Type: MessageTypeSystem,
+ Error: fmt.Sprintf("Warning: Failed to initialize memory system: %v", err),
+ })
+ }
+ }()
+
return d
}
@@ -348,6 +391,229 @@ func (d *Del) registerTools() {
// Web operations
d.tools["web_fetch"] = d.webFetch
d.tools["web_search"] = d.webSearch
+
+ // MCP Memory operations
+ d.tools["remember"] = d.remember
+ d.tools["recall"] = d.recall
+ d.tools["forget"] = d.forget
+}
+
+// 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)
+ }
+
+ 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{}{
+ "protocolVersion": "2024-11-05",
+ "capabilities": map[string]interface{}{"tools": map[string]interface{}{}},
+ "clientInfo": map[string]interface{}{"name": "del", "version": "1.0.0"},
+ },
+ }
+
+ _, 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)
+ if err != nil {
+ return nil, fmt.Errorf("failed to marshal request: %v", err)
+ }
+
+ if _, err := server.stdin.Write(append(reqJSON, '\n')); err != nil {
+ return nil, fmt.Errorf("failed to write request: %v", err)
+ }
+
+ // Read response
+ scanner := bufio.NewScanner(server.stdout)
+ if !scanner.Scan() {
+ return nil, fmt.Errorf("failed to read response")
+ }
+
+ var resp MCPResponse
+ if err := json.Unmarshal(scanner.Bytes(), &resp); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal response: %v", err)
+ }
+
+ if resp.Error != nil {
+ return nil, fmt.Errorf("MCP error %d: %s", resp.Error.Code, resp.Error.Message)
+ }
+
+ return &resp, nil
+}
+
+// Memory Tools
+func (d *Del) remember(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ content, ok := args["content"].(string)
+ if !ok {
+ return "", fmt.Errorf("missing 'content' argument")
+ }
+
+ entity, _ := args["entity"].(string)
+ if entity == "" {
+ entity = "general_knowledge"
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "remember",
+ Status: "storing",
+ Content: "Storing in memory...",
+ }
+
+ if d.mcpMemory == nil {
+ return "", fmt.Errorf("MCP memory not initialized")
+ }
+
+ // 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},
+ },
+ },
+ },
+ },
+ }
+
+ resp, err := d.callMCP(d.mcpMemory, createReq)
+ if err != nil {
+ return "", fmt.Errorf("failed to create memory: %v", err)
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "remember",
+ Status: "completed",
+ Content: "Memory stored successfully",
+ }
+
+ return fmt.Sprintf("Remembered: %s\nStored as entity: %s\nResponse: %v", content, entity, resp.Result), nil
+}
+
+func (d *Del) recall(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ query, ok := args["query"].(string)
+ if !ok {
+ return "", fmt.Errorf("missing 'query' argument")
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "recall",
+ Status: "searching",
+ Content: fmt.Sprintf("Searching memory for: %s", query),
+ }
+
+ 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,
+ },
+ },
+ }
+
+ resp, err := d.callMCP(d.mcpMemory, searchReq)
+ if err != nil {
+ return "", fmt.Errorf("failed to search memory: %v", err)
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "recall",
+ Status: "completed",
+ Content: "Memory search completed",
+ }
+
+ return fmt.Sprintf("Memory search for '%s':\n%v", query, resp.Result), nil
+}
+
+func (d *Del) forget(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
+ entity, ok := args["entity"].(string)
+ if !ok {
+ return "", fmt.Errorf("missing 'entity' argument")
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "forget",
+ Status: "deleting",
+ Content: fmt.Sprintf("Forgetting: %s", entity),
+ }
+
+ if d.mcpMemory == nil {
+ return "", fmt.Errorf("MCP memory not initialized")
+ }
+
+ // 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)
+ if err != nil {
+ return "", fmt.Errorf("failed to delete memory: %v", err)
+ }
+
+ progress <- StreamMessage{
+ Type: MessageTypeProgress,
+ ToolName: "forget",
+ Status: "completed",
+ Content: "Memory deleted successfully",
+ }
+
+ return fmt.Sprintf("Forgot: %s\nResponse: %v", entity, resp.Result), nil
}
func (d *Del) readFile(ctx context.Context, args map[string]interface{}, progress chan<- StreamMessage) (string, error) {
@@ -2011,6 +2277,67 @@ func (d *Del) buildOllamaTools() []api.Tool {
Function: webSearchFunc,
})
+ // remember tool
+ rememberFunc := api.ToolFunction{
+ Name: "remember",
+ Description: "Store information in persistent memory for future recall",
+ }
+ rememberFunc.Parameters.Type = "object"
+ rememberFunc.Parameters.Required = []string{"content"}
+ rememberFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ rememberFunc.Parameters.Properties["content"] = makeProperty("string", "Information to remember")
+ rememberFunc.Parameters.Properties["entity"] = makeProperty("string", "Optional entity/category for organization")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: rememberFunc,
+ })
+
+ // recall tool
+ recallFunc := api.ToolFunction{
+ Name: "recall",
+ Description: "Search and retrieve information from persistent memory",
+ }
+ recallFunc.Parameters.Type = "object"
+ recallFunc.Parameters.Required = []string{"query"}
+ recallFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ recallFunc.Parameters.Properties["query"] = makeProperty("string", "Search query for memory recall")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: recallFunc,
+ })
+
+ // forget tool
+ forgetFunc := api.ToolFunction{
+ Name: "forget",
+ Description: "Delete specific information from persistent memory",
+ }
+ forgetFunc.Parameters.Type = "object"
+ forgetFunc.Parameters.Required = []string{"entity"}
+ forgetFunc.Parameters.Properties = make(map[string]struct {
+ Type api.PropertyType `json:"type"`
+ Items any `json:"items,omitempty"`
+ Description string `json:"description"`
+ Enum []any `json:"enum,omitempty"`
+ })
+ forgetFunc.Parameters.Properties["entity"] = makeProperty("string", "Entity/memory to delete")
+
+ tools = append(tools, api.Tool{
+ Type: "function",
+ Function: forgetFunc,
+ })
+
return tools
}
@@ -2378,6 +2705,7 @@ func (d *Del) Start(ctx context.Context) {
if input == "quit" || input == "exit" || input == "q" {
fmt.Println("๐ Stay funky!")
+ d.cleanup()
break
}
@@ -2390,6 +2718,21 @@ func (d *Del) Start(ctx context.Context) {
close(d.output)
}
+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()
+ }
+ }
+}
+
func main() {
var model = flag.String("model", "qwen2.5:latest", "Ollama model to use")
var help = flag.Bool("help", false, "Show help message")