| | |
| | """ |
| | Mnemo MCP Server |
| | Provides memory capabilities to Claude and other MCP-compatible LLMs |
| | |
| | Usage: |
| | uvx mnemo-memory # Run as MCP server |
| | |
| | Or in Claude config: |
| | { |
| | "mcpServers": { |
| | "mnemo": {"command": "uvx", "args": ["mnemo-memory"]} |
| | } |
| | } |
| | """ |
| |
|
| | import json |
| | import sys |
| | from typing import Any, Dict, List |
| |
|
| | try: |
| | from mcp.server import Server |
| | from mcp.types import Tool, TextContent |
| | HAS_MCP = True |
| | except ImportError: |
| | HAS_MCP = False |
| | print("Warning: MCP not installed. Install with: pip install mcp", file=sys.stderr) |
| |
|
| | from mnemo import Mnemo, should_inject_memory |
| |
|
| | |
| | memory = Mnemo(use_real_embeddings=True) |
| |
|
| | |
| | TOOLS = [ |
| | { |
| | "name": "mnemo_add", |
| | "description": "Store a new memory. Use this to save important information for later retrieval.", |
| | "inputSchema": { |
| | "type": "object", |
| | "properties": { |
| | "content": { |
| | "type": "string", |
| | "description": "The content to remember" |
| | }, |
| | "metadata": { |
| | "type": "object", |
| | "description": "Optional metadata (tags, source, etc.)" |
| | } |
| | }, |
| | "required": ["content"] |
| | } |
| | }, |
| | { |
| | "name": "mnemo_search", |
| | "description": "Search stored memories. Returns relevant memories ranked by similarity.", |
| | "inputSchema": { |
| | "type": "object", |
| | "properties": { |
| | "query": { |
| | "type": "string", |
| | "description": "Search query" |
| | }, |
| | "top_k": { |
| | "type": "integer", |
| | "description": "Number of results (default: 5)", |
| | "default": 5 |
| | } |
| | }, |
| | "required": ["query"] |
| | } |
| | }, |
| | { |
| | "name": "mnemo_should_inject", |
| | "description": "Check if memory should be injected for this query. Uses context-check algorithm with 90% accuracy.", |
| | "inputSchema": { |
| | "type": "object", |
| | "properties": { |
| | "query": { |
| | "type": "string", |
| | "description": "The query to check" |
| | }, |
| | "context": { |
| | "type": "string", |
| | "description": "Optional additional context" |
| | } |
| | }, |
| | "required": ["query"] |
| | } |
| | }, |
| | { |
| | "name": "mnemo_get_context", |
| | "description": "Get formatted memory context for injection into prompts.", |
| | "inputSchema": { |
| | "type": "object", |
| | "properties": { |
| | "query": { |
| | "type": "string", |
| | "description": "Search query for finding relevant context" |
| | }, |
| | "top_k": { |
| | "type": "integer", |
| | "description": "Number of memories to include (default: 3)", |
| | "default": 3 |
| | } |
| | }, |
| | "required": ["query"] |
| | } |
| | }, |
| | { |
| | "name": "mnemo_list", |
| | "description": "List all stored memories.", |
| | "inputSchema": { |
| | "type": "object", |
| | "properties": {} |
| | } |
| | }, |
| | { |
| | "name": "mnemo_delete", |
| | "description": "Delete a specific memory by ID.", |
| | "inputSchema": { |
| | "type": "object", |
| | "properties": { |
| | "memory_id": { |
| | "type": "string", |
| | "description": "ID of the memory to delete" |
| | } |
| | }, |
| | "required": ["memory_id"] |
| | } |
| | }, |
| | { |
| | "name": "mnemo_stats", |
| | "description": "Get memory system statistics.", |
| | "inputSchema": { |
| | "type": "object", |
| | "properties": {} |
| | } |
| | }, |
| | { |
| | "name": "mnemo_clear", |
| | "description": "Clear all stored memories. Use with caution!", |
| | "inputSchema": { |
| | "type": "object", |
| | "properties": { |
| | "confirm": { |
| | "type": "boolean", |
| | "description": "Must be true to confirm deletion" |
| | } |
| | }, |
| | "required": ["confirm"] |
| | } |
| | } |
| | ] |
| |
|
| |
|
| | def handle_tool_call(name: str, arguments: Dict[str, Any]) -> str: |
| | """Handle a tool call and return result""" |
| | |
| | if name == "mnemo_add": |
| | content = arguments.get("content", "") |
| | metadata = arguments.get("metadata", {}) |
| | mem_id = memory.add(content, metadata) |
| | return json.dumps({ |
| | "status": "success", |
| | "memory_id": mem_id, |
| | "message": f"Memory stored successfully" |
| | }) |
| | |
| | elif name == "mnemo_search": |
| | query = arguments.get("query", "") |
| | top_k = arguments.get("top_k", 5) |
| | results = memory.search(query, top_k=top_k) |
| | |
| | return json.dumps({ |
| | "status": "success", |
| | "count": len(results), |
| | "results": [ |
| | { |
| | "id": r.id, |
| | "content": r.content, |
| | "score": round(r.score, 3), |
| | "metadata": r.metadata |
| | } |
| | for r in results |
| | ] |
| | }) |
| | |
| | elif name == "mnemo_should_inject": |
| | query = arguments.get("query", "") |
| | context = arguments.get("context", "") |
| | should, reason = should_inject_memory(query, context) |
| | |
| | return json.dumps({ |
| | "should_inject": should, |
| | "reason": reason, |
| | "recommendation": "Inject memory context" if should else "Skip memory - standalone query" |
| | }) |
| | |
| | elif name == "mnemo_get_context": |
| | query = arguments.get("query", "") |
| | top_k = arguments.get("top_k", 3) |
| | context = memory.get_context(query, top_k=top_k) |
| | |
| | return json.dumps({ |
| | "status": "success", |
| | "context": context if context else None, |
| | "message": "Context retrieved" if context else "No relevant context found" |
| | }) |
| | |
| | elif name == "mnemo_list": |
| | memories = memory.list_all() |
| | return json.dumps({ |
| | "status": "success", |
| | "count": len(memories), |
| | "memories": [ |
| | { |
| | "id": m.id, |
| | "content": m.content[:200] + "..." if len(m.content) > 200 else m.content, |
| | "created_at": m.created_at, |
| | "metadata": m.metadata |
| | } |
| | for m in memories |
| | ] |
| | }) |
| | |
| | elif name == "mnemo_delete": |
| | memory_id = arguments.get("memory_id", "") |
| | success = memory.delete(memory_id) |
| | |
| | return json.dumps({ |
| | "status": "success" if success else "error", |
| | "message": f"Memory {memory_id} deleted" if success else f"Memory {memory_id} not found" |
| | }) |
| | |
| | elif name == "mnemo_stats": |
| | stats = memory.get_stats() |
| | return json.dumps({ |
| | "status": "success", |
| | "stats": stats |
| | }) |
| | |
| | elif name == "mnemo_clear": |
| | if arguments.get("confirm", False): |
| | memory.clear() |
| | return json.dumps({ |
| | "status": "success", |
| | "message": "All memories cleared" |
| | }) |
| | else: |
| | return json.dumps({ |
| | "status": "error", |
| | "message": "Must set confirm=true to clear all memories" |
| | }) |
| | |
| | else: |
| | return json.dumps({ |
| | "status": "error", |
| | "message": f"Unknown tool: {name}" |
| | }) |
| |
|
| |
|
| | def run_mcp_server(): |
| | """Run as MCP server""" |
| | if not HAS_MCP: |
| | print("Error: MCP not installed. Install with: pip install mcp", file=sys.stderr) |
| | sys.exit(1) |
| | |
| | server = Server("mnemo-memory") |
| | |
| | @server.list_tools() |
| | async def list_tools() -> List[Tool]: |
| | return [ |
| | Tool( |
| | name=tool["name"], |
| | description=tool["description"], |
| | inputSchema=tool["inputSchema"] |
| | ) |
| | for tool in TOOLS |
| | ] |
| | |
| | @server.call_tool() |
| | async def call_tool(name: str, arguments: Dict[str, Any]) -> List[TextContent]: |
| | result = handle_tool_call(name, arguments) |
| | return [TextContent(type="text", text=result)] |
| | |
| | |
| | import asyncio |
| | asyncio.run(server.run()) |
| |
|
| |
|
| | def run_cli(): |
| | """Simple CLI for testing""" |
| | print("Mnemo Memory System - CLI Mode") |
| | print("Commands: add, search, inject, context, list, stats, clear, quit") |
| | print("-" * 50) |
| | |
| | while True: |
| | try: |
| | cmd = input("\n> ").strip().lower() |
| | |
| | if cmd == "quit": |
| | break |
| | elif cmd == "add": |
| | content = input("Content: ") |
| | result = handle_tool_call("mnemo_add", {"content": content}) |
| | print(result) |
| | elif cmd == "search": |
| | query = input("Query: ") |
| | result = handle_tool_call("mnemo_search", {"query": query}) |
| | print(result) |
| | elif cmd == "inject": |
| | query = input("Query: ") |
| | result = handle_tool_call("mnemo_should_inject", {"query": query}) |
| | print(result) |
| | elif cmd == "context": |
| | query = input("Query: ") |
| | result = handle_tool_call("mnemo_get_context", {"query": query}) |
| | print(result) |
| | elif cmd == "list": |
| | result = handle_tool_call("mnemo_list", {}) |
| | print(result) |
| | elif cmd == "stats": |
| | result = handle_tool_call("mnemo_stats", {}) |
| | print(result) |
| | elif cmd == "clear": |
| | confirm = input("Are you sure? (yes/no): ") |
| | if confirm.lower() == "yes": |
| | result = handle_tool_call("mnemo_clear", {"confirm": True}) |
| | print(result) |
| | else: |
| | print("Unknown command. Use: add, search, inject, context, list, stats, clear, quit") |
| | |
| | except KeyboardInterrupt: |
| | print("\nBye!") |
| | break |
| | except Exception as e: |
| | print(f"Error: {e}") |
| |
|
| |
|
| | if __name__ == "__main__": |
| | if len(sys.argv) > 1 and sys.argv[1] == "--cli": |
| | run_cli() |
| | else: |
| | run_mcp_server() |
| |
|