Summary
I'm building an MCP server (LivePilot — 325 tools across 45 domains, production system for Ableton Live 12) and I need to iterate every registered tool at module-import time in order to post-process the generated JSON Schemas.
There's currently no stable public way to do this, so I'm reaching into private internals and the paths have changed across FastMCP versions. I'd love a small public surface to depend on instead.
Why I need to walk the registry
Some MCP hosts serialize integer/number tool arguments as JSON strings ("5" instead of 5). To keep those tools working, I walk every tool's schema and mutate numeric properties into anyOf: [integer, string] with a coercion hint. ~60 of my tools accept numeric parameters, so without this post-processing they error out on those hosts.
The patch is applied at import time (before any event loop is running), so await mcp.list_tools() isn't a natural fit — it'd require spinning an event loop just to read registrations.
What the workaround looks like today
After several private-internal renames, my current probe chain is:
def _get_all_tools():
"""Get all registered tools — defends against FastMCP internal drift."""
probes = [
# FastMCP 0.x
("_tool_manager._tools", lambda: list(mcp._tool_manager._tools.values())),
# FastMCP 3.0–3.2
("_local_provider._components",
lambda: list(mcp._local_provider._components.values())),
# FastMCP 3.3+ speculative (anticipated rename)
("_local_provider._tools",
lambda: list(mcp._local_provider._tools.values())),
# Future public API — this FR
("list_tools", lambda: list(mcp.list_tools())),
]
for label, fn in probes:
try:
tools = fn()
except (AttributeError, TypeError):
continue
if tools:
return tools
# Loud stderr diagnostic + return []
...
I pin fastmcp>=3.0.0,<3.3.0 specifically because of this fragility. (LivePilot source)
What I'd love
Any one of these would work — a tiny, stable public surface:
# Option A — property
for tool in mcp.tools:
...
# Option B — sync accessor
for tool in mcp.list_tools_sync():
...
The sync-iteration flavor is the important bit for the import-time use case. The internals can stay private; just expose the registry.
Scope
~10 lines of upstream code (a property on FastMCP that returns the current tools).
Happy to send a PR if a maintainer can confirm this is a direction you're open to. Thank you for FastMCP — it's the backbone of the whole project.
Summary
I'm building an MCP server (LivePilot — 325 tools across 45 domains, production system for Ableton Live 12) and I need to iterate every registered tool at module-import time in order to post-process the generated JSON Schemas.
There's currently no stable public way to do this, so I'm reaching into private internals and the paths have changed across FastMCP versions. I'd love a small public surface to depend on instead.
Why I need to walk the registry
Some MCP hosts serialize integer/number tool arguments as JSON strings (
"5"instead of5). To keep those tools working, I walk every tool's schema and mutate numeric properties intoanyOf: [integer, string]with a coercion hint. ~60 of my tools accept numeric parameters, so without this post-processing they error out on those hosts.The patch is applied at import time (before any event loop is running), so
await mcp.list_tools()isn't a natural fit — it'd require spinning an event loop just to read registrations.What the workaround looks like today
After several private-internal renames, my current probe chain is:
I pin
fastmcp>=3.0.0,<3.3.0specifically because of this fragility. (LivePilot source)What I'd love
Any one of these would work — a tiny, stable public surface:
The sync-iteration flavor is the important bit for the import-time use case. The internals can stay private; just expose the registry.
Scope
~10 lines of upstream code (a property on
FastMCPthat returns the current tools).Happy to send a PR if a maintainer can confirm this is a direction you're open to. Thank you for FastMCP — it's the backbone of the whole project.