{"slug": "anthropic-has-two-different-cimd-clients", "title": "Anthropic has two different CIMD clients", "summary": "An engineer tested Anthropic's Claude products—Claude Code, Claude Desktop, Claude.ai Web, and Claude Cowork—to verify their support for Client ID Metadata Document (CIMD) in OAuth 2.1 flows. All four products use CIMD on user-initiated paths, meaning an MCP authorization server can ship CIMD-only and cover the current Anthropic client population. The engineer argues CIMD is preferable to Dynamic Client Registration (DCR) because it avoids open registration endpoints and long-lived state.", "body_md": "# Testing CIMD support across Anthropic’s Claude products\n\nTL;DR:Claude Code, Claude Desktop and Claude Cowork all use CIMD on the user-initiated paths I observed. An MCP authorization server can ship CIMD-only and cover the current Anthropic client population.\n\nAnthropic only documents Client ID Metadata Document (CIMD) support for one of their products: Claude Code. For Desktop, Web and Cowork the documentation says the surfaces share infrastructure but never names the client_id or the registration mechanism. If you are designing an OAuth 2.1 authorization server for MCP and want to know whether you can ship CIMD-only or have to support Dynamic Client Registration (DCR) as well, that gap matters.\n\nA [Reddit thread](https://www.reddit.com/r/ClaudeAI/comments/1tb7fqo/any_info_about_cimd_for_claude_desktopweb_client/) from May 2026 asks exactly the same question. The answers are not authoritative. A few third-party blogs claim hosted surfaces use a pre-registered UUID; those claims do not cite primary sources and turned out to be incorrect once tested. So I tested each product directly against an authorization server (AS) I control, logged every request and read the answer off the wire.\n\n## Why CIMD beats DCR\n\nDCR is a permanent operational liability. It exposes an open registration endpoint, so anti-abuse becomes the AS operator’s problem. RFC 7591 leaves the gating policy entirely to the operator. Without `software_statement`\n\nvalidation or some other admission control, spammers can carve registration rows into the database until storage explodes or queries become slow scans.\n\nEvery registered client gets to claim its own `client_name`\n\n, `client_uri`\n\nand `redirect_uris`\n\n. Consent screens can lie unless registration is gated on a trust signal stronger than the request itself, such as pre-shared signing keys, manual review or an allowlist. Each registration also creates a long-lived row with its own garbage collection story and a question of what happens to issued tokens when the client is deleted. RFC 7592 adds an entire CRUD API on top.\n\nCIMD avoids all of that. The client_id is a URL. The origin of the URL is the trust anchor; if the client_id is on `claude.ai`\n\n, the AS already knows whether it trusts `claude.ai`\n\n. The metadata document is fetched fresh each time, so there is no stored state. There is no open endpoint to abuse and no admission policy to write.\n\nAs such, given the choice between the two, the right call is to ship CIMD first. DCR can wait until a non-Anthropic MCP client that needs it shows up.\n\n## Why Cowork needs an extra step\n\nWeb, Desktop and Mobile all run the OAuth client in the user’s own browser or app process. Whatever `client_id`\n\nreaches my AS is, by construction, the one those products use. There is no service in between that could mint a different identity.\n\nCowork is different. Cowork runs on Anthropic’s servers. When a Cowork agent invokes an MCP tool, the request originates from Anthropic’s egress, not from the user’s machine. Even if the initial OAuth flow looks identical to Web (because the user kicks it off from a browser), nothing in the protocol rules out Cowork’s server side using a different OAuth client when it refreshes a token, picks up a scheduled task or runs autonomously. The shared-infrastructure claim in Anthropic’s docs is suggestive but not conclusive on its own.\n\nAs such, the Cowork test has to go past the initial authorize. I need to see Cowork actually invoke a tool and confirm the bearer it sends matches the one issued during the user-initiated CIMD flow. That covers the user-initiated happy path. Token refresh, scheduled tasks and autonomous runs are each separate observable events that would each require their own capture; this article only closes the user-initiated case directly.\n\n## Setup\n\n```\nTested clients- Claude Code CLI:     2.1.142- Claude Desktop:      v1.7196.0 (macOS)- Claude.ai Web:       personal account- Claude Cowork:       running inside Claude.app v1.7196.0, personal account\nTest infrastructure- Language:            Python 3.14, FastAPI + uvicorn (~150 LOC)- Public URL:          cloudflared quick tunnel (https://<random>.trycloudflare.com)- Logging:             every inbound HTTP request to JSONL, full headers + body\n```\n\nThe AS advertised both `registration_endpoint`\n\nand `client_id_metadata_document_supported: true`\n\nso each client had every path available and picked the one it actually wanted to use. Per Anthropic's selection rule, Claude picks CIMD only when both `client_id_metadata_document_supported: true`\n\n*and* `none`\n\nin `token_endpoint_auth_methods_supported`\n\nare advertised. Both conditions were satisfied.\n\n```\n{  \"issuer\": \"https://<tunnel>.trycloudflare.com\",  \"authorization_endpoint\": \"https://<tunnel>.trycloudflare.com/oauth/authorize\",  \"token_endpoint\": \"https://<tunnel>.trycloudflare.com/oauth/token\",  \"registration_endpoint\": \"https://<tunnel>.trycloudflare.com/oauth/register\",  \"client_id_metadata_document_supported\": true,  \"grant_types_supported\": [\"authorization_code\", \"refresh_token\"],  \"response_types_supported\": [\"code\"],  \"token_endpoint_auth_methods_supported\": [\"none\", \"private_key_jwt\"],  \"code_challenge_methods_supported\": [\"S256\"],  \"scopes_supported\": []}\n```\n\nThe MCP server returned `401 Unauthorized`\n\nwith the standard `WWW-Authenticate: Bearer resource_metadata=\"...\"`\n\nchallenge. The classification rule is whichever OAuth-related request hits the AS first after that 401:\n\n``` php\nGET /oauth/authorize?...&client_id=https://...   -> CIMD       (client_id is a URL)POST /oauth/register with a JSON body            -> DCR        (client self-registered first)GET /oauth/authorize?...&client_id=<UUID>        -> Pre-registered  (out-of-band)\n```\n\nFor CIMD captures I also fetched the metadata document independently to record its full contents.\n\n## Findings\n\n### Claude Code CLI 2.1.142\n\nAfter `claude mcp add --transport http cimd-test https://<tunnel>/mcp`\n\nand a new session:\n\n```\nPOST /mcp                                            ua=claude-code/2.1.142 (cli)GET  /.well-known/oauth-protected-resource           ua=claude-code/2.1.142 (cli)GET  /.well-known/oauth-authorization-server         ua=claude-code/2.1.142 (cli)GET  /.well-known/oauth-protected-resource           ua=Bun/1.3.14GET  /.well-known/oauth-authorization-server         ua=Bun/1.3.14GET  /oauth/authorize?response_type=code       &client_id=https%3A%2F%2Fclaude.ai%2Foauth%2Fclaude-code-client-metadata       &redirect_uri=http%3A%2F%2Flocalhost%3A61264%2Fcallback       &code_challenge_method=S256       &resource=https%3A%2F%2F<tunnel>%2Fmcp       &state=...\n```\n\nCIMD. The metadata document, fetched independently:\n\n```\n{  \"client_id\": \"https://claude.ai/oauth/claude-code-client-metadata\",  \"client_name\": \"Claude Code\",  \"client_uri\": \"https://claude.ai\",  \"redirect_uris\": [    \"http://localhost/callback\",    \"http://127.0.0.1/callback\"  ],  \"grant_types\": [\"authorization_code\", \"refresh_token\"],  \"response_types\": [\"code\"],  \"token_endpoint_auth_method\": \"none\"}\n```\n\nThe metadata declares loopback redirect URIs *without* ports, but the runtime authorize uses `http://localhost:61264/callback`\n\n. The port is OS-assigned and changes on every invocation. Strict byte-for-byte `redirect_uri`\n\nmatching against the registered list would reject every Claude Code flow on the first request. [RFC 8252 §7.3](https://www.rfc-editor.org/rfc/rfc8252#section-7.3) covers exactly this case: for loopback redirect URIs, the AS must accept any port at request time. Without that carve-out, Claude Code does not work; there is no client-side workaround because the OS picks the port.\n\nThe CLI also duplicates discovery between its main process (`claude-code/2.1.142 (cli)`\n\n) and an internal Bun worker (`Bun/1.3.14`\n\n). Each `/.well-known`\n\nendpoint gets hit twice. The duplication does not affect correctness but doubles the discovery traffic per session.\n\n### Claude Web (claude.ai)\n\nThe custom-connector modal at `https://claude.ai/customize/connectors?modal=add-custom-connector`\n\naccepts a Name and a Remote MCP server URL, with optional OAuth Client ID and Client Secret fields. With both OAuth fields left blank:\n\n```\nPOST /mcp                                          ua=Claude-UserPOST /mcp                                          ua=python-httpx/0.28.1GET  /.well-known/oauth-protected-resource         ua=python-httpx/0.28.1GET  /.well-known/oauth-authorization-server       ua=python-httpx/0.28.1GET  /oauth/authorize?response_type=code       &client_id=https%3A%2F%2Fclaude.ai%2Foauth%2Fmcp-oauth-client-metadata       &redirect_uri=https%3A%2F%2Fclaude.ai%2Fapi%2Fmcp%2Fauth_callback       &code_challenge_method=S256       &resource=https%3A%2F%2F<tunnel>%2Fmcp       &state=...\n```\n\nCIMD again, with a different URL than Code:\n\n```\n{  \"client_id\": \"https://claude.ai/oauth/mcp-oauth-client-metadata\",  \"client_name\": \"Claude\",  \"client_uri\": \"https://claude.ai\",  \"redirect_uris\": [    \"https://claude.ai/api/mcp/auth_callback\"  ],  \"grant_types\": [\"authorization_code\", \"refresh_token\"],  \"response_types\": [\"code\"],  \"token_endpoint_auth_method\": \"none\"}\n```\n\nThe `python-httpx/0.28.1`\n\nrequests are a server-side probe Anthropic's backend performs *before* the user-facing browser redirect. The connector form validates the URL before sending the user anywhere. A localhost MCP server cannot be tested through Web; the tunnel needs to be publicly reachable from Anthropic's egress.\n\n### Claude Desktop v1.7196.0\n\nA connector added on Web shows up in Desktop’s Connectors view automatically because both share the same account. Clicking “Connect” in Desktop:\n\n```\nPOST /mcp                                       ua=python-httpx/0.28.1GET  /.well-known/oauth-protected-resource      ua=python-httpx/0.28.1GET  /.well-known/oauth-authorization-server    ua=python-httpx/0.28.1GET  /oauth/authorize?response_type=code       &client_id=https%3A%2F%2Fclaude.ai%2Foauth%2Fmcp-oauth-client-metadata       &redirect_uri=https%3A%2F%2Fclaude.ai%2Fapi%2Fmcp%2Fauth_callback       &code_challenge_method=S256       &resource=https%3A%2F%2F<tunnel>%2Fmcp       &state=...\n```\n\nFunctionally identical to Web from the AS’s perspective: same CIMD URL, same redirect URI, same auth method. The user agent and originating process differ (the captures show the Desktop app rather than the browser kicking off the flow), but the AS sees the same OAuth client.\n\n### Claude Cowork inside Claude Desktop\n\nFor Cowork, the AS had to complete the flow rather than stub it out. I extended it to issue a code, issue a bearer and serve `initialize`\n\n/ `tools/list`\n\n/ `tools/call`\n\nwith a single `ping`\n\ntool. Then I connected the test server, opened a Cowork space and prompted *\"Can you try the ping tool in cimd-test mcp?\"*. Claude replied *\"Got pong\"*. The relevant captures:\n\n```\nGET  /oauth/authorize     client_id=https://claude.ai/oauth/mcp-oauth-client-metadataPOST /oauth/token         ua=python-httpx/0.28.1POST /mcp  rpc=initialize                       auth=Bearer tt_5R7FPF5KnzEOaS4...POST /mcp  rpc=notifications/initialized        auth=Bearer tt_5R7FPF5KnzEOaS4...POST /mcp  rpc=tools/list                       auth=Bearer tt_5R7FPF5KnzEOaS4...POST /mcp  rpc=initialize                       auth=Bearer tt_5R7FPF5KnzEOaS4...POST /mcp  rpc=notifications/initialized        auth=Bearer tt_5R7FPF5KnzEOaS4...POST /mcp  rpc=tools/list                       auth=Bearer tt_5R7FPF5KnzEOaS4...POST /mcp  rpc=tools/call                       auth=Bearer tt_5R7FPF5KnzEOaS4...\n```\n\nThe Cowork `tools/call`\n\ncarries the same bearer token issued during the original CIMD-based exchange. No second authorize, no second token request, no separate OAuth client. Cowork's server-side runtime reuses the user's CIMD-issued token verbatim when it invokes a tool, which is the most common path. The three other paths I flagged earlier (token refresh, scheduled tasks, autonomous runs) are not covered by this single observation: the test token had a 1-hour TTL and the session was short, so no refresh occurred, and I did not exercise scheduled or autonomous Cowork runs. Those each remain untested.\n\n## What the AS needs to do in practice\n\nThe two CIMD URLs to allowlist, both on `claude.ai`\n\n:\n\n```\nhttps://claude.ai/oauth/claude-code-client-metadata        (Claude Code)https://claude.ai/oauth/mcp-oauth-client-metadata          (Web / Desktop / Mobile / Cowork)\n```\n\nAnd a short list of things the AS must do to make CIMD-only work end-to-end:\n\n- Accept loopback redirect URIs with any port (RFC 8252 §7.3). Required for Claude Code.\n- Advertise\n`token_endpoint_auth_methods_supported`\n\nincluding`\"none\"`\n\n. Required for any product to pick CIMD. - Require PKCE S256. Every product sends it.\n- Honor the\n`resource`\n\nparameter (RFC 8707) and audience-bind issued tokens. Every product sends it. - Accept connectors with no declared scope. Empty\n`scopes_supported: []`\n\nis enough. - Be reachable from Anthropic’s egress, not just from the user’s machine. Required for Web, Desktop and Cowork to even reach the OAuth step.\n\n## Caveats\n\nTested on 2026–05–15. Claude Code CLI 2.1.142, Claude.app v1.7196.0. Builds older than the Web/Desktop unification (sometime in 2025) may behave differently. Future Anthropic builds could ship per-surface CIMD URLs, signed software statements or DCR-based registration at any point.\n\nThe Cowork observation is a single user-initiated tool call. That is enough to confirm token reuse on the happy path but not enough to rule out a different OAuth client being used for token refresh, scheduled tasks or autonomous runs. A follow-up test could exercise refresh by issuing a short-TTL access token and waiting for Cowork to refresh, and exercise the scheduled path by leaving an autonomous task on the connector. I have not done either yet.\n\nCowork was tested under a personal Anthropic account because the team workspace I had primary access to disables member-added custom connectors. The captures matched the Web/Desktop captures on `client_id`\n\n, `redirect_uri`\n\nand `token_endpoint_auth_method`\n\n, but a re-run on an admin-allowed team workspace is worth doing if the difference between team and personal is on your threat model.\n\nDCR is not the moral equivalent of CIMD with extra steps. There are legitimate reasons MCP clients without a hosted origin (desktop IDEs, etc.) reach for it. Cursor and Windsurf both use DCR at the time of writing. If the AS needs to support non-Anthropic clients, DCR comes back into scope. The conclusion here is narrowly about Anthropic’s own products.\n\n## Sources\n\n[Authentication for connectors, Anthropic docs](https://claude.com/docs/connectors/building/authentication)[r/ClaudeAI — Any info about CIMD for Claude Desktop/Web client?](https://www.reddit.com/r/ClaudeAI/comments/1tb7fqo/any_info_about_cimd_for_claude_desktopweb_client/)[RFC 7591, OAuth 2.0 Dynamic Client Registration Protocol](https://www.rfc-editor.org/rfc/rfc7591)[RFC 7592, OAuth 2.0 Dynamic Client Registration Management Protocol](https://www.rfc-editor.org/rfc/rfc7592)[RFC 8252 §7.3, OAuth 2.0 for Native Apps, Loopback Interface Redirection](https://www.rfc-editor.org/rfc/rfc8252#section-7.3)[RFC 8707, Resource Indicators for OAuth 2.0](https://www.rfc-editor.org/rfc/rfc8707)[MCP Authorization Spec](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)[Aaron Parecki, Client ID Metadata Document draft](https://www.ietf.org/archive/id/draft-parecki-oauth-client-id-metadata-document-02.html)", "url": "https://wpnews.pro/news/anthropic-has-two-different-cimd-clients", "canonical_source": "https://leduccc.medium.com/testing-cimd-support-across-anthropics-claude-products-585366dbe089", "published_at": "2026-07-15 20:15:26+00:00", "updated_at": "2026-07-15 20:28:16.661081+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "ai-products", "artificial-intelligence"], "entities": ["Anthropic", "Claude Code", "Claude Desktop", "Claude.ai", "Claude Cowork", "MCP"], "alternates": {"html": "https://wpnews.pro/news/anthropic-has-two-different-cimd-clients", "markdown": "https://wpnews.pro/news/anthropic-has-two-different-cimd-clients.md", "text": "https://wpnews.pro/news/anthropic-has-two-different-cimd-clients.txt", "jsonld": "https://wpnews.pro/news/anthropic-has-two-different-cimd-clients.jsonld"}}