refactor(mcp): use discover_and_filter in main.py; add per-service manifests
- main.py: drops the hard-coded McpService class and MCP_SERVICES list. Lifespan now calls discover_and_filter() which does import + env filtering. Unset MCP_SERVICES reproduces today's behavior exactly. - spark_executor/service.py: new file exporting SERVICE = McpService( name='spark_executor', app=spark_executor.app, mount_path='/spark-executor-mcp'). - files_mcp/service.py: new file exporting SERVICE = McpService( name='files', app=files_mcp.app, mount_path='/files-mcp'). The McpService dataclass itself moved to common/mcp_service.py in the prior commit. 327 existing tests pass unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,15 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@Time :2026/6/30
|
||||||
|
@Author :tao.chen
|
||||||
|
|
||||||
|
Pluggable manifest for the files MCP service.
|
||||||
|
"""
|
||||||
|
from common.mcp_service import McpService
|
||||||
|
from files_mcp import app
|
||||||
|
|
||||||
|
SERVICE: McpService = McpService(
|
||||||
|
name="files",
|
||||||
|
app=app,
|
||||||
|
mount_path="/files-mcp",
|
||||||
|
)
|
||||||
@@ -1,54 +1,33 @@
|
|||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
"""
|
"""
|
||||||
@Time :2026/6/24
|
@Time :2026/6/30
|
||||||
@Author :tao.chen
|
@Author :tao.chen
|
||||||
"""
|
"""
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
from common.factory import init_mcp_server
|
from common.factory import init_mcp_server
|
||||||
from common.logging import logger
|
from common.logging import logger
|
||||||
from files_mcp import app as files_app
|
from common.mcp_service import discover_and_filter
|
||||||
from spark_executor import app as spark_executor_app
|
|
||||||
|
|
||||||
|
|
||||||
# --- MCP service registry ---
|
# --- MCP service registry ---
|
||||||
#
|
#
|
||||||
# Each McpService entry becomes one mount under the root app. The lifespan
|
# Each service package declares itself via a `service.py` that exports a
|
||||||
# iterates this list at startup, so adding a new MCP service is a one-line
|
# `SERVICE: McpService` instance. The list of known service import paths
|
||||||
# append here — no edits to the lifespan function needed.
|
# 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.
|
||||||
#
|
#
|
||||||
# Example (future):
|
# To add a new MCP service:
|
||||||
# McpService(name="metrics", app=metrics_app, mount_path="/metrics-mcp"),
|
# 1. Build a package with a FastAPI app.
|
||||||
# McpService(name="catalog", app=catalog_app, mount_path="/catalog-mcp"),
|
# 2. Add `<package>/service.py` exporting `SERVICE: McpService`.
|
||||||
|
# 3. Append `"<package>.service"` to `BUILTIN_SERVICES` in
|
||||||
|
# `common/mcp_service.py`.
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
def _log_mcp_tools(mcp_server, service) -> None:
|
||||||
class McpService:
|
|
||||||
"""One MCP-exposed FastAPI sub-app to be mounted under the root."""
|
|
||||||
|
|
||||||
name: str # short identifier used in startup logs (e.g. "spark_executor")
|
|
||||||
app: FastAPI # the sub-app whose routes will be exposed as MCP tools
|
|
||||||
mount_path: str # HTTP path under the root app (e.g. "/spark-executor-mcp")
|
|
||||||
|
|
||||||
|
|
||||||
MCP_SERVICES: list[McpService] = [
|
|
||||||
McpService(
|
|
||||||
name="spark_executor",
|
|
||||||
app=spark_executor_app,
|
|
||||||
mount_path="/spark-executor-mcp",
|
|
||||||
),
|
|
||||||
McpService(
|
|
||||||
name="files",
|
|
||||||
app=files_app,
|
|
||||||
mount_path="/files-mcp",
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _log_mcp_tools(mcp_server, service: McpService) -> None:
|
|
||||||
"""Log every tool the MCP server exposes, with its description.
|
"""Log every tool the MCP server exposes, with its description.
|
||||||
|
|
||||||
fastapi-mcp appends an auto-generated "### Responses: ..." block to
|
fastapi-mcp appends an auto-generated "### Responses: ..." block to
|
||||||
@@ -71,7 +50,8 @@ def _log_mcp_tools(mcp_server, service: McpService) -> None:
|
|||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: "FastAPI"):
|
async def lifespan(app: "FastAPI"):
|
||||||
logger.info(f"Startup {app.title}")
|
logger.info(f"Startup {app.title}")
|
||||||
for svc in MCP_SERVICES:
|
services = discover_and_filter()
|
||||||
|
for svc in services:
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Mounting MCP service {svc.name!r} at {svc.mount_path} "
|
f"Mounting MCP service {svc.name!r} at {svc.mount_path} "
|
||||||
f"(source: {svc.app.title} v{svc.app.version})"
|
f"(source: {svc.app.title} v{svc.app.version})"
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@Time :2026/6/30
|
||||||
|
@Author :tao.chen
|
||||||
|
|
||||||
|
Pluggable manifest for the spark_executor MCP service. The name and
|
||||||
|
mount_path here are matched against the MCP_SERVICES env whitelist and
|
||||||
|
the BUILTIN_SERVICES list in common.mcp_service.
|
||||||
|
"""
|
||||||
|
from common.mcp_service import McpService
|
||||||
|
from spark_executor import app
|
||||||
|
|
||||||
|
SERVICE: McpService = McpService(
|
||||||
|
name="spark_executor",
|
||||||
|
app=app,
|
||||||
|
mount_path="/spark-executor-mcp",
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user