mnemo-memory / mcp_server.py
AthelaPerk's picture
Update mcp_server.py with smart injection and real embeddings
0065b9f verified
#!/usr/bin/env python3
"""
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
# Global memory instance
memory = Mnemo(use_real_embeddings=True)
# Tool definitions
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)]
# Run server
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()