Skip to content

Commit 826bde0

Browse files
fix(mcpserver): resolve TypedDict hints via typing_extensions for Python 3.10
`typing.get_type_hints` only strips the `Required`/`NotRequired` qualifier from 3.11 onwards. Below that the bare qualifier reached `create_model`, so on 3.10 -- a supported version -- ANY tool returning a TypedDict with `NotRequired` keys failed at registration with PydanticForbiddenQualifier, before it could ever be called. `typing_extensions.get_type_hints` strips it on every supported version. Verified against unmodified upstream in a separate worktree: this crash predates this branch and is not introduced by the structuredContent change. Also switches the test module to `typing_extensions.TypedDict`, which pydantic requires below 3.12 for `NotRequired` to be honoured.
1 parent 8d306c0 commit 826bde0

2 files changed

Lines changed: 10 additions & 4 deletions

File tree

src/mcp/server/mcpserver/utilities/func_metadata.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from collections.abc import Awaitable, Callable, Sequence
55
from itertools import chain
66
from types import GenericAlias
7-
from typing import Annotated, Any, Union, cast, get_args, get_origin, get_type_hints
7+
from typing import Annotated, Any, Union, cast, get_args, get_origin
88

99
import anyio
1010
import anyio.to_thread
@@ -13,7 +13,7 @@
1313
from pydantic import BaseModel, ConfigDict, Field, PydanticUserError, WithJsonSchema, create_model
1414
from pydantic.fields import FieldInfo
1515
from pydantic.json_schema import GenerateJsonSchema, JsonSchemaWarningKind
16-
from typing_extensions import is_typeddict
16+
from typing_extensions import get_type_hints, is_typeddict
1717
from typing_inspection.introspection import (
1818
UNKNOWN,
1919
AnnotationSource,
@@ -514,6 +514,9 @@ def _create_model_from_typeddict(td_type: type[Any]) -> type[BaseModel]:
514514
515515
The created model will have the same name and fields as the TypedDict.
516516
"""
517+
# `typing_extensions.get_type_hints` strips the `Required`/`NotRequired` qualifier on
518+
# every supported version; `typing.get_type_hints` only does so from 3.11, and below
519+
# that the bare qualifier reaches `create_model`, which pydantic rejects outright.
517520
type_hints = get_type_hints(td_type)
518521
required_keys = getattr(td_type, "__required_keys__", set(type_hints.keys()))
519522

tests/server/mcpserver/test_func_metadata.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@
55
# pyright: reportUnknownLambdaType=false
66
from collections.abc import Callable
77
from dataclasses import dataclass
8-
from typing import Annotated, Any, Final, NamedTuple, TypedDict
8+
from typing import Annotated, Any, Final, NamedTuple
99

1010
import annotated_types
1111
import jsonschema
1212
import pytest
1313
from dirty_equals import IsPartialDict
1414
from mcp_types import CallToolResult, InputRequiredResult
1515
from pydantic import BaseModel, Field
16-
from typing_extensions import NotRequired
16+
17+
# Pydantic requires `typing_extensions.TypedDict` (not `typing.TypedDict`) below 3.12,
18+
# otherwise a `NotRequired` qualifier is rejected as invalid in the context it is defined.
19+
from typing_extensions import NotRequired, TypedDict
1720

1821
from mcp.server.mcpserver.exceptions import InvalidSignature
1922
from mcp.server.mcpserver.utilities.func_metadata import func_metadata

0 commit comments

Comments
 (0)