diff --git a/files_mcp/service.py b/files_mcp/service.py new file mode 100644 index 0000000..34c25d4 --- /dev/null +++ b/files_mcp/service.py @@ -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", +) diff --git a/main.py b/main.py index d524c32..9d62f99 100644 --- a/main.py +++ b/main.py @@ -1,54 +1,33 @@ # coding=utf-8 """ -@Time :2026/6/24 +@Time :2026/6/30 @Author :tao.chen """ from contextlib import asynccontextmanager -from dataclasses import dataclass from fastapi import FastAPI from common.factory import init_mcp_server from common.logging import logger -from files_mcp import app as files_app -from spark_executor import app as spark_executor_app +from common.mcp_service import discover_and_filter # --- MCP service registry --- # -# Each McpService entry becomes one mount under the root app. The lifespan -# iterates this list at startup, so adding a new MCP service is a one-line -# append here — no edits to the lifespan function needed. +# 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. # -# Example (future): -# McpService(name="metrics", app=metrics_app, mount_path="/metrics-mcp"), -# McpService(name="catalog", app=catalog_app, mount_path="/catalog-mcp"), +# 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`. -@dataclass(frozen=True) -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: +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 @@ -71,7 +50,8 @@ def _log_mcp_tools(mcp_server, service: McpService) -> None: @asynccontextmanager async def lifespan(app: "FastAPI"): logger.info(f"Startup {app.title}") - for svc in MCP_SERVICES: + 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})" diff --git a/spark_executor/service.py b/spark_executor/service.py new file mode 100644 index 0000000..f8e60ec --- /dev/null +++ b/spark_executor/service.py @@ -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", +)