theorydeltaclaim-fidelity audits
built 2026-07-17dossiers: 5last verified 2026-07-09independent · evidence-traced · no vendor influence

Claude Desktop and Claude Code send the same clientInfo.name — MCP servers cannot tell them apart

Published: 2026-07-04Last verified: 2026-05-07empirical

Claude Desktop and Claude Code send the same clientInfo.name — MCP servers cannot tell them apart

What you expect

MCP servers receive a client identity during the initialize handshake via clientInfo.name. If you are building a server that should behave differently per Anthropic client — different tool sets exposed to Claude Desktop vs Claude Code, different rate limits, or different capability assumptions — you expect clientInfo.name to be your detection mechanism. The MCP spec defines this field; using it for routing is the natural approach.

What actually happens

Both Claude Desktop and Claude Code send { "name": "claude-ai", "version": "0.1.0" } on the wire. They are indistinguishable at the protocol level.

The Apify mcp-client-capabilities community database (active as of v0.0.14, Feb 2026, tracking 40+ clients) uses separate keys (claude-ai for Desktop, claude-code for Code), but this reflects community convention for the database’s own keying scheme — not confirmed differences in what each client sends over the wire. The wire values appear to both be "claude-ai".

SEP-1289 proposed cryptographic client identity via platform attestation (iOS App Attest, TPM, DPoP tokens), filed September 2025, sponsored by Microsoft. It was closed as unimplemented due to conflicts with the existing DCR spec (#1032). No production implementation exists.

Capability negotiation is also incomplete

During initialize, clients declare supported capability categories. Observed declarations:

ClientDeclared
Claude Coderoots: {}
Claude.ai (web)none
VS CodeAll 8 categories including sampling, elicitation, tools.listChanged, resources, prompts, logging

Claude Code now supports tools/list_changed notifications as of v2.1.x (explicitly documented). However, neither Claude Desktop nor Claude Code declares support for instructions or resource subscriptions. When declarations are missing, client and server silently proceed with divergent assumptions — no error is raised in either direction. A server sending notifications the client ignores gets no feedback; a server that avoids sending them gets no explanation.

When a client has no listChanged handler, it defaults to periodic full re-fetch of the tool list rather than event-driven refresh — high-latency round-trips on every polling cycle instead of a single notification-triggered update.

Known failure modes in multi-client deployments

DXT extensions silently fail in Claude Code. Desktop Extensions (.dxt) install to ~/Library/Application Support/Claude/Claude Extensions/. The Claude Code connectors tab shows them as toggleable. Enabling the toggle does nothing — tools do not appear. The extension’s credentials are stored encrypted at a path Claude Code cannot access. Issue #28775 was closed as a duplicate without the underlying fix shipping. Workaround: manually add the server config to ~/.claude/mcp_settings.json with plaintext credentials — which defeats the extension’s security model.

Process explosion with no shared daemon. Each Claude Code session spawns its own copy of every configured MCP server. 4 concurrent sessions × 6 servers = 42 processes, approximately 1.9 GB RAM. macOS OOM kills are a confirmed production failure mode. Issue #28860 requesting a shared MCP daemon was closed as a duplicate of a separate tracking issue — no shared daemon has shipped.

claude-in-chrome broken when both apps are installed. The Chrome extension’s native messaging host (com.anthropic.claude_browser_extension) is registered only by Claude Desktop. When both apps are installed, mcp__claude-in-chrome__* tools fail in Claude Code with “Browser extension is not connected.” Issue #28539 was closed as a duplicate.

Stateful stdio servers create multi-conversation state hazards. The MCP spec has no conversation identifier for stdio transport. Concurrent conversations sharing a single stdio server process cannot distinguish requests from different conversations. MCP spec issue #823 is open as of May 2026. VS Code chose per-process isolation as a workaround; Claude Desktop has not implemented either solution.

Claude Code race condition on multi-server startup. When 3+ MCP servers are configured, a shared Protocol instance causes all but the first server to fail with "Already connected to a transport. Call close() before connecting to a new transport, or use a separate Protocol instance per connection." Issue #27390 in the claude-code repo is confirmed and closed.

What this means for you

If you are building an MCP server that should behave differently depending on whether the caller is Claude Desktop or Claude Code, you cannot rely on clientInfo.name to make that distinction. Both clients look identical on the wire. Any server-side routing logic keyed on clientInfo.name that distinguishes "claude-ai" from "claude-code" is using a convention that does not reflect confirmed wire behavior — it may work coincidentally in some environments and silently break in others.

The process explosion and race condition issues compound each other for developers running multi-session Claude Code workflows: more sessions mean more servers means more memory pressure, and startup ordering can silently drop servers even before session overhead accumulates.

DXT extensions targeting Claude Desktop’s encrypted credential store are not portable to Claude Code without degrading their security model. Extension authors who want Claude Code compatibility must ship a separate integration path.

What to do

  1. Use config-file scoping as your primary mechanism. Claude Desktop reads ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows). Claude Code reads ~/.claude.json (user scope) and .mcp.json at project root (project scope). These files are separate and not synced — adding a server to only one of them is filesystem-enforced scoping.

  2. Do not key routing logic on clientInfo.name. If you need client-specific behavior, use out-of-band configuration (environment variables, separate config files, server startup flags). Do not assume the Apify community DB keys (claude-ai vs claude-code) reflect wire values — treat the wire value as "claude-ai" for both until Anthropic confirms otherwise.

  3. For capability negotiation gaps, consult the Apify mcp-client-capabilities database. It tracks observed capability behavior per client outside the handshake, providing practical information that supplements the incomplete initialize declarations.

  4. For stdio servers shared across conversations, implement per-request state isolation. Since the spec provides no conversation ID, any mutable server state (working directory, active session, user context) is shared across all concurrent callers. Either restrict to stateless handlers or run one server process per client session.

  5. For Claude Code deployments with many MCP servers, test multi-server startup explicitly. The race condition on shared Protocol instances means your server may silently fail to connect on startup when other servers initialize concurrently. Monitor startup logs for “Already connected to a transport” errors.

