Release line
2.x (main), reproduced at a4f4ccd0.
Bug description
On Python 3.10 a tool whose return annotation is a TypedDict containing a NotRequired key
cannot be registered at all. @mcp.tool() raises while the decorator runs, so the failure lands at
import time and the tool never exists.
3.11 and above are unaffected. The project supports 3.10 (requires-python = ">=3.10", and CI runs
a full 3.10 matrix), and TypedDict is one of the documented structured-output return shapes, so on
that interpreter the documented feature is unusable.
Steps to reproduce
import sys
from typing_extensions import NotRequired, TypedDict
from mcp.server.mcpserver import MCPServer
class Person(TypedDict):
name: str
age: NotRequired[int]
mcp = MCPServer("td")
print("python", sys.version.split()[0])
@mcp.tool()
def get_person() -> Person:
return {"name": "Dave"}
uv run --python 3.10 --frozen --all-extras --dev python repro.py
Actual behaviour
python 3.10.19
pydantic.errors.PydanticForbiddenQualifier: The annotation 'NotRequired[int]' contains the
'typing.NotRequired' type qualifier, which is invalid in the context it is defined.
The same file on 3.13 registers the tool without complaint.
Expected behaviour
The tool registers on every supported interpreter, as it already does on 3.11+.
Root cause
_create_model_from_typeddict (src/mcp/server/mcpserver/utilities/func_metadata.py) resolves the
annotations with typing.get_type_hints and passes each one straight to create_model:
type_hints = get_type_hints(td_type)
...
model_fields[field_name] = (field_type, None)
typing.get_type_hints only strips the Required / NotRequired qualifier from 3.11 onwards.
Below that field_type is still NotRequired[int], and pydantic rejects a bare qualifier outside a
TypedDict context.
Observed directly:
python 3.10.19 typing.get_type_hints(Person) -> {'name': str, 'age': typing_extensions.NotRequired[int]}
python 3.13.12 typing.get_type_hints(Person) -> {'name': str, 'age': int}
typing_extensions.get_type_hints returns the stripped form on both.
Why the tests do not catch it
test_structured_output_typeddict uses all-required keys, and the total=False variants express
optionality through __optional_keys__ rather than a qualifier in the annotation, so no existing
test constructs a NotRequired[...] annotation.
Note
A fix is already included in #3225, because the tests added there cannot pass on 3.10 without it.
Filing separately since the defect is independent of that PR's subject and is worth tracking on its
own — happy to split the fix out into its own PR if you would prefer.
Disclosure
Investigated with AI assistance (Claude Code). Filed by me as the human contributor.
Release line
2.x (
main), reproduced ata4f4ccd0.Bug description
On Python 3.10 a tool whose return annotation is a
TypedDictcontaining aNotRequiredkeycannot be registered at all.
@mcp.tool()raises while the decorator runs, so the failure lands atimport time and the tool never exists.
3.11 and above are unaffected. The project supports 3.10 (
requires-python = ">=3.10", and CI runsa full 3.10 matrix), and
TypedDictis one of the documented structured-output return shapes, so onthat interpreter the documented feature is unusable.
Steps to reproduce
Actual behaviour
The same file on 3.13 registers the tool without complaint.
Expected behaviour
The tool registers on every supported interpreter, as it already does on 3.11+.
Root cause
_create_model_from_typeddict(src/mcp/server/mcpserver/utilities/func_metadata.py) resolves theannotations with
typing.get_type_hintsand passes each one straight tocreate_model:typing.get_type_hintsonly strips theRequired/NotRequiredqualifier from 3.11 onwards.Below that
field_typeis stillNotRequired[int], and pydantic rejects a bare qualifier outside aTypedDict context.
Observed directly:
typing_extensions.get_type_hintsreturns the stripped form on both.Why the tests do not catch it
test_structured_output_typeddictuses all-required keys, and thetotal=Falsevariants expressoptionality through
__optional_keys__rather than a qualifier in the annotation, so no existingtest constructs a
NotRequired[...]annotation.Note
A fix is already included in #3225, because the tests added there cannot pass on 3.10 without it.
Filing separately since the defect is independent of that PR's subject and is worth tracking on its
own — happy to split the fix out into its own PR if you would prefer.
Disclosure
Investigated with AI assistance (Claude Code). Filed by me as the human contributor.