Files

77 lines
2.7 KiB
Python

"""MCP Server embedded in Django.
Uses asyncio stdio transport. Registered via the Django management command
``python manage.py start_mcp``.
Django ORM initialisation happens at the management-command level so that
the server thread has access to models when needed (e.g. during feature
extraction).
"""
import asyncio
import json
import os
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import TextContent
# Ensure Django is configured when this module is imported from the
# management command. The management command calls django.setup()
# before constructing the server.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tianxuan.settings')
from .session_store import SessionStore
from .tool_registry import get_tools_meta, handle_call
class TLSAnalyzerMCPServer:
"""Embedded MCP server for the TLS Analyzer.
Exposes 11 tools over stdio transport. The :class:`SessionStore`
singleton holds datasets and analysis results in memory for the
lifetime of the server process.
"""
def __init__(self):
self.server = Server("tls-analyzer")
self.store = SessionStore()
self._setup_tools()
# ── tool registration ───────────────────────────────────────────────
def _setup_tools(self) -> None:
"""Register all tools with the MCP server via the SDK decorator API.
Uses :func:`get_tools_meta` for tool metadata (name, description,
inputSchema) and :func:`handle_call` for request routing.
"""
tools_meta = get_tools_meta()
_tools_map = {t.name: t for t in tools_meta}
@self.server.list_tools()
async def list_tools():
return tools_meta
@self.server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
result = await handle_call(name, arguments)
return [TextContent(type="text", text=json.dumps(result, default=str))]
# ── lifecycle ───────────────────────────────────────────────────────
async def run(self) -> None:
"""Start the MCP server with stdio transport.
Blocks until the transport is closed.
"""
async with stdio_server() as (read, write):
await self.server.run(
read,
write,
self.server.create_initialization_options(),
)
def run_sync(self) -> None:
"""Synchronous entry point for the management command."""
asyncio.run(self.run())