Problem
When a tool's inputSchema is built from a Pydantic model containing nested BaseModel types, model_json_schema() emits a \$defs block with named definitions and \$ref pointers at each use site. The output is valid JSON Schema 2020-12, but some MCP clients (notably Claude Desktop during the tools/list discovery flow) don't resolve internal \$ref references — tools with nested types in their inputs become invisible or unusable in those clients.
The current workaround that downstream MCP servers reach for is "flatten every input model by hand" — this leaks a Pydantic limitation into the public API surface of every MCP author who hits the issue. In our case (research-mcp, ~14 tools), we hand-flattened all input models.
Proposed shape
A small, additive helper that post-processes the output of model_json_schema():
```python
from mcp.server.mcpserver.utilities.json_schema import dereference_json_schema
flat = dereference_json_schema(MyToolInput.model_json_schema())
`flat` has no $defs; every internal $ref has been inlined.
```
- Pure function. Doesn't mutate the input.
- Only handles internal `#/$defs/` refs. External refs (URLs, pointers into other documents) preserved verbatim.
- Self-referential and mutually recursive definitions are handled safely: `$ref` is preserved at the cycle boundary and the relevant `$defs` entries are retained so the output schema stays valid.
- Sibling keys to `$ref` (allowed by JSON Schema 2020-12; emitted by Pydantic for some types) are merged into the resolved object, with sibling values overriding the resolved definition's values (matches the JSON Schema 2020-12 semantics for sibling annotations).
Default behavior of the SDK is unchanged — this is purely additive. Callers opt in. Existing tools/users who prefer the compact `$ref` form are unaffected.
Why a helper rather than a flag on `model_json_schema`
A flag on `model_json_schema` would have to live in Pydantic. A helper in the SDK is a one-line change at the call site for users who need it, and doesn't require coordinating across projects.
Implementation
I have a working implementation with 21 tests covering: flat schemas, single-level inlining, transitive resolution, arrays, anyOf, sibling key merging, external refs, unknown internal refs, direct self-reference, mutual recursion, and idempotency. All existing `tests/server/mcpserver/test_func_metadata.py` and `test_tool_manager.py` tests still pass (no regressions).
Happy to open a PR once this is `ready for work`.
Problem
When a tool's
inputSchemais built from a Pydantic model containing nestedBaseModeltypes,model_json_schema()emits a\$defsblock with named definitions and\$refpointers at each use site. The output is valid JSON Schema 2020-12, but some MCP clients (notably Claude Desktop during thetools/listdiscovery flow) don't resolve internal\$refreferences — tools with nested types in their inputs become invisible or unusable in those clients.The current workaround that downstream MCP servers reach for is "flatten every input model by hand" — this leaks a Pydantic limitation into the public API surface of every MCP author who hits the issue. In our case (
research-mcp, ~14 tools), we hand-flattened all input models.Proposed shape
A small, additive helper that post-processes the output of
model_json_schema():```python
from mcp.server.mcpserver.utilities.json_schema import dereference_json_schema
flat = dereference_json_schema(MyToolInput.model_json_schema())
`flat` has no $defs; every internal $ref has been inlined.
```
Default behavior of the SDK is unchanged — this is purely additive. Callers opt in. Existing tools/users who prefer the compact `$ref` form are unaffected.
Why a helper rather than a flag on `model_json_schema`
A flag on `model_json_schema` would have to live in Pydantic. A helper in the SDK is a one-line change at the call site for users who need it, and doesn't require coordinating across projects.
Implementation
I have a working implementation with 21 tests covering: flat schemas, single-level inlining, transitive resolution, arrays, anyOf, sibling key merging, external refs, unknown internal refs, direct self-reference, mutual recursion, and idempotency. All existing `tests/server/mcpserver/test_func_metadata.py` and `test_tool_manager.py` tests still pass (no regressions).
Happy to open a PR once this is `ready for work`.