Files
odoo-ai/agent_service/routers/mcp_router.py
ActiveBlue Build 66b114cdcf feat(mcp): add MCP gateway — 14 tools over SSE, all agent calls forced local
Architecture:
- agent_service/mcp/tools.py: 14 Tool definitions with JSON schemas
    dispatch, finance_query, accounting_query, crm_query, sales_query,
    project_query, elearning_query, expenses_query, employees_query,
    get_health, list_agents, trigger_sweep, get_pending_approvals, approve_directive
- agent_service/mcp/server.py: mcp.Server with list_tools + call_tool handlers
- agent_service/routers/mcp_router.py: Starlette routes at /mcp/sse + /mcp/messages
- main.py: mounts MCP routes alongside existing FastAPI routers (graceful fallback if mcp not installed)

Privacy guarantee (enforced in server.py, not by convention):
- _force_local_context() sets llm_router._privacy_mode = 'local' before EVERY agent call
- _restore_mode() restores original mode after the tool returns
- HIPAA agents (finance, accounting, expenses, employees) were already Ollama-only;
  MCP adds a second enforcement layer for all 8 agents
- MCP client (e.g. Claude Code CLI) receives only tool results — no LLM completions cross the boundary

Usage (Claude Code CLI):
  claude mcp add --transport sse http://192.168.2.47:8001/mcp/sse
  or copy claude_mcp_config.json to ~/.claude/mcp_servers.json

requirements.txt: added mcp==1.3.0
tests/test_mcp_server.py: 13 tests covering tool count, schemas, HIPAA labelling, privacy override

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 16:45:49 -04:00

58 lines
1.8 KiB
Python

"""
MCP gateway router — mounts SSE transport at /mcp/sse and /mcp/messages.
All requests that invoke agents are forced to local (Ollama) mode inside
server.py before any agent reasoning begins. The MCP client (e.g. Claude Code
CLI) only ever receives tool *results*, not any LLM completions.
"""
from __future__ import annotations
import logging
from mcp.server.models import InitializationOptions
from starlette.requests import Request
from starlette.responses import Response
from starlette.routing import Route
from ..mcp.server import create_mcp_server, build_sse_transport
logger = logging.getLogger(__name__)
# Singletons created once at module import time
_mcp_server = create_mcp_server()
_sse_transport = build_sse_transport()
_INIT_OPTIONS = InitializationOptions(
server_name='activeblue-ai',
server_version='0.1.0',
capabilities=_mcp_server.get_capabilities(
notification_options=None,
experimental_capabilities={},
),
)
async def handle_sse(request: Request) -> Response:
"""SSE endpoint — client connects here to establish a session."""
logger.debug('MCP SSE connection from %s', request.client)
async with _sse_transport.connect_sse(
request.scope, request.receive, request._send
) as streams:
await _mcp_server.run(streams[0], streams[1], _INIT_OPTIONS)
return Response()
async def handle_post_message(request: Request) -> Response:
"""POST endpoint — client sends JSON-RPC tool call messages here."""
await _sse_transport.handle_post_message(
request.scope, request.receive, request._send
)
return Response()
# Starlette routes to be added to the FastAPI app
mcp_routes = [
Route('/mcp/sse', endpoint=handle_sse, methods=['GET']),
Route('/mcp/messages', endpoint=handle_post_message, methods=['POST']),
]