The lifespan was hardcoded to mount just one MCP service
(spark_executor_app at /spark-executor-mcp). To add a second MCP
service, you'd have to edit the lifespan function body — error-prone
and not obvious where to add a new entry.
Refactor:
- New McpService dataclass (name, app, mount_path)
- MCP_SERVICES list at module top — adding a new service is one append
- Lifespan iterates MCP_SERVICES; the per-service mount + tool-list
logic is now a helper (_log_mcp_tools)
- Tool-list log now prefixes the service name:
'MCP tools registered (spark_executor, 14):'
instead of just '(14):', so multi-service startups are unambiguous
116/116 still pass. Live verified: gunicorn 2-worker startup logs
each service with its tool list and the source app version.
Future services just need a McpService entry — no lifespan edit. The
MCP_SERVICES list has a docstring example showing the one-liner.
main.py lifespan now enumerates every MCP tool with its description at
startup so operators can see at a glance what the server exposes.
The auto-generated '### Responses:' suffix that fastapi-mcp appends to
each tool description is truncated before logging to keep the startup
log scannable.
Docstrings added to all 12 route handlers in server.py so the MCP tool
descriptions are meaningful (without them, fastapi-mcp would emit empty
descriptions). Each route now carries a summary + description used by
the auto-generated OpenAPI schema.