anthropics / anthropics/claude-ai-mcp
Cowork desktop: OAuth loopback callback listener refuses connection on ephemeral port after consent — token never minted
- Vorherrschende Sprache
- Keine Sprachdaten
- Sterne
- 471
- Forks
- 76
- PR-Merge-Kennzahlen
- Keine gemergten PRs in 30 T.
Beschreibung
### What happened?
**Cowork desktop (macOS)** cannot complete OAuth against a custom MCP server that uses an AuthKit-style authorization server with DCR/CIMD. The flow gets through DCR, authorize, and the consent screen, but **breaks on the final 302 back to the loopback callback URL**:
After clicking "Allow access," the AS redirects the browser to `http://localhost:55713/callback?code=&state=`, and the browser shows **ERR_CONNECTION_REFUSED**. Nothing is listening on the ephemeral port Cowork advertised in the authorize request. The authorization code is never exchanged. No bearer token ever reaches the MCP server (verified via server logs — zero authenticated POST requests across all attempts).
The CIMD at `https://claude.ai/oauth/claude-code-client-metadata` declares `redirect_uris: ["http://localhost/callback", "http://127.0.0.1/callback"]` (RFC 8252 §7.3 loopback URIs), so Cowork is expected to bind an ephemeral port locally before opening the browser, the same way Claude Code CLI does. That bind appears to either not happen or be torn down before the AS redirects back.
### What did you expect to happen?
After clicking "Allow access," the browser's 302 to `http://localhost:/callback?code=...` should reach a listener that Cowork-desktop bound prior to opening the authorize URL. Cowork should exchange the code at the AS token endpoint, then send the resulting bearer token to the MCP server.
### Steps to reproduce
1. Configure a custom MCP server in Cowork-desktop with `{"type":"http","url":""}`. The server uses `@modelcontextprotocol/sdk` `WebStandardStreamableHTTPServerTransport`, returns `WWW-Authenticate: Bearer resource_metadata="..."` on unauthenticated requests, and serves RFC 9728 protected-resource metadata pointing at a DCR/CIMD-enabled AuthKit AS.
2. Invoke any tool from the connector. Cowork opens the authorize URL at the AuthKit domain. AuthKit consent screen appears with "Application origin: https://claude.ai".
3. Click "Allow access."
4. Browser navigates to `http://localhost:55713/callback?code=&state=` and immediately fails with **"This site can't be reached — localhost refused to connect. ERR_CONNECTION_REFUSED."**
5. No subsequent request reaches the MCP server.
### Area
OAuth / Authentication
### MCP Server (if applicable)
A custom MCP server (Next.js 15 + `@modelcontextprotocol/sdk` `WebStandardStreamableHTTPServerTransport`, stateless, `enableJsonResponse: true`). The authorization server is WorkOS AuthKit (DCR/CIMD enabled per its `/.well-known/oauth-authorization-server` — `client_id_metadata_document_supported: true`, `registration_endpoint` advertised). Server URL omitted as it is not the failing component; happy to share privately with reference IDs if helpful.
### Error messages or logs
Browser at the failing redirect:
```
URL: http://localhost:55713/callback?code=&state=
"This site can't be reached"
"localhost refused to connect"
"ERR_CONNECTION_REFUSED"
```
MCP server access logs across multiple reproductions: one unauthenticated `POST /api/mcp → 401` (initial probe) plus a few `GET /.well-known/oauth-protected-resource → 200` (discovery retries). Zero authenticated requests. No bearer token presented.
### Additional context
**The exact same MCP server + same CIMD + same AS works in two other clients**:
- **Claude Code CLI** with `"type": "http"`: end-to-end success. Loopback listener binds, callback succeeds, token is exchanged, MCP calls authenticate.
- **`mcp-remote`** via `{"command":"npx","args":["mcp-remote", ""]}`: end-to-end success. `mcp-remote` keeps a persistent local listener and presents stdio upstream, so the OAuth callback always finds a server bound on the advertised port.
Only **Cowork desktop** fails — and it fails specifically at the loopback-callback step, before any token is minted. This points at the loopback listener lifecycle inside Cowork-desktop itself: either failing to bind, binding on a port other than the one advertised, or tearing down before the AS 302 arrives.
Consistent with the initial in-app diagnostic shown on the first failure:
> "The MCP server appears to have disconnected before the authentication callback could be completed. The auth code you pasted can't be exchanged because the completion tool is no longer available."
(That message blames the MCP server, but the MCP server is downstream of the failing redirect and never participates — the listener that "disappears" is Cowork-desktop's own loopback OAuth listener.)
Server-side defensive work already done and not the cause:
- CORS + OPTIONS on `/api/mcp` and `/.well-known/oauth-protected-resource`
- RFC 9728 protected-resource metadata served correctly
- `WWW-Authenticate: Bearer resource_metadata="..."` on 401
Happy to provide additional reference IDs / packet captures if useful.
Beitragsleitfaden
Für dieses Repository ist kein Beitragsleitfaden indexiert
Bewertung
Dieses Issue wurde noch nicht bewertet.