anthropics / anthropics/claude-ai-mcp

Cowork injects "requires authentication" for a plugin-bundled OAuth MCP server while tool calls to that same server succeed in the same context

Open
#727 1 comment 1 reaction 0 assignees View on GitHub
auth bug server-developer-report
Dominant language
No language data
Stars
471
Forks
76
PR merge metrics
No merged PRs in 30d

Description

### MCP server URL

https://mcp.withbamboo.com/mcp

### Where does the issue occur?

During a conversation (after initial auth)

### Transport used

streamable-http

### Client registration type

Dynamic Client Registration (DCR)

### SSE only: Does your server return the expected status code for POST requests?

Not applicable (my server does not use SSE)

### When did you last reproduce this?

2026-07-29 5:00 PM PST

### Browser and OS

macOs 26.5.2

### Describe the issue

Our product ships a Claude Code plugin (distributed via an org marketplace) whose .mcp.json declares our remote MCP server — the same shape as first-party plugins like linear@claude-plugins-official ({"type": "http", "url": "..."}, OAuth 2.1 + DCR). The same server is also connected as a claude.ai custom connector with a completed org-level OAuth grant.

In every fresh Cowork session, two subsystems disagree about this one server, and the harness feeds the model the wrong one.

Dispatch works. Tool calls to the plugin-declared server succeed on the first try, executed server-side via the broker's org credentials — consistent with the Cowork architecture doc ("connector authorization tokens never enter the sandbox; connector calls are made on the server side"). Our server logs show zero auth errors on any dispatched call.

Client-side auth state is cold and unrecoverable. The fresh sandbox VM has no stored OAuth state, so the plugin entry gets a 401, runs discovery, and performs a fresh DCR registration every session (we see one POST /oauth/register 201 per session, client_name "Claude Code (plugin:bamboo-marketing:bamboo)", redirect localhost:3118). Interactive auth can never complete in the sandbox, so the state stays "unauthenticated" indefinitely.

The harness then injects a system-level message telling the model the server "requires authentication before its tools can be used" and that it should tell the user it's unavailable. The model parrots this — in the same context window where calls to that server are returning populated JSON. In one session we asked for 10+ minutes and were repeatedly told "not authenticated" while every actual call succeeded. The state is lazily evaluated: it only flips after a call is dispatched — and we have a transcript where the injection appears immediately after a successful call.

### Issue details

```shell
Replacing our company name with "X"
-------
Me- "Try using the X mcp discover tool"

Used a tool
Used a tool
Ran it — same result as before: zero connectors registered, no auth. The tool call itself succeeds (it's not blocked), but it's returning an empty/unauthenticated state, confirming Bamboo isn't connected yet. You'd still need to authorize it via claude.ai connector settings or /mcp.

Me - "Use it"

Used a tool
Used a tool
That actually returned data — 4 brands (A, B, C, D), none with connections though. So discover itself is reachable, but there's no connected data source for any brand yet — connection_count: 0 across the board, and no brand matching "M" from your CLAUDE.md.

Want me to check discover(scope: agents) to see if there's an agent already configured for M, or is this enough to confirm the gap?

Me - "Why did you tell me you were authenticated? Don't assume, read our mcp url calls to figure out why. give me verified responses only"

Thought process
Thought process
You're right to push on this — let me trace exactly what happened rather than restate my earlier answer.

What the tool calls actually returned:

Call 1 — discover(scope: connectors): returned {"connectors":[],"count":0,"fallback_action":...}. That's a normal successful JSON response, not an authentication error.

Immediately after that call, a system-level message (not part of the tool's own output) was injected into my context stating that the X:X MCP server "requires authentication before its tools can be used" and that I should tell you it's unavailable. I took that system message at face value and told you "not authenticated" — but that claim came from the injected reminder, not from anything the tool itself returned.

Call 2 — discover(scope: connectors) again: same result, count: 0, no error field, no auth-related message.

Call 3 — discover(scope: brands): returned real data — 4 brands (A, B, C, D) with agent counts. This is not the behavior of a rejected/unauthenticated call; an auth failure would return an error, not a populated list.

So the verified facts are: no tool call in this session has returned an authentication error. Every call has returned normal (if sparse) JSON. What is verified from data is that every brand shows connection_count: 0 — meaning no data connectors (Shopify, Northbeam, etc.) are attached to any brand, which is a different fact than "the MCP session itself is unauthenticated."

I don't have a way to independently verify the injected system claim against the tool's own output — the two disagree, and I shouldn't have flattened that into a confident "not authenticated" answer. The accurate statement is: I can't confirm auth status from the tool responses alone, and the one signal claiming I'm unauthenticated came from a system message, not a tool error.
```

### Expected behavior

Either (1) a plugin-declared server whose URL matches a broker-authenticated connector inherits and reports that state, or (2) the harness verifies dispatchability before asserting "requires authentication" as fact to the model. The current behavior is the worst case: the assertion is false, the model states it confidently, and the user is told a fully working integration is unavailable — while one orphaned OAuth client row accumulates per session on our authorization server from the doomed DCR loop.

### Logs from your server

```shell
2026-07-29 19:55:21.259 [info] [OAuth/register] Proxying DCR (forced public client): {"client_name":"Claude Code (plugin:bamboo-marketing:bamboo)","redirect_uris":["http://localhost:3118/callback"],"grant_types":["authorization_code","refresh_token"],"response_types":["code"],"token_e
2026-07-29 19:55:21.324 [info] [OAuth/register] Supabase responded: 201 {"client_id":"b9712b33-ea3e-47c8-9069-3c9f370cce75","client_type":"public","redirect_uris":["http://localhost:3118/callback"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"response_types":["code"],"client_name":"Claude Code (plugin:bamboo-marketing:bamboo)
```

### Additional context

Controls: Local Claude Code with the identical plugin works correctly — interactive auth completes once and persists. The claude.ai connector for the same URL reports status correctly. So the defect is isolated to the plugin-entry auth state machine in Cowork's sandbox.

Reproduces with Anthropic's own linear@claude-plugins-official (identical .mcp.json shape) in a Cowork workspace that also has the Linear connector. (Keep this line only if you ran the experiment and it confirmed.)

Eliminated: token TTL (repro persists 1 minute after re-auth), 401 challenge shape (verified both public MCP SDK clients only consult the WWW-Authenticate error field on 403), timing (10-minute repeated-false vs instant call success), and server-side auth bugs (zero errors; broker refresh grants complete in ~98ms).

Related but distinct: claude-code #39271, #44535, #39443 (auth-status vs reality mismatches, static-token or local; all closed) and #30385, #34832 (Cowork session-state loss). None isolates the contradiction shown here: a false "requires authentication" injected while calls to the same server succeed in the same context.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.