[BUG] Cowork agentic mode: plugin OAuth helper loses flow state between turns — authorization codes issued but never exchanged ("No OAuth flow is in progress")
Preflight Checklist
- [x] I have searched existing issues and this consolidates several (see "Related issues" below — the canonical one, #49043, was closed by the stale-bot without a fix)
- [x] This is a single bug report (one root cause: plugin OAuth helper state is not durable across Cowork agentic turns)
- [x] Reproduced on the latest Cowork desktop build (2026-06-11)
What's Wrong?
We operate a remote MCP server (https://mcp.adspirer.com/mcp — OAuth 2.1 authorization server with Dynamic Client Registration, PKCE, hosted https://claude.ai/api/mcp/auth_callback and RFC 8252 loopback support). Our marketplace plugin (adspirer-ads-agent) ships an .mcp.json pointing at that URL.
In Cowork agentic mode, the plugin OAuth flow can never complete. Sequence observed (verbatim from an affected user, reproduced by us):
mcp__plugin_adspirer-ads-agent_adspirer__authenticatereturns a valid authorize URL (loopbackredirect_uri = http://localhost:<port>/callback, PKCE S256).- User authorizes in the browser within seconds. Our server issues the authorization code and 302s to the loopback.
- The loopback listener is already gone (torn down between agentic turns), so the user copies the callback URL and pastes it into
complete_authentication. complete_authenticationreturns "No OAuth flow is in progress." — even though thestatein the pasted URL exactly matches step 1.
The client-side OAuth helper keeps the pending-flow record (including the PKCE code_verifier) in process memory, and Cowork disconnects/reconnects MCP between tool calls in agentic mode, wiping it. The server never receives a /oauth/token call at all.
Server-side forensic evidence (authorization-server logs/DB, one user, same hour, 2026-06-11 UTC)
| Attempt | Client | redirect_uri | Code issued | Code exchanged at /oauth/token |
|---|---|---|---|---|
| 22:14:20 | cli-claude-code-* (plugin) | http://localhost:51019/callback | yes (state + PKCE persisted) | never |
| 22:14:32 | cli-claude-code-* (plugin) | http://localhost:51019/callback | yes | never |
| 22:15:23 | cli-claude-code-* (plugin) | http://localhost:60222/callback | yes | never |
| 22:16:35 | cli-claude-code-* (plugin) | http://localhost:57572/callback | yes | never |
| 22:20:07 | claude (custom connector) | https://claude.ai/api/mcp/auth_callback | yes | 0.4 seconds later → working token |
| 22:38 / 22:42 | cli-claude-code-* (plugin) | http://localhost:<port>/callback | yes | never |
Same user, same server, same hour: every hosted-callback flow completes instantly; every plugin/loopback flow dies client-side after our redirect. Across our production traffic the pattern is consistent: hundreds of successful loopback exchanges from Claude Code CLI (whose process persists), ~0% success from Cowork agentic sessions.
Why the server cannot mitigate this
- Our flow state is fully DB-backed (code,
state, PKCE challenge) — durable across anything on our side. The lost state (thecode_verifier) exists only inside the Cowork helper; we never see it by design. - We cannot ship our own
authenticate/complete_authenticationMCP tools: the MCP transport is correctly 401-gated, so an auth-establishing tool sits behind the auth it would create. - OOB / paste-a-code (
urn:ietf:wg:oauth:2.0:oob) is not supported by Claude clients (per the connector docs: hosted callback and loopback only; "user-pasted bearer tokens are not yet supported"), so there is no manual recovery path we can offer. - The loopback listener lifecycle is owned by the client; we cannot keep it alive between turns.
Related issues (same root cause family, multiple vendors)
- #49043 — Asana MCP, identical symptom; closed by stale-bot as NOT_PLANNED
- #60290 — Atlassian MCP, identical symptom; auto-closed as duplicate of #49043
- #59725 — Slack MCP (open)
- #61262 — Greptile plugin: OAuth completes but tokens not persisted
- #51398 —
${CLAUDE_PLUGIN_DATA}is session-scoped in Cowork, so even disk-caching plugin tokens there doesn't survive a new conversation - #52565, #58607, #34832 — token/registration persistence across restarts and compaction
The ecosystem is currently routing around the client: Atlassian's official desktop docs tell users to connect through the mcp-remote npm bridge; Asana's docs steer users to custom connectors and label mcp-remote experimental. We now do the same (our consent page detects loopback flows and pre-emptively explains the failure with the custom-connector workaround) — but every remote-MCP vendor is independently rediscovering and working around this.
What Should Happen?
Any one of these fixes the class of bug:
- Persist the plugin OAuth helper's pending-flow state (state param → code_verifier, keyed on
state) durably across agentic turns / MCP reconnects, socomplete_authenticationcan finish a flow started in an earlier turn. - Keep (or re-bind) the loopback listener across message turns so the redirect can be received without manual paste-back.
- Route plugin-bundled remote MCP OAuth through the hosted
claude.ai/api/mcp/auth_callbackpath — the custom-connector flow already proves it works in Cowork.
We're happy to share anonymized server logs, DB traces, or a test account against https://mcp.adspirer.com/mcp. Our endpoints are standard OAuth 2.1 and pass connector validation.
Environment
- Cowork desktop (macOS), agentic mode, marketplace plugin with
.mcp.json→ remote HTTP MCP server - Server: OAuth 2.1 + DCR + PKCE (S256), hosted + loopback redirect support