Add MCP HTTP/SSE server and complete n8n integration
Major Changes: - Implemented MCP HTTP/SSE transport server for n8n and web clients - Created mcp_server/http_server.py with FastAPI for JSON-RPC 2.0 over HTTP - Added health check endpoint (/health) for container monitoring - Refactored mcp-server/ to mcp_server/ (Python module structure) - Updated Dockerfile.mcp to run HTTP server with health checks MCP Server Features: - 7 memory tools exposed via MCP (add, search, get, update, delete) - HTTP/SSE transport on port 8765 for n8n integration - stdio transport for Claude Code integration - JSON-RPC 2.0 protocol implementation - CORS support for web clients n8n Integration: - Successfully tested with AI Agent workflows - MCP Client Tool configuration documented - Working webhook endpoint tested and verified - System prompt optimized for automatic user_id usage Documentation: - Created comprehensive Mintlify documentation site - Added docs/mcp/introduction.mdx - MCP server overview - Added docs/mcp/installation.mdx - Installation guide - Added docs/mcp/tools.mdx - Complete tool reference - Added docs/examples/n8n.mdx - n8n integration guide - Added docs/examples/claude-code.mdx - Claude Code setup - Updated README.md with MCP HTTP server info - Updated roadmap to mark Phase 1 as complete Bug Fixes: - Fixed synchronized delete operations across Supabase and Neo4j - Updated memory_service.py with proper error handling - Fixed Neo4j connection issues in delete operations Configuration: - Added MCP_HOST and MCP_PORT environment variables - Updated .env.example with MCP server configuration - Updated docker-compose.yml with MCP container health checks Testing: - Added test scripts for MCP HTTP endpoint verification - Created test workflows in n8n - Verified all 7 memory tools working correctly - Tested synchronized operations across both stores Version: 1.0.0 Status: Phase 1 Complete - Production Ready 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
165
mcp_server/main.py
Normal file
165
mcp_server/main.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""
|
||||
T6 Mem0 v2 MCP Server
|
||||
Model Context Protocol server for memory operations
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import asyncio
|
||||
from mcp.server import Server
|
||||
from mcp.server.stdio import stdio_server
|
||||
from mcp.types import (
|
||||
Resource,
|
||||
Tool,
|
||||
TextContent,
|
||||
ImageContent,
|
||||
EmbeddedResource
|
||||
)
|
||||
from mem0 import Memory
|
||||
|
||||
from config import mem0_config, settings
|
||||
from mcp_server.tools import MemoryTools
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, settings.log_level.upper()),
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[logging.StreamHandler(sys.stderr)] # MCP uses stderr for logs
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class T6Mem0Server:
|
||||
"""T6 Mem0 v2 MCP Server"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize MCP server"""
|
||||
self.server = Server("t6-mem0-v2")
|
||||
self.memory: Memory | None = None
|
||||
self.tools: MemoryTools | None = None
|
||||
|
||||
# Setup handlers
|
||||
self.setup_handlers()
|
||||
|
||||
def setup_handlers(self):
|
||||
"""Setup MCP server handlers"""
|
||||
|
||||
@self.server.list_resources()
|
||||
async def list_resources() -> list[Resource]:
|
||||
"""
|
||||
List available resources (for future extension)
|
||||
"""
|
||||
return []
|
||||
|
||||
@self.server.read_resource()
|
||||
async def read_resource(uri: str) -> str:
|
||||
"""
|
||||
Read resource by URI (for future extension)
|
||||
"""
|
||||
logger.warning(f"Resource read not implemented: {uri}")
|
||||
return ""
|
||||
|
||||
@self.server.list_tools()
|
||||
async def list_tools() -> list[Tool]:
|
||||
"""List available memory tools"""
|
||||
logger.info("Listing tools")
|
||||
if not self.tools:
|
||||
raise RuntimeError("Tools not initialized")
|
||||
return self.tools.get_tool_definitions()
|
||||
|
||||
@self.server.call_tool()
|
||||
async def call_tool(name: str, arguments: dict) -> list[TextContent | ImageContent | EmbeddedResource]:
|
||||
"""
|
||||
Handle tool calls
|
||||
|
||||
Args:
|
||||
name: Tool name
|
||||
arguments: Tool arguments
|
||||
|
||||
Returns:
|
||||
Tool response
|
||||
"""
|
||||
logger.info(f"Tool called: {name}")
|
||||
logger.debug(f"Arguments: {arguments}")
|
||||
|
||||
if not self.tools:
|
||||
raise RuntimeError("Tools not initialized")
|
||||
|
||||
# Route to appropriate handler
|
||||
handlers = {
|
||||
"add_memory": self.tools.handle_add_memory,
|
||||
"search_memories": self.tools.handle_search_memories,
|
||||
"get_memory": self.tools.handle_get_memory,
|
||||
"get_all_memories": self.tools.handle_get_all_memories,
|
||||
"update_memory": self.tools.handle_update_memory,
|
||||
"delete_memory": self.tools.handle_delete_memory,
|
||||
"delete_all_memories": self.tools.handle_delete_all_memories,
|
||||
}
|
||||
|
||||
handler = handlers.get(name)
|
||||
if not handler:
|
||||
logger.error(f"Unknown tool: {name}")
|
||||
return [TextContent(type="text", text=f"Error: Unknown tool '{name}'")]
|
||||
|
||||
try:
|
||||
return await handler(arguments)
|
||||
except Exception as e:
|
||||
logger.error(f"Tool execution failed: {e}", exc_info=True)
|
||||
return [TextContent(type="text", text=f"Error executing tool: {str(e)}")]
|
||||
|
||||
async def initialize(self):
|
||||
"""Initialize memory service"""
|
||||
logger.info("Initializing T6 Mem0 v2 MCP Server")
|
||||
logger.info(f"Environment: {settings.environment}")
|
||||
|
||||
try:
|
||||
# Initialize Mem0
|
||||
logger.info("Initializing Mem0...")
|
||||
self.memory = Memory.from_config(config_dict=mem0_config)
|
||||
logger.info("Mem0 initialized successfully")
|
||||
|
||||
# Initialize tools
|
||||
self.tools = MemoryTools(self.memory)
|
||||
logger.info("Tools initialized successfully")
|
||||
|
||||
logger.info("T6 Mem0 v2 MCP Server ready")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize server: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
async def run(self):
|
||||
"""Run the MCP server"""
|
||||
try:
|
||||
# Initialize before running
|
||||
await self.initialize()
|
||||
|
||||
# Run server with stdio transport
|
||||
logger.info("Starting MCP server with stdio transport")
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
await self.server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
self.server.create_initialization_options()
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Server error: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
async def main():
|
||||
"""Main entry point"""
|
||||
try:
|
||||
server = T6Mem0Server()
|
||||
await server.run()
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Server stopped by user")
|
||||
except Exception as e:
|
||||
logger.error(f"Fatal error: {e}", exc_info=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user