[BUG] Cowork: custom MCP connector reports "needs authorization / no tools" on every session spawn, despite valid working auth — server-side logs included

Open 💬 3 comments Opened Jul 10, 2026 by popmatik

Environment

  • Surface: Cowork (Claude desktop app), macOS 14 (Darwin 23.6.0)
  • Connector: custom remote MCP (Streamable HTTP), published to the team via a

plugin (.mcp.json → "add to team" → authorised in connector settings)

  • Server: OAuth 2.1 AS+RS per the MCP auth spec — RFC 9728 Protected Resource

Metadata, RFC 8414 AS metadata, RFC 7591 DCR, RFC 8707 resource indicators,
RS256 JWTs, offline_access advertised, refresh-token rotation. 401s carry
WWW-Authenticate with the resource_metadata pointer.

Symptom

On every Cowork session spawn (app launch or new conversation), the first use
of the connector reports *"The <connector> needs authorization first — it isn't
connected" / "no tools are exposed yet"*, even though the connector is
authorised and demonstrably working. Asking again in the same session typically
succeeds. Re-authenticating does not stop the next session from repeating it.

Server-side evidence (timestamps UTC, 2026-07-10)

We operate the MCP server and correlated its logs with the client behaviour.

Clean-slate reproduction — connector fully removed, plugin re-added, one
fresh authorisation:

15:28:46  GET  /.well-known/* (discovery)            200
15:28:47  POST /register                              201   ← single fresh DCR client
15:28:47  GET  /authorize → Google SSO → callback     302
15:28:51  POST /token (authorization_code)            200   ← auth complete
15:28:52–15:29:08  11 × POST /mcp                     200   ← tools listing + calls all work
15:29:44  POST /mcp                                   401   ← 36s later: request with NO valid credentials
15:29:44  GET  /.well-known/* (discovery)             200
15:29:44  POST /register                              201   ← client re-registers via DCR… and stops

The 15:29:44 sequence is the next session's first probe. It arrives without
the credentials issued 53 seconds earlier
, receives a spec-correct 401 +
WWW-Authenticate, performs a fresh DCR registration (abandoning the live
token set), and the session then tells the user the connector needs
authorising. The same pattern repeats on every session spawn — we observe one
401 → discovery → /register → stop cycle per launch, indefinitely.

Meanwhile the previous credential set remains fully valid server-side: silent
refresh works (POST /token refresh grant → 200 in ~0.28s), access tokens
verify, tool calls succeed.

What we ruled out (server side)

We spent the day eliminating our own failure modes first, with log evidence for
each:

  • Cold starts: the AS endpoints are on min-instances=1; refresh /token

calls complete warm in ~0.25–0.3s with 200.

  • Lost DCR registrations: client registrations are persisted in a database

and survive instance replacement (previously they were in-memory; fixed and
verified — the 401s above are NOT invalid_client).

  • Token validity: the 401'd probes carry no valid token (the token issued at

15:28:51 was 36 seconds old and worked concurrently for the other path).

  • Spec compliance: 401 semantics, PRM, AS metadata, PKCE, resource

indicators all verified against the spec during the investigation.

Impact

  • Users are trained to "ask twice" for every connector, every session — or they

conclude the integration is broken.

  • Every session spawn creates an abandoned DCR client registration

server-side (one per launch, per user, per connector). Registrations
accumulate without bound; servers that persist registrations (as the SDK's
OAuthRegisteredClientsStore contract encourages) accrue orphan records
indefinitely.

Control experiment: Claude Code CLI works correctly against the same server

Same machine, same user, same server, same day (16:02–16:06 UTC):

  • claude mcp add --transport http … + probe → 401 → **correctly classified

! Needs authentication**, needs-auth cache entry written.

  • Interactive /mcp auth → success → needs-auth cache entry cleared, tools

work in-session.

  • Fresh process (claude mcp list in a new invocation) → ✔ Connected

the stored token is presented immediately: server logs show only POST /mcp
2xx. No 401, no discovery, no re-registration.

So credential persistence and reuse across process spawns works on the CLI
surface. Claude Chat (claude.ai) also works with the same connector — same
account, same server: three of Anthropic's surfaces exercised, and only
Cowork's session bootstrap fails. The per-spawn credential-less probe + DCR re-registration is specific
to Cowork's connector path (backend-managed credentials / claudeai-proxy),
which matches #52565's "divergent code paths for custom vs managed connectors".

Likely related (all closed stale/not-planned — this report adds the missing server-side evidence)

  • #52565 — custom connector OAuth tokens fail to persist across restart; 401

misclassified as generic connect failure; Cowork listed as affected; stale
DCR entries accumulate. Our observations match all three mechanisms.

  • #48670 — machine-wide 15-minute "needs-auth cache" skips the connection

attempt even after successful re-auth. Ruled out for Cowork by test: after
45 minutes of zero Cowork activity (any cache long expired) and with a
still-valid unexpired access token held by the backend, a fresh session at
16:14:15 UTC still probed credential-less (401 → discovery → /register →
stop). The bootstrap never attaches credentials at all; it is not a stale
cache verdict.

  • #42922 — eager per-session connect + per-session credential isolation;

matches the one-probe-per-spawn pattern in our logs.

Suggested fixes

  1. Attach the connector's stored credentials before (or with) the session's

first probe — or connect lazily on first tool use — instead of probing
unauthenticated and caching the failure.

  1. Classify a custom-connector 401 as **needs-auth → try stored refresh

token / backend credentials** before surfacing an auth prompt, matching
managed-connector behaviour.

  1. Invalidate any needs-auth cache when an authorisation completes.
  2. On 401, reuse the existing DCR registration rather than re-registering — the

current behaviour orphans the registration and its refresh-token chain.

We're happy to provide correlated server logs for any reproduction window,
or to test candidate builds against our server.

Related reports (same defect family — Cowork/desktop session ↔ connector credential wiring)

The symptoms in this report are not specific to our server or to custom
connectors; the same family is reported across managed connectors and both
desktop platforms:

  • #61682 — GitHub (managed) connector shows "Connected" but exposes no tools in Cowork
  • #18194 — managed connectors (Asana, Google Calendar) 403 in Cowork despite working in Claude Chat
  • #60222 — reconnect restores the tools list but tools/call silently fails (split state)
  • #34832 — Cowork connectors lose auth after context compaction (ephemeral session auth state)
  • #52565 — custom connector tokens fail to persist across restart; 401 misclassified (Cowork listed)
  • #59854 — Cowork GitHub connector: misleading connection state, dead Disconnect

This issue adds what those lack: server-side measurements (credential-attach
latency table, no-token-exchange attach, split credential state within one
session) — see the two evidence comments below.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