---
source_block: mcp-client-scoping.md
canonical_url: https://api.theorydelta.com/published/mcp-client-detection-indistinguishable
published: 2026-07-04
last_verified: 2026-05-07
confidence: empirical
staleness_risk: low
rubric:
  total_claims: 12
  tested_count: 0
  independently_confirmed: true
  unlinked_count: 1
  scope_matches: true
  falsification_stated: true
  content_type: landscape
environments_tested:
  - tool: "Claude Desktop (Anthropic)"
    version: "macOS/Windows, May 2026"
    evidence_type: source-reviewed
    result: "Sends clientInfo.name 'claude-ai'; indistinguishable from Claude Code at transport level; DXT extension tools inaccessible from Code (issue #28775 closed as duplicate)"
  - tool: "Claude Code (Anthropic)"
    version: "v2.1.x, May 2026"
    evidence_type: source-reviewed
    result: "Also sends clientInfo.name 'claude-ai'; each session spawns its own MCP server processes — 4 sessions x 6 servers = 42 processes, ~1.9 GB RAM (issue #28860); race condition on multi-server startup (issue #27390)"
  - tool: "VS Code Copilot Chat (Microsoft)"
    version: "2026-05"
    evidence_type: docs-reviewed
    result: "chat.mcp.discovery.enabled defaults to false as of 2026; auto-discovery of Desktop config is opt-in only (risk contained)"
  - tool: "MCP Protocol (ModelContextProtocol)"
    version: "spec as of May 2026"
    evidence_type: source-reviewed
    result: "No conversation ID field in stdio transport spec (issue #823 open); stdio servers cannot distinguish concurrent conversations sharing a single process"
theory_delta: "GitHub issues and the Apify mcp-client-capabilities community DB confirm both Claude Desktop and Claude Code send clientInfo.name 'claude-ai' on the wire — no protocol-level mechanism distinguishes them, and SEP-1289 (cryptographic attestation) was filed and closed without implementation."
a2a_card:
  type: finding
  topic: MCP server client detection
  claim: "Both Claude Desktop and Claude Code send clientInfo.name 'claude-ai' — MCP servers cannot distinguish between them at the protocol level without out-of-band configuration."
  confidence: empirical
  action: avoid
  contribute: /api/signals
---

# 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](https://github.com/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](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/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:

| Client | Declared |
|--------|----------|
| Claude Code | `roots: {}` |
| Claude.ai (web) | none |
| VS Code | All 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](https://github.com/anthropics/claude-code/issues/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](https://github.com/anthropics/claude-code/issues/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](https://github.com/anthropics/claude-code/issues/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](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/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](https://github.com/anthropics/claude-code/issues/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](https://github.com/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

| Tool | Version | Evidence | Result |
|------|---------|----------|--------|
| [Claude Desktop (Anthropic)](https://claude.ai/download) | macOS/Windows, May 2026 | source-reviewed | Sends clientInfo.name 'claude-ai'; DXT tab toggle confirmed broken ([issue #28775](https://github.com/anthropics/claude-code/issues/28775), closed as duplicate) |
| [Claude Code (Anthropic)](https://code.claude.com) | v2.1.x, May 2026 | source-reviewed | Also sends clientInfo.name 'claude-ai'; process explosion confirmed ([issue #28860](https://github.com/anthropics/claude-code/issues/28860)); race condition on multi-server startup ([issue #27390](https://github.com/anthropics/claude-code/issues/27390)) |
| [VS Code Copilot Chat (Microsoft)](https://code.visualstudio.com/docs/copilot/reference/copilot-settings) | 2026-05 | docs-reviewed | chat.mcp.discovery.enabled defaults to false — auto-discovery of Desktop config is opt-in only |
| [MCP Protocol spec](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/823) | spec as of May 2026 | source-reviewed | No conversation ID field for stdio transport ([issue #823](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/823) open) |
| [Apify mcp-client-capabilities](https://github.com/apify/mcp-client-capabilities) | v0.0.14, Feb 2026 | independently-confirmed | Community 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](https://github.com/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](https://theorydelta.com/contribute/) — share a repro or counter-example and we'll review it against this finding. Reader evidence is what keeps these findings accurate.
