Summary
Replace the multi-step wrapper chain (make_tool_wrapper, create_mcp_tool, execute_db_tool, asyncio.to_thread) with FastMCP v3 Depends() so database connections and request context are injected directly into handler signatures as hidden parameters.
Background
Every tool currently goes through ~130 lines of plumbing: make_tool_wrapper() strips internal params from the signature, create_mcp_tool() builds an async wrapper with asyncio.to_thread(), execute_db_tool() fetches the connection, and _fetch_request_context() reaches into FastMCP state for the RequestContext. FastMCP v3's Depends() (borrowed from FastAPI) handles all of this natively — parameters annotated with Depends() are automatically hidden from the MCP schema.
Proposed Approach
from fastmcp.dependencies import Depends
def get_db_connection(ctx: Context = CurrentContext()) -> Connection:
tdconn = ctx.lifespan_context["tdconn"]
return tdconn.engine.connect()
def get_request_context() -> RequestContext | None:
ctx = get_context()
return ctx.get_state_sync("request_context")
@mcp.tool
def base_readQuery(
sql: str,
conn: Connection = Depends(get_db_connection),
rc: RequestContext | None = Depends(get_request_context),
) -> str:
"""Run a read-only SQL query."""
...
Files to Change
app.py:323-450
tools/utils/factory.py (can be deleted)
- All
tools/*/handle_*.py handler files (signature change)
Value
- Eliminates
factory.py entirely — create_mcp_tool, _fetch_request_context, and asyncio.to_thread wrapper are no longer needed.
- Eliminates
make_tool_wrapper() in app.py.
handle_* functions can be registered directly with @mcp.tool — no wrapping step.
- Sync handlers still run in a thread pool automatically (FastMCP default for sync tools).
Dependencies
Requires lifespan management (#9) to be in place first. Benefits from docstring Args: sections (#6) being completed beforehand. A phased approach — adopt Depends() for new tools first and migrate existing handlers incrementally — is recommended to reduce risk.
Summary
Replace the multi-step wrapper chain (
make_tool_wrapper,create_mcp_tool,execute_db_tool,asyncio.to_thread) with FastMCP v3Depends()so database connections and request context are injected directly into handler signatures as hidden parameters.Background
Every tool currently goes through ~130 lines of plumbing:
make_tool_wrapper()strips internal params from the signature,create_mcp_tool()builds an async wrapper withasyncio.to_thread(),execute_db_tool()fetches the connection, and_fetch_request_context()reaches into FastMCP state for theRequestContext. FastMCP v3'sDepends()(borrowed from FastAPI) handles all of this natively — parameters annotated withDepends()are automatically hidden from the MCP schema.Proposed Approach
Files to Change
app.py:323-450tools/utils/factory.py(can be deleted)tools/*/handle_*.pyhandler files (signature change)Value
factory.pyentirely —create_mcp_tool,_fetch_request_context, andasyncio.to_threadwrapper are no longer needed.make_tool_wrapper()inapp.py.handle_*functions can be registered directly with@mcp.tool— no wrapping step.Dependencies
Requires lifespan management (#9) to be in place first. Benefits from docstring
Args:sections (#6) being completed beforehand. A phased approach — adoptDepends()for new tools first and migrate existing handlers incrementally — is recommended to reduce risk.