anthropics / anthropics/claude-ai-mcp

"Required when the server asks" starts OAuth at connect after a successful anonymous initialize/tools/list — server never returned a 401 (lazy-auth server built per the official guide)

Aperta
#1,013 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
auth bug server-developer-report
Lingua principale
Nessun dato sulla lingua
Stelle
471
Fork
76
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Descrizione

### MCP server URL

https://mcp.luxalgo.com/mcp

### Where does the issue occur?

When pressing the Connect button

### 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-09-09 9:30 PM EEST (18:30 UTC)

### Browser and OS

Chrome on macOS 26 (claude.ai web); connector is org-level on a Claude Team plan

### Describe the issue

Our remote MCP server implements lazy authentication exactly as described in https://claude.com/docs/connectors/building/lazy-authentication: initialize, tools/list and 28 public tools are served anonymously (HTTP 200); one protected tool (luxalgo_account) is gated at the HTTP layer and, when called without a token, returns HTTP 401 with:

WWW-Authenticate: Bearer error="invalid_token", error_description="Authentication required for this tool", resource_metadata="https://mcp.luxalgo.com/.well-known/oauth-protected-resource/mcp", scope="openid profile email offline_access"
The connector was added at organization level by an Owner with Authentication set to Required when the server asks and OAuth client set to Use Anthropic's hosted client metadata.

Two things happen that contradict the guide. First, the Add custom connector dialog pre-fills Authentication as Always required (Detected) even though the anonymous handshake succeeds. Second, after the Owner changed it to Required when the server asks, pressing Connect opens the OAuth window immediately, every time, and the connector detail page shows "Sign-in: Required".

Our origin logs show Claude completing the anonymous handshake (initialize 200, tools/list 200), then fetching /.well-known/oauth-protected-resource/mcp on its own and starting OAuth. Across ~3 hours / 85 requests the server returned zero 401s to Claude and no request ever carried a bearer token — the 401 path the guide describes was never exercised.

Support (Fin) confirmed on 2026-09-09 that per the docs "Required when the server asks" should only trigger OAuth after a transport-level 401 and that our logs do not match the documented behaviour, but could not escalate (Team plan).

### Issue details

```shell
Client registration: the authorization server supports both DCR (registration_endpoint) and CIMD (client_id_metadata_document_supported: true, "none" in token_endpoint_auth_methods_supported); the connector was added with "Use Anthropic's hosted client metadata", so Claude should be taking the CIMD path. DCR is selected above only because the form has no CIMD option.

Discovery documents, all verified with curl from outside: GET https://mcp.luxalgo.com/.well-known/oauth-protected-resource/mcp → 200 with {"resource":"https://mcp.luxalgo.com/mcp","authorization_servers":["https://app.luxalgo.com/api/auth"],"bearer_methods_supported":["header"],"scopes_supported":["openid","profile","email","offline_access"]}; GET https://mcp.luxalgo.com/.well-known/oauth-protected-resource → 200, same document; GET https://app.luxalgo.com/api/auth/.well-known/oauth-authorization-server → 200 with client_id_metadata_document_supported: true, token_endpoint_auth_methods_supported including "none", code_challenge_methods_supported: ["S256"], and a registration_endpoint. resource is byte-equal to the connector URL as entered.

Anonymous handshake with curl and no Authorization header: initialize → 200 with no WWW-Authenticate; tools/list → 200, 29 tools, no WWW-Authenticate; tools/call luxalgo_account → 401 with the WWW-Authenticate above; tools/call on any public tool → 200.

Ruled out: the server sends no challenge of any kind during connect (curl and logs); the connector was removed and re-added by an Owner with "Required when the server asks" explicitly selected, no change; the same server connects anonymously and lazily authenticates as intended in Claude Code (claude mcp add --transport http luxalgo https://mcp.luxalgo.com/mcp), Cursor and ChatGPT; Anthropic egress is not filtered — every request in the logs reached the origin and received a 2xx.

No pop-up error is shown; the OAuth window opens as if the connector were configured "Always required". flow_id from the OAuth window URL: ofid_________
```

### Expected behavior

Per https://claude.com/docs/connectors/building/lazy-authentication ("Ask Claude to list products (no prompt), then ask for your orders — the inline Connect card appears"): with Authentication = "Required when the server asks", Claude should connect without credentials, expose the public tools immediately, and start OAuth only after receiving a transport-level 401 + WWW-Authenticate on a protected tools/call. It should not fetch the protected resource metadata and start OAuth after a successful anonymous initialize/tools/list with no 401.

Secondary: the Add custom connector dialog should not detect "Always required" for a server whose anonymous initialize and tools/list return 200 — a lazy-auth server per your own sample.

### Logs from your server

```shell
Origin request log (Vercel), one Connect click, all requests from Anthropic egress, no Authorization header on any of them:

2026-09-09 18:30:10.0Z POST /mcp 200 initialize
2026-09-09 18:30:10.1Z POST /mcp 202 notifications/initialized
2026-09-09 18:30:10.3Z GET /mcp 405 (SSE probe; server declares tools.listChanged but serves no GET stream)
2026-09-09 18:30:10.4Z POST /mcp 200 tools/list
2026-09-09 18:30:11.5Z GET /.well-known/oauth-protected-resource/mcp 200
→ OAuth window opens in the browser

Identical sequence on further Connect clicks at 18:29:16Z, 18:30:37Z and 18:31:11Z. Window 2026-09-09 15:30–18:32Z, 85 requests total: 0 × HTTP 401, 0 requests with a bearer token.
```

### Additional context

Server: Node 24, @modelcontextprotocol/server v2 via mcp-handler v2 on Vercel; stateless; responses use SSE framing (text/event-stream) rather than enableJsonResponse. Authorization server: better-auth OAuth provider in our own app, issuing JWT access tokens with aud = https://mcp.luxalgo.com/mcp.

One difference from the guide's sample that may matter for the "Detected" logic: our tools/list adds a per-tool securitySchemes array (OpenAI MCP extension) — [{"type":"noauth"}] on public tools and [{"type":"oauth2","scopes":["openid","profile","email","offline_access"]}] on the protected one. If the connector reads that field to decide "Always required", that would explain both symptoms. We can disable it on request to help bisect.

Related: #262 shows the broker running OAuth discovery after a successful initialize on an authless server — the same "discover without a 401" behaviour from the other direction.

The Supported authentication types table on https://claude.com/docs/connectors/building/authentication says for none: "An optional partial-auth mode is experimental." If lazy auth is that mode and is gated, please say so in the lazy-authentication guide.

Source is public: https://github.com/LuxAlgo/luxalgo-mcp-server (gate src/auth/gate.ts, challenge src/auth/challenge.ts, PRM src/auth/metadata.ts).

Guida per i contributori

Nessuna guida per i contributori indicizzata per questo repository

Valutazione

Questa issue non è ancora stata valutata.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.