Falsification criterion: This finding would be disproved by evidence that Claude Desktop and Claude Code send distinct clientInfo.name values on the wire — for example, a CHANGELOG entry or MCP protocol trace showing "claude-code" from Claude Code and "claude-ai" from Desktop in the same environment.

Evidence

ToolVersionEvidenceResult
Claude Desktop (Anthropic)macOS/Windows, May 2026source-reviewedSends clientInfo.name ‘claude-ai’; DXT tab toggle confirmed broken (issue #28775, closed as duplicate)
Claude Code (Anthropic)v2.1.x, May 2026source-reviewedAlso sends clientInfo.name ‘claude-ai’; process explosion confirmed (issue #28860); race condition on multi-server startup (issue #27390)
VS Code Copilot Chat (Microsoft)2026-05docs-reviewedchat.mcp.discovery.enabled defaults to false — auto-discovery of Desktop config is opt-in only
MCP Protocol specspec as of May 2026source-reviewedNo conversation ID field for stdio transport (issue #823 open)
Apify mcp-client-capabilitiesv0.0.14, Feb 2026independently-confirmedCommunity DB tracks 40+ clients; uses separate keys for Desktop/Code but does not confirm distinct wire values

Confidence: empirical — 4 environments reviewed. Independent confirmation via Apify mcp-client-capabilities community database and multiple filed GitHub issues from separate reporters confirming DXT failure, process explosion, and stdio race conditions.

Strongest case against: Anthropic may have updated clientInfo.name values since February 2026 without a CHANGELOG entry — the implementation_evidence in the source block marks the wire value as “unverifiable” rather than “source-reviewed.” If clientInfo.name now differs between Desktop ("claude-ai") and Code ("claude-code"), the primary detection claim is resolved and the content_type drops from landscape to historical record. The secondary failure modes (DXT toggle broken, process explosion, race condition) are separately confirmed via GitHub issues and remain valid regardless.

Open questions: Has Anthropic shipped distinct clientInfo.name values since May 2026? Does Claude Code now declare instructions and resource subscriptions in its initialize capabilities? Has the shared MCP daemon tracking issue resolved?

Seen different? Contribute your evidence — share a repro or counter-example and we’ll review it against this finding. Reader evidence is what keeps these findings accurate.

theorydelta.com · 2026independent · evidence-backed · every claim sourced or labelledabout ·glossary ·rss ·mcp ·llms.txt