Skip to content

Dereference $ref in tool schemas for MCP client compatibility#2808

Merged
jlowin merged 1 commit intomainfrom
fix/dereference-input-schemas
Jan 7, 2026
Merged

Dereference $ref in tool schemas for MCP client compatibility#2808
jlowin merged 1 commit intomainfrom
fix/dereference-input-schemas

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Jan 7, 2026

Some MCP clients (like VS Code Copilot) don't properly handle $ref in tool input schemas. When Pydantic generates schemas for Enum types, it uses $defs with $ref pointers:

{
  "$defs": {"Category": {"enum": ["food", "transport"], "type": "string"}},
  "properties": {"category": {"$ref": "#/$defs/Category"}}
}

VS Code Copilot appears to strip $defs before sending to Claude, leaving dangling $ref pointers that cause tool calls to fail.

This PR dereferences all $ref entries in compress_schema() using jsonref, producing clean inlined schemas:

{
  "properties": {"category": {"enum": ["food", "transport"], "type": "string"}}
}

Self-referencing schemas (which can't be fully dereferenced) fall back to resolve_root_ref() for MCP spec compliance.

Background

We previously closed PRs #1192 and #1641 which proposed similar solutions. Our position was that FastMCP's obligation is MCP spec compliance, not working around individual client bugs. However, this issue has persisted for a long time across multiple popular clients (Claude Desktop, VS Code Copilot) that are otherwise excellent. The tradeoff is minimal—slightly larger schemas due to inlining—with no functional compromise. Given how widespread and long-standing this is, it feels reasonable to make this accommodation.

Closes #2807, closes #1193, fixes #2236

Some MCP clients (e.g., VS Code Copilot) don't properly handle $ref
in tool input schemas, causing enum parameters to fail. This change
dereferences all $ref entries by inlining definitions using jsonref.

Self-referencing schemas fall back to resolve_root_ref() since they
can't be fully dereferenced.

Closes #2807, closes #1193
@jlowin jlowin added the bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality. label Jan 7, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 7, 2026

Walkthrough

This pull request refactors schema dereferencing by introducing a new dereference_refs() function that inlines $ref values using jsonref.replace_refs and removes $defs sections when fully dereferenced. The compress_schema() function now calls dereference_refs() at the start to ensure MCP compatibility. The prune_defs parameter is removed from compress_schema() signature. Separate resolve_root_ref calls in tool.py are removed since dereferencing is now centralized in compress_schema(). All compress_schema() calls in tool_transform.py are updated to remove the prune_defs argument.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: dereferencing $ref entries in tool schemas for MCP client compatibility, which is the core objective of this PR.
Linked Issues check ✅ Passed The changes directly address both linked issues: #2807 (Opus rejecting $ref in input schemas) and #1193 (Claude Desktop sending null for enum refs). The PR dereferences $ref entries to produce inlined schemas compatible with stricter MCP clients.
Out of Scope Changes check ✅ Passed All changes are directly scoped to dereferencing $ref in tool schemas. Modifications to tool.py, tool_transform.py, and json_schema.py are focused on the schema dereferencing workflow with no unrelated changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The PR description provides a clear and comprehensive overview of the changes, including problem statement, solution, and context.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 81e2bd8365

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +35 to +40
try:
# Use jsonref to resolve all $ref references
# proxies=False returns plain dicts (not proxy objects)
# lazy_load=False resolves immediately
dereferenced = replace_refs(schema, proxies=False, lazy_load=False)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict dereferencing to local $defs

dereference_refs() calls replace_refs(schema, proxies=False, lazy_load=False) without filtering for local #/$defs references. jsonref resolves external URIs eagerly when lazy_load=False, so any schema that includes an external $ref (valid in JSON Schema and possible in user‑supplied tool/input schemas) will now trigger network fetches or raise resolution errors during schema compression. Previously those refs were left intact, so this is a regression that can break tool creation or hang in offline environments. Consider limiting dereferencing to local refs or providing a loader that rejects non‑local refs and leaves them untouched.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working. Reports of errors, unexpected behavior, or broken functionality.

Projects

None yet

1 participant