Repository navigation
Conversation
Since Python 3.14 (PEP 649/749) annotations are evaluated lazily, so a tool may
legally name a type the module defines further down:
@app.tool()
def search(q: Query) -> list[Hit]: ...
class Query(BaseModel): ...
`MCPServer.tool()` builds the `Tool` eagerly, and three of its steps call
`inspect.signature(fn)` while only needing the parameter *names*. The default
`annotation_format` evaluates `__annotate__`, so registration died with a bare
`NameError: name 'Query' is not defined` raised from inside `inspect`.
Those steps now read names through `signature_parameters()`, which asks for
`Format.FORWARDREF` on 3.14+ and keeps the plain call on older Pythons, where
string annotations are already returned unevaluated. Registration then reaches
the step that genuinely needs the types and fails there with the `InvalidSignature`
it already produced for a quoted annotation -- now naming the unresolved type, so
the error says which name to move above the decorated function instead of only
that something could not be evaluated.
Deferring the whole schema build so the tool would register and resolve on first
use is the other half of the issue and is left to the maintainers' preference;
this change makes the failure consistent and actionable either way.
Refs modelcontextprotocol#3668
|
This PR has been closed automatically. This repo only keeps pull requests open when they come from a maintainer, or from a contributor a maintainer has assigned to the linked issue, and you aren't currently assigned to #3668. If a maintainer assigns you to #3668, this PR reopens on its own and there's nothing more you need to do here. Assignment is a maintainer call based on capacity; comments that only ask to be assigned don't factor in. What does help is engaging on the issue itself by confirming the repro, explaining why it matters for your use case, or describing the approach you'd take. You're welcome to keep pushing commits here (just avoid force-pushing, since GitHub can't reopen a rewritten branch), but that on its own won't get the PR reviewed or the issue assigned, and realistically most auto-closed PRs stay closed. There's no need to open a new PR either way. CONTRIBUTING.md has the full reasoning, but in short:
Maintainers: reopen, remove |
What
Since Python 3.14 (PEP 649/749) annotations are evaluated lazily, so a tool may legally name a type the module defines further down, without quoting:
MCPServer.tool()builds theTooleagerly, and three of its steps callinspect.signature(fn)while only needing the parameter names. The defaultannotation_formatevaluates__annotate__, so registration died with a bareNameError: name 'Query' is not definedraised from insideinspect. Quoting the annotation — orfrom __future__ import annotations— failed one step later withInvalidSignature, so the error type depended on how the annotation was written.How
Those three steps now read names through a new
signature_parameters()inmcp/shared/_callable_inspection.py, which asks forannotationlib.Format.FORWARDREFon 3.14+ and keeps the plaininspect.signaturecall on older Pythons, where string annotations are already returned unevaluated:resolve.py—find_resolved_parametersandbuild_resolver_plansserver.py— the resource decorator (the one place there that needs a default reads it from the same object)Registration then reaches the step that genuinely needs the types and fails there with the
InvalidSignatureit already produced for a quoted annotation. That message now includes the unresolved name, so it says which type to move above the decorated function instead of only that something could not be evaluated:Scope
The issue's preferred outcome is that the tool registers, by deferring the schema build until first use. That is a change to the
Toollifecycle and the issue explicitly leaves it to the maintainers' preference, so this PR delivers the outcome the issue also accepts — a clearInvalidSignaturenaming the tool and the unresolved name — and makes the failure consistent regardless of how the annotation is written. Deferring the build on top of this would be additive.Tests
Three tests, all built with
execand gated on 3.14+, because an unquoted undefined name raises where the function is defined on older Pythons and would make the module fail to import:test_func_metadata.py— the error names the callable and the missing typetest_resolve.py—find_resolved_parametersno longer raises a bareNameErrorfor a lazily-annotated parametertest_server.py— the issue's repro: registering a tool whose types are defined below it raisesInvalidSignaturenaming'search'andQuery, not aNameErrorVerified on Python 3.14.4:
tests/server/mcpserver/644 passed (641 before, plus these three); the three fail on the unpatched source with exactly theNameErrorfrom the issue;ruff check,ruff format --checkandpyright(strict) clean on the changed files. Fulltests/run has 4 failures, all stdio-subprocess tests that fail identically on the base commit in this environment.Fixes #3668