Repository navigation
Description
Python FastMCP servers have no route either: @mcp.tool is the largest measured registration idiom
Summary
#431 shipped a built-in registry of MCP tool-registration idioms covering TypeScript
and Go — the two languages its three vendor walks measured. The 30-server survey it
required before choosing that set is published at
docs/mcp-registration-idioms.md, and it
found one shape above the coverage line that #431 deliberately did not ship:
| idiom | repositories | largest single repo |
|---|---|---|
Python @mcp.tool (FastMCP decorator) |
5 | awslabs/mcp (488 call sites) |
Go Tool{Name: "…"} |
3 | github/github-mcp-server (135) |
TS .tool( / .registerTool("…" |
3 | cloudflare/mcp-server-cloudflare (85) |
Python FastMCP is the largest single shape in the survey by both measures. redis/mcp-redis,
neo4j-contrib/mcp-neo4j, chroma-core/chroma-mcp and awslabs/mcp are all in the state the
MongoDB and Grafana servers were in before #431: detect returns is_agent_project: false.
Why it was deferred rather than skipped
It is a different extraction mechanism, and #393's lesson is that the mechanism is
reusable but each one needs its own adversarial probe list before it can claim anything.
Concretely, none of these hold for the lexical reader #431 shipped:
- The name defaults to the decorated function's name and is only sometimes a literal
(@mcp.tool(name="…")). - The input schema comes from the signature, so unlike TS/Go this route could carry
real parameters — which changes what theSHIP-SCHEMA-*family sees. - Provenance is an import binding (
from mcp.server.fastmcp import FastMCP,
from fastmcp import FastMCP), not a declared dependency, and the binding is what stops
@anything.toolfrom reading as a registration. - Python has a real AST in-tree, so a masking lexer would be the wrong tool: the existing
inputs/_python_framework.pymachinery is the right base.
The lexical reader had eight fail-open constructs in its first draft, all found by
writing the probe list. A Python idiom bolted onto #431's increment without its own list
would have shipped without that check.
Scope
- One idiom,
py_fastmcp_decorator, in the existing built-in registry — same registry,
samemediumceiling, same per-file completeness rule, same exclusion-ledger accounting
for a name this reader cannot resolve. - AST-based, following
google_adk's binding-table approach rather than a text scan. - Its own adversarial probe list, at minimum: a decorator on an alias
(t = mcp.tool), a name built at runtime, a decorator applied by a factory,
functools.wraps, a conditional registration, a re-bound module symbol, and
@app.toolwhereappis not a FastMCP instance.
Acceptance
-
detectonredis/mcp-redisandneo4j-contrib/mcp-neo4jreturns
is_agent_project: truewith a named source. -
scanenumerates their tools, with parameters read from the signature. - A decorator whose name is built at runtime is reported unenumerated and reaches the
exclusion ledger, as in MCP servers whose tool surface exists only as code have no route:detectreports the official MongoDB and Grafana servers as not agent projects #431. - The published survey table in
docs/mcp-registration-idioms.mdmoves the Python row
to "shipped". -
IDIOM_REGISTRY_VERSIONbumps, and the trigger catalog's token list follows.
Filed from #431's survey; the measurement is the evidence the
roadmap non-goal clause asks for.
Activity
docs: link the two follow-ups the #431 survey and review produced
Read an MCP server's tool surface out of its own source (#431) (#483)
pengfei-threemoonslab commentedon Sep 2, 2026
Priority: P1, v0.18.0. By the survey this issue cites, Python FastMCP
@mcp.toolis the largest single registration idiom (5 repositories, 488 call sites inawslabs/mcpalone); without it the top-N MCP study excludes its biggest segment. Same registry, same measured-confidence discipline as #431; a runtime-built tool name stays unenumerated and lands in the exclusion ledger. Sequencing: land onmainbehind the v0.16.0 release candidate — it must not enter the wheel that #456's receipts bind to unless the candidate is re-cut deliberately.Read the Python `@mcp.tool` decorator, in both detectors (#484) (#535)
Resolve the injected Context by its binding, not its spelling (#539)
FastMCP signature projection resolves the injected Context and disclo…