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

Abierto
#591 0 comentarios 0 reacciones 0 asignados Ver en GitHub
bug
Lenguaje dominante
Sin datos de lenguaje
Estrellas
471
Forks
76
Métricas de merge de PR
Sin PR fusionados en 30 d

Descripción

### 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.

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.