# coding=utf-8 """ @Time :2026/6/30 @Author :tao.chen """ from contextlib import asynccontextmanager from fastapi import FastAPI from common.factory import init_mcp_server from common.logging import logger from common.mcp_service import discover_and_filter # --- MCP service registry --- # # Each service package declares itself via a `service.py` that exports a # `SERVICE: McpService` instance. The list of known service import paths # lives in `common.mcp_service.BUILTIN_SERVICES`. The lifespan below calls # `discover_and_filter()` to import each, validate them, apply the # MCP_SERVICES env whitelist, and mount the survivors. # # To add a new MCP service: # 1. Build a package with a FastAPI app. # 2. Add `/service.py` exporting `SERVICE: McpService`. # 3. Append `".service"` to `BUILTIN_SERVICES` in # `common/mcp_service.py`. def _log_mcp_tools(mcp_server, service) -> None: """Log every tool the MCP server exposes, with its description. fastapi-mcp appends an auto-generated "### Responses: ..." block to every tool description; we strip that here so the startup log stays scannable. Each tool is logged on one line: ` N. tool_name - desc`. """ tools = mcp_server.tools # Single INFO line per service summarizing how many tools it exposes. # Per-tool name + description is DEBUG-level so production logs (INFO) # stay scannable but operators can flip to DEBUG to see the full list. logger.info(f"MCP service {service.name!r}: {len(tools)} tool(s) registered") for i, tool in enumerate(tools, 1): desc = (tool.description or "").strip() if "### Responses" in desc: desc = desc.split("### Responses", 1)[0].rstrip() desc = " ".join(desc.split()) logger.debug(f" {service.name}.{tool.name} - {desc}") @asynccontextmanager async def lifespan(app: "FastAPI"): logger.info(f"Startup {app.title}") services = discover_and_filter() for svc in services: logger.info( f"Mounting MCP service {svc.name!r} at {svc.mount_path} " f"(source: {svc.app.title} v{svc.app.version})" ) mcp = init_mcp_server(svc.app) mcp.mount_http(app, mount_path=svc.mount_path) _log_mcp_tools(mcp, svc) yield logger.info(f"Shutdown {app.title}") app = FastAPI(title="Main App", lifespan=lifespan) @app.get("/health") def health_check(): return {"status": "ok"} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)