| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
pydantic emits a self-referential model as {"$defs": {...}, "$ref": "#/$defs/Model"}
with no type at the root. Tool.outputSchema requires type: object at the root, and
strict clients (TypeScript SDK 1.x, C# SDK 1.x, python-sdk 2.x on a 2025-11-25
session) reject the entire tools/list result when one tool publishes that shape.
Inline the referenced definition onto the root when the generated schema is a
bare local $ref, keeping $defs for the nested references. Backport of the fix on
main.
Github-Issue: #3337
| ref = schema.get("$ref") | ||
| if not isinstance(ref, str) or not ref.startswith(_LOCAL_DEFS_PREFIX): | ||
| return schema | ||
| definition = cast(dict[str, Any], schema["$defs"][ref.removeprefix(_LOCAL_DEFS_PREFIX)]) |
There was a problem hiding this comment.
🟡 Minor/edge: _inline_root_ref does unguarded schema["$defs"][name] lookups, so a root-level $ref without a matching local definition raises KeyError instead of being passed through
Extended reasoning...A user customizes a return model's schema (e.g. model_config = ConfigDict(json_schema_extra={"$ref": "#/$defs/X"}) or a custom schema generator) so the generated schema has a root $ref starting with #/$defs/ but no $defs key or no X entry. Before this change the schema was published as-is; after it, func_metadata raises an uncaught KeyError inside _try_create_model_and_schema (the surrounding try only wraps model_json_schema), so mcp.tool() registration crashes at import/startup instead of registering the tool.
Verification: nit — Line 62 of src/mcp/server/fastmcp/utilities/func_metadata.py performs unguarded lookups: definition = cast(dict[str, Any], schema["$defs"][ref.removeprefix(_LOCAL_DEFS_PREFIX)]). The guard at lines 59-61 only checks that $ref is a string starting with "#/$defs/"; it never checks that $defs exists or contains the referenced name. The enclosing try/except in `_try_create_model_and_sc
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Backport of #3376 to the v1.x line. Refs #3337.
When a FastMCP tool's return type is self-referential, pydantic emits the output schema as {"$defs": {...}, "$ref": "#/$defs/Node"} with no type at the root. Tool.outputSchema requires type: "object" at the root on every protocol version v1.x speaks, and strict clients — TypeScript SDK 1.x, C# SDK 1.x, and python-sdk 2.x on a 2025-11-25 session — reject the entire tools/list result when one tool publishes that shape. This inlines the referenced definition onto the root and keeps $defs for the nested references.
Motivation and Context
On v1.x nothing errors on the Python side (Tool.outputSchema is dict[str, Any] and isn't shape-checked), so the failure shows up in the peer: a host embedding the TypeScript SDK 1.x sees the server as having no tools at all. That's the same report as PrefectHQ/fastmcp#2455. Given the blast radius against the most common client family and the size of the change, this seemed worth carrying on the maintenance line.
How Has This Been Tested?
Breaking Changes
None. The only observable change is the JSON content of outputSchema for recursive return types: the root gains the definition's keys, $defs is unchanged, structuredContent is unchanged.
Types of changes
Checklist
Additional context
Same out-of-scope note as #3376: a RootModel whose root isn't an object still publishes a non-object root.
AI Disclaimer