anthropics / anthropics/claude-ai-mcp
Cowork local/desktop connectors rewrite MCP tool input schemas to draft-07 and discard `$ref`/`$defs` — same server via web custom connector (or Claude Code/Chat) is intact
- Lingua principale
- Nessun dato sulla lingua
- Stelle
- 471
- Fork
- 77
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
### What happened?
When an MCP server is attached to Claude Desktop as a **local connector** (an `mcpServers` stdio entry or an `.mcpb` bundle — in our case a stdio proxy forwarding to a remote streamable-HTTP server), the tool input schemas the model sees in **Cowork** have been rewritten in transit:
- a `"$schema": "http://json-schema.org/draft-07/schema#"` header is added (the server emits none),
- every property-position `$ref` and the entire `$defs` block are **removed, not inlined** — affected properties keep their `description` but lose their type entirely (some properties arrive as completely empty `{}`; array properties arrive as `"items": {}`),
- integer-as-string fields are retyped: `{"type": "string", "format": "int64"}` becomes `{"type": "number"}`.
With no type information, the model sends object-valued arguments as JSON-encoded **strings**, and the server's schema validation correctly rejects them, making every tool with a named-type (ref) parameter uncallable with structured arguments.
We A/B tested the **same server** across attachment paths on the same day:
| Path | Result |
|---|---|
| Cowork, local connector (stdio proxy → remote server) | **rewritten**: draft-07 stamp, `$ref`/`$defs` stripped |
| Cowork, same server added as a **web custom connector** | intact: `$defs` verbatim, no `$schema` key |
| Claude Chat (web), web custom connector | intact |
| Claude Code, the identical stdio proxy command (`mcp-remote`) | intact |
Because Claude Code consumes the same server through the *identical* local proxy invocation and receives clean schemas, the proxy and transport are exonerated — the rewrite happens in Cowork's ingestion of local/desktop connectors specifically.
### What did you expect to happen?
Tool input schemas passed through verbatim (or, if a normalization pass is intentional, `$ref`s dereferenced correctly rather than the targets being discarded), matching the behavior of web connectors, Claude Chat, and Claude Code against the identical server.
### Steps to reproduce
1. Run an MCP server whose tool input schemas use local `$ref`s into `$defs` (representative shape below; recursive types are why `$defs` exists rather than inlining).
2. Attach it to Claude Desktop as a local connector (stdio `mcpServers` entry, e.g. via `mcp-remote` pointing at the server's streamable-HTTP endpoint, or packaged as `.mcpb`).
3. In a Cowork session, load the tool and ask: *"Show me the verbatim input schema of your loaded tool ``, including the `$schema`."*
4. Observe the draft-07 header and the missing `$defs` / hollowed properties.
5. Add the same server as a **web custom connector** (Settings → Connectors → Add custom connector), repeat step 3 in Cowork: schema arrives intact with no `$schema` key.
6. For contrast, connect Claude Code via the identical `mcp-remote` command: schema also intact.
7. Ask the model to call a tool passing a nested object for a ref-typed parameter: the argument is sent as a JSON-encoded string and the server rejects it.
What the server publishes (verified byte-level via raw `tools/list`; names genericized):
```json
{
"type": "object",
"properties": {
"filter": {
"$ref": "#/$defs/Filter",
"description": "Restrict the rows returned"
},
"limit": {
"type": "string",
"format": "int64",
"description": "Maximum number of rows"
},
"select": {
"type": "array",
"items": { "type": "string" }
}
},
"$defs": {
"Filter": {
"type": "object",
"properties": {
"field": { "type": "string" },
"operator": { "type": "string", "enum": ["EQ", "NE", "GT", "LT"] },
"value": { "type": "string" },
"filters": {
"type": "array",
"items": { "$ref": "#/$defs/Filter" },
"description": "Nested sub-filters (AND/OR trees)"
}
}
}
}
}
```
What the model sees in Cowork via the local connector (dumped verbatim in-session):
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"filter": {
"description": "Restrict the rows returned"
},
"limit": {
"type": "number",
"description": "Maximum number of rows"
},
"select": {
"type": "array",
"items": { "type": "string" }
}
}
}
```
Descriptions and enums survive verbatim (so this is a transform of the real published schema, not a different source). On other tools we observed a whole ref-typed property reduced to `"input": {}` and array items reduced to `"items": {}`.
### Area
Tool Discovery / Invocation
### MCP Server (if applicable)
Remote MCP server, streamable HTTP transport, built on the official Go SDK (`modelcontextprotocol/go-sdk` v1.6.1); negotiates protocol version `2025-11-25`
### Error messages or logs
```shell
Server-side validation rejecting the stringified argument that results from the typeless schema:
validate arguments: validating /properties/filter: ... has type "string", want "object"
No error is surfaced client-side; the schema mutation is silent.
```
### Additional context
**Full details of connection**:
- Remote MCP server, streamable HTTP transport, built on the official Go SDK (`modelcontextprotocol/go-sdk` v1.6.1); negotiates protocol version `2025-11-25`.
- Schemas are JSON Schema 2020-12 with local `$ref`s into `$defs`; **no `$schema` header emitted** (per SEP-1613 the default dialect is 2020-12).
- ~30 tools with a `$ref` at property position — all of them lose their argument types via the local-connector path.
- Reached through a stdio proxy (`mcp-remote`, and separately a custom stdio proxy in an `.mcpb` bundle) — both exhibit the rewrite in Cowork; both are clean in Claude Code.
**Server-side variables we eliminated** (each tested in isolation, reconnecting fresh):
1. Declaring `"$schema": "https://json-schema.org/draft/2020-12/schema"` explicitly on every schema — no change.
2. Removing draft-07/OpenAPI-isms (non-portable `format` values `int64`/`int32`/`float`/`duration`, sibling keywords next to `$ref`, empty `required` arrays) — no change.
3. Catalog size (~50 tools → 11 tools → 1 tool) — no change.
4. Ref presence anywhere in the catalog (reduced to two tools with zero `$ref`/`$defs`; one tool's whole schema is a single string property) — the draft-07 stamp is **still added** to that trivial schema, byte-identical otherwise. The rewrite is unconditional for the local-connector path, not triggered by schema content.
5. Suppressing the MCP Apps extension (`io.modelcontextprotocol/ui`) capability at `initialize` — no change.
6. Protocol version (server negotiates `2025-11-25`, echoes older offers correctly) — not a legacy-server fallback.
7. Auth mode / target URL of the proxy (localhost vs public deployment, static OAuth) — no change; the connector type is the discriminator.
**Likely mechanism**: the signature (draft-07 stamp, unresolvable refs dropped rather than inlined, `format: int64` retyped) is consistent with an AJV-style compile/re-serialize step — AJV's default dialect is draft-07, and modelcontextprotocol/mcpb#174 documents Claude Desktop's local path failing on `$defs`-bearing schemas with AJV "Error compiling schema" errors, which looks like the same component in a louder failure mode.
**Why server-side inlining is not a general workaround**: our schemas include recursive types (see `Filter` above — unbounded expansion), so full dereferencing is impossible by definition; only depth-capped inlining is feasible, which degrades exactly the nested-tree parameters these tools exist to accept. Valid 2020-12 schemas that every other Claude surface handles correctly shouldn't require it.
**Related issues**:
- anthropics/claude-code#18260 — MCP tool parameters with `$ref` schemas serialized as strings (open)
- anthropics/claude-code#25865 / #26027 — Cowork-specific regressions of the same symptom (regression window reported ~Feb 13→15, 2026)
- modelcontextprotocol/mcpb#174 — Claude Desktop failing on `$defs`-bearing schemas (AJV compile errors)
- anthropics/claude-ai-mcp#548 — object-typed params serialized as JSON strings ("expected object, received string")
- anthropics/claude-ai-mcp#578 — connector silently drops nested object properties from fully-inlined schemas
- modelcontextprotocol/typescript-sdk#1562 / PR #1563 — server-SDK-side `$ref` inlining mitigation (cannot fully cover recursive schemas)
**Asks**:
1. Preserve `$ref`/`$defs` through the local/desktop connector schema ingestion in Cowork (or dereference correctly instead of discarding targets), matching web connectors, Claude Chat, and Claude Code.
2. If a dialect normalization pass is intentional, don't drop unresolved refs to empty schemas — that turns a stylistic downgrade into a silent correctness bug.
3. Document that local and web connectors process tool schemas differently, so server authors can stop bisecting their own schema generation.
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Direzione di ricerca
Start with the Tool Discovery / Invocation path and reproduce the local-connector schema transformation using the supplied MCP server shape, then compare it with the web connector and Claude Code paths. Trace where local connector schemas receive the draft-07 header and lose `$ref`/`$defs`. Done means preserving valid schemas or resolving references without hollowing properties, with the cross-connector behavior verified.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Valutazione
- Stack tecnologico
- json
- Ambito
- api
- Tipo di issue
- Bug
- Difficoltà
- 5/5
- Tempo stimato
- Più di una settimana
- Stato di attività
- Tranquilla
- Chiarezza
- Abbastanza chiara
- Idoneità per principianti
- 35/100