2e2fb0a21811ce2dbaca5bb738b0be8f94de051e
4
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a956add0b6 |
Fix MCP tools with $ref/$defs being silently rejected (#60165)
Closes #60162. MCP servers whose tool `inputSchema` uses `$ref`/`$defs` (e.g., Notion MCP v2.x, and any server using Zod/Pydantic-generated schemas) are silently rejected with: ``` ERROR Schema cannot be made compatible because it contains "$ref" ``` The affected tools are dropped from the agent panel — the user never sees them and there is no user-visible error. ## Root cause `adapt_to_json_schema_subset` rejects any schema containing `$ref` via `UNSUPPORTED_KEYS`: ```rust const UNSUPPORTED_KEYS: [&str; 4] = ["if", "then", "else", "$ref"]; ``` This is hit by every provider that uses `JsonSchemaSubset` format (Google Gemini, xAI Grok, OpenAI-compatible proxies, Vercel AI Gateway, Copilot Chat for Google/xAI vendors, OpenRouter for gemini/grok models). Providers using `JsonSchema` (Anthropic direct, OpenAI direct) don't hit this check — `$ref` is passed through to the API, which may or may not handle it correctly. This is not an edge case. Every modern MCP server using Zod (TypeScript), Pydantic (Python), or JSON Schema with shared definitions generates `$ref`/`$defs` in tool schemas. ## Fix Add a `resolve_refs` step in `adapt_schema_to_format` that dereferences all `$ref` pointers using the document's own `$defs` (or legacy `definitions`) map, making the schema self-contained before format-specific processing. Applied at the entry point so both `JsonSchema` and `JsonSchemaSubset` formats benefit. **Scope note:** previously `JsonSchema` providers (Anthropic direct, OpenAI direct) received the raw `$ref`/`$defs` and were expected to handle it themselves — which most do not. After this change, both paths receive a self-contained schema with refs inlined. This is intentional: it fixes the same root cause for both paths and avoids provider-specific behavior divergence. Supported `$ref` forms: - `#/$defs/<name>` (JSON Schema draft 2019-09+) - `#/definitions/<name>` (draft 4-7 legacy) Edge cases: - **Nested `$ref`** (definition references another definition): resolved recursively. - **Sibling properties alongside `$ref`** (e.g. `{ "$ref": "...", "description": "..." }`, legal under draft 2019-09+): merged onto the resolved definition, with siblings overriding the definition's keys. - **Cyclic references** (A → B → A, or self-referential schemas like a Tree node): replaced with an empty schema `{}` ("any JSON value"). The tool still works, just without type info for that recursive field. - **Unsupported `$ref` forms** (e.g., external URLs): returns an error with a clear message. - **Missing definition target**: returns an error naming the missing ref. ## Testing Added 10 unit tests in `crates/language_model_core/src/tool_schema.rs` that cover the patterns produced by Zod/Pydantic-generated MCP schemas: - `test_refs_are_resolved_via_adapt_schema_to_format` — basic `$ref` → `$defs` resolution - `test_refs_in_defs_are_resolved` — nested `$ref` (definition references another definition) - `test_refs_in_array_items_are_resolved` — `$ref` inside `array.items` - `test_legacy_definitions_prefix_is_supported` — old `#/definitions/<name>` prefix - `test_schema_without_defs_is_unchanged` — schemas with no `$defs` are unaffected - `test_refs_fail_for_unsupported_prefix` — external URL refs error clearly - `test_refs_fail_for_missing_definition` — missing target errors clearly - `test_cyclic_refs_are_replaced_with_empty_schema` — A → B → A cycle replaced with `{}` - `test_self_referential_ref_is_replaced_with_empty_schema` — Tree-like self-ref replaced with `{}` - `test_ref_sibling_properties_are_preserved` — sibling properties alongside `$ref` are merged onto the resolved definition Existing tests (which call `adapt_to_json_schema_subset` and `preprocess_json_schema` directly) are unaffected — the fix is additive at the `adapt_schema_to_format` level. ## Disclosure I used an LLM to help draft the implementation and tests. I reviewed and understand the change — it adds one new function (`resolve_refs`) with two helpers (`parse_ref`, `resolve_refs_recursive`), plus unit tests. Release Notes: - Fixed MCP tools with `$ref`/`$defs` in their `inputSchema` being silently rejected by providers using the JSON Schema Subset format (Google Gemini, xAI Grok, OpenAI-compatible proxies, etc.). Tools from servers like Notion MCP v2.x, and any server using Zod or Pydantic-generated schemas, now work correctly. --------- Co-authored-by: Bennet Bo Fenner <bennetbo@gmx.de> Co-authored-by: Bennet Bo Fenner <bennet@zed.dev> |
||
|
|
ae47ec9ac0 |
language_models: Fix Gemini tool parameter nullability and multi-type schema (#49292)
Transform JSON schemas for Google AI tools to use `nullable: true` instead of `type: ["type", "null"]`, which is not supported by the Gemini API. Additionally, convert multi-type arrays (e.g., `type: ["string", "number"]`) to `anyOf` constraints, as Gemini expects a single string for the `type` field. This handles recursive transformation of properties, items, definitions, and logical operators, safely merging conflicting `anyOf` and `allOf` constraints. Closes https://github.com/zed-industries/zed/issues/44875 Closes https://github.com/zed-industries/zed/issues/32429 Release Notes: - Fixed a bug where using Gemini with certain tools (especially via MCP) resulted in "Invalid JSON payload received" errors due to incompatible JSON schema formats. ## Testing Added unit tests in `crates/language_model/src/tool_schema.rs` covering nullability, multi-types, and `oneOf` conversions. ### Manual Testing with MCP Test Server The [MCP Test Server](https://github.com/dastrobu/mcp-test-server) was used to verify these edge cases with Gemini 3 Flash. #### Setup 1. Install the test server: `cargo install --git https://github.com/dastrobu/mcp-test-server` 2. Add to Zed `settings.json`: ```json "context_servers": [ { "command": "mcp-test-server" } ] ``` Use the following pattern in a chat window: ``` call the add_tool function to create a new tool: weather with input schema: { "type": "object", "properties": { "city": { "type": ["string", "null"] } } } ``` Afterwards: ... ``` call it ``` Without fix: <img width="671" height="448" alt="image" src="https://github.com/user-attachments/assets/e8b6e7bd-d431-4a7e-8c9a-fff73d82127d" /> With fix: <img width="729" height="334" alt="image" src="https://github.com/user-attachments/assets/eb758765-4c34-4915-a8cf-dd4bfb993619" /> #### Cases verified manually: **1. Nullability in properties** - **Input:** ```json { "type": "object", "properties": { "city": { "type": ["string", "null"] } } } ``` - **Converted:** ```json { "type": "object", "properties": { "city": { "type": "string", "nullable": true } } } ``` **2. Multi-type properties** - **Input:** ```json { "type": "object", "properties": { "city": { "type": ["string", "number"] } } } ``` - **Converted:** ```json { "type": "object", "properties": { "city": { "anyOf": [ { "type": "string" }, { "type": "number" } ] } } } ``` **3. Explicit `anyOf` with nullability** - **Input:** ```json { "type": "object", "properties": { "city": { "anyOf": [ { "type": "string" }, { "type": "null" } ] } } } ``` - **Converted:** ```json { "type": "object", "properties": { "city": { "anyOf": [ { "type": "string" }, { "nullable": true } ] } } } ``` **4. Conflicting `anyOf` sources (Multi-type + existing `anyOf`)** - **Input:** ```json { "type": "object", "properties": { "city": { "type": ["string", "number"], "anyOf": [ { "minLength": 5 } ] } } } ``` - **Converted:** ```json { "type": "object", "properties": { "city": { "allOf": [ { "anyOf": [{ "minLength": 5 }] }, { "anyOf": [{ "type": "string" }, { "type": "number" }] } ] } } } ``` Co-authored-by: Richard Feldman <richard@zed.dev> |
||
|
|
c5a2807492 |
Remove smol as a dependency from a bunch of crates (#53603)
We aren't making use of it in these crates and it unblocks some web-related work Release Notes: - N/A or Added/Fixed/Improved ... |
||
|
|
98c17ca160 |
language_models: Refactor deps and extract cloud (#53270)
- `language_model` no longer depends on provider-specific crates such as `anthropic` and `open_ai` (inverted dependency) - `language_model_core` was extracted from `language_model` which contains the types for the provider-specific crates to convert to/from. - `gpui::SharedString` has been extracted into its own crate (still exposed by `gpui`), so `language_model_core` and provider API crates don't have to depend on `gpui`. - Removes some unnecessary `&'static str` | `SharedString` -> `String` -> `SharedString` conversions across the codebase. - Extracts the core logic of the cloud `LanguageModelProvider` into its own crate with simpler dependencies. Release Notes: - N/A --------- Co-authored-by: John Tur <john-tur@outlook.com> |