cognee/cognee-mcp/src/server.py
2025-10-30 18:04:02 +00:00

204 lines
6 KiB
Python
Executable file

"""
MCP Registry Server
A personal registry for discovering and remembering MCP servers.
Available Tools:
- remember_mcp_server: Store MCP server information
- find_mcp_server: Search for servers by requirements
- list_mcp_servers: View all stored servers
- clear_registry: Clear the registry
"""
import sys
import argparse
import asyncio
import subprocess
from pathlib import Path
from typing import Any
from cognee.shared.logging_utils import get_logger, setup_logging
from mcp.server import FastMCP
from starlette.responses import JSONResponse
from starlette.middleware.cors import CORSMiddleware
import uvicorn
from fastmcp import Client as MCPClient
from src.utils.context import set_cognee_client
from src.clients import CogneeClient
from src.tools import clear_registry, find_mcp_server, list_mcp_servers, remember_mcp_server
mcp = FastMCP[Any]("MCP-Registry")
logger = get_logger()
mcp.tool()(clear_registry)
mcp.tool()(find_mcp_server)
mcp.tool()(list_mcp_servers)
mcp.tool()(remember_mcp_server)
async def run_sse_with_cors():
"""Custom SSE transport with CORS middleware."""
sse_app = mcp.sse_app()
sse_app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["GET"],
allow_headers=["*"],
)
config = uvicorn.Config(
sse_app,
host=mcp.settings.host,
port=mcp.settings.port,
log_level=mcp.settings.log_level.lower(),
)
server = uvicorn.Server(config)
await server.serve()
async def run_http_with_cors():
"""Custom HTTP transport with CORS middleware."""
http_app = mcp.streamable_http_app()
http_app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["GET"],
allow_headers=["*"],
)
config = uvicorn.Config(
http_app,
host=mcp.settings.host,
port=mcp.settings.port,
log_level=mcp.settings.log_level.lower(),
)
server = uvicorn.Server(config)
await server.serve()
@mcp.custom_route("/health", methods=["GET"])
async def health_check(request):
return JSONResponse({"status": "ok"})
async def main():
global cognee_client
parser = argparse.ArgumentParser()
parser.add_argument(
"--transport",
choices=["sse", "stdio", "http"],
default="stdio",
help="Transport to use for communication with the client. (default: stdio)",
)
# HTTP transport options
parser.add_argument(
"--host",
default="127.0.0.1",
help="Host to bind the HTTP server to (default: 127.0.0.1)",
)
parser.add_argument(
"--port",
type=int,
default=8000,
help="Port to bind the HTTP server to (default: 8000)",
)
parser.add_argument(
"--path",
default="/mcp",
help="Path for the MCP HTTP endpoint (default: /mcp)",
)
parser.add_argument(
"--log-level",
default="info",
choices=["debug", "info", "warning", "error"],
help="Log level for the HTTP server (default: info)",
)
parser.add_argument(
"--no-migration",
default=False,
action="store_true",
help="Argument stops database migration from being attempted",
)
# Cognee API connection options
parser.add_argument(
"--api-url",
default=None,
help="Base URL of a running Cognee FastAPI server (e.g., http://localhost:8000). "
"If provided, the MCP server will connect to the API instead of using cognee directly.",
)
parser.add_argument(
"--api-token",
default=None,
help="Authentication token for the API (optional, required if API has authentication enabled).",
)
args = parser.parse_args()
# Initialize the global CogneeClient
cognee_client = CogneeClient(api_url=args.api_url, api_token=args.api_token)
set_cognee_client(cognee_client)
mcp.settings.host = args.host
mcp.settings.port = args.port
# Skip migrations when in API mode (the API server handles its own database)
if not args.no_migration and not args.api_url:
# Run Alembic migrations from the main cognee directory where alembic.ini is located
logger.info("Running database migrations...")
migration_result = subprocess.run(
["python", "-m", "alembic", "upgrade", "head"],
capture_output=True,
text=True,
cwd=Path(__file__).resolve().parent.parent.parent,
)
if migration_result.returncode != 0:
migration_output = migration_result.stderr + migration_result.stdout
# Check for the expected UserAlreadyExists error (which is not critical)
if (
"UserAlreadyExists" in migration_output
or "User default_user@example.com already exists" in migration_output
):
logger.warning("Warning: Default user already exists, continuing startup...")
else:
logger.error(f"Migration failed with unexpected error: {migration_output}")
sys.exit(1)
logger.info("Database migrations done.")
elif args.api_url:
logger.info("Skipping database migrations (using API mode)")
logger.info(f"Starting MCP Registry server with transport: {args.transport}")
if args.transport == "stdio":
await mcp.run_stdio_async()
elif args.transport == "sse":
logger.info(f"Running MCP server with SSE transport on {args.host}:{args.port}")
await run_sse_with_cors()
elif args.transport == "http":
logger.info(
f"Running MCP server with Streamable HTTP transport on {args.host}:{args.port}{args.path}"
)
await run_http_with_cors()
if __name__ == "__main__":
logger = setup_logging()
try:
asyncio.run(main())
except Exception as e:
logger.error(f"Error initializing MCP Registry server: {str(e)}")
raise