Skip to content

Commit 167903b

Browse files
committed
fix: tighten schema typing for pyright
1 parent bbf0e12 commit 167903b

2 files changed

Lines changed: 23 additions & 11 deletions

File tree

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

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@
1414

1515
from __future__ import annotations
1616

17-
from typing import Any
17+
from typing import Any, TypeAlias, cast
1818

19+
JSONPrimitive: TypeAlias = None | str | int | float | bool
20+
JSONValue: TypeAlias = JSONPrimitive | list["JSONValue"] | dict[str, "JSONValue"]
21+
JSONObject: TypeAlias = dict[str, JSONValue]
1922

20-
def dereference_local_refs(schema: dict[str, Any]) -> dict[str, Any]:
23+
def dereference_local_refs(schema: JSONObject) -> JSONObject:
2124
"""Inline local ``$ref`` pointers in a JSON Schema.
2225
2326
Behavior mirrors ``dereferenceLocalRefs`` in the TypeScript SDK:
@@ -51,18 +54,24 @@ def dereference_local_refs(schema: dict[str, Any]) -> dict[str, Any]:
5154
else:
5255
return schema
5356

54-
defs: dict[str, Any] = schema[defs_key] or {}
57+
raw_defs = schema[defs_key]
58+
if raw_defs is None:
59+
return schema
60+
if not isinstance(raw_defs, dict):
61+
return schema
62+
63+
defs: JSONObject = raw_defs
5564
if not defs:
5665
return schema
5766

5867
# Cache resolved defs to avoid redundant traversal on diamond references.
59-
resolved_defs: dict[str, Any] = {}
68+
resolved_defs: dict[str, JSONValue] = {}
6069
# Def names where a cycle was detected — their $ref is left in place and
6170
# their $defs entries must be preserved in the output.
6271
cyclic_defs: set[str] = set()
6372
prefix = f"#/{defs_key}/"
6473

65-
def inline(node: Any, stack: set[str]) -> Any:
74+
def inline(node: JSONValue, stack: set[str]) -> JSONValue:
6675
if node is None or isinstance(node, str | int | float | bool):
6776
return node
6877
if isinstance(node, list):
@@ -95,14 +104,17 @@ def inline(node: Any, stack: set[str]) -> Any:
95104
resolved_defs[def_name] = resolved
96105

97106
# Siblings of $ref (JSON Schema 2020-12).
98-
siblings = {k: v for k, v in node.items() if k != "$ref"}
107+
siblings: JSONObject = {k: v for k, v in node.items() if k != "$ref"}
99108
if siblings and isinstance(resolved, dict):
100-
resolved_siblings = {k: inline(v, stack) for k, v in siblings.items()}
101-
return {**resolved, **resolved_siblings}
109+
resolved_schema = cast(JSONObject, resolved)
110+
resolved_siblings: JSONObject = {
111+
key: inline(value, stack) for key, value in siblings.items()
112+
}
113+
return {**resolved_schema, **resolved_siblings}
102114
return resolved
103115

104116
# Regular object — recurse into values, but skip the top-level $defs container.
105-
result: dict[str, Any] = {}
117+
result: JSONObject = {}
106118
for key, value in node.items():
107119
if node is schema and key in ("$defs", "definitions"):
108120
continue
@@ -116,7 +128,7 @@ def inline(node: Any, stack: set[str]) -> Any:
116128

117129
# Preserve only cyclic defs in the output.
118130
if cyclic_defs:
119-
preserved = {name: defs[name] for name in cyclic_defs if name in defs}
131+
preserved: JSONObject = {name: defs[name] for name in cyclic_defs if name in defs}
120132
inlined[defs_key] = preserved
121133

122134
return inlined

tests/server/mcpserver/utilities/test_schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def test_inlines_through_array_of_objects(self) -> None:
158158
159159
Covers the `if isinstance(node, list)` branch of the inner inline().
160160
"""
161-
schema = {
161+
schema: dict[str, Any] = {
162162
"anyOf": [
163163
{"$ref": "#/$defs/A"},
164164
{"$ref": "#/$defs/B"},

0 commit comments

Comments
 (0)