anthropics / anthropics/claude-ai-mcp
Custom connector fails on both auth paths: Claude never sends credentials to the MCP endpoint
- 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 or name
https://mds-gestion.mds-sdm.com/api/mcp/
### How are you connecting to this server?
From a custom connector URL I entered manually
### Where does the issue occur?
When auth finishes and redirects back to Claude.ai
### When did you last reproduce this?
2026-08-28 15:39:00
### Browser and OS
Firefox on Linux Mint - IPad OS27
### Describe the issue
See attached files
### Error details
```shell
see attached files
```
### Expected behavior
Able to connect to my mcp
### Additional context
## Custom connector fails on both auth paths: Claude never sends credentials to the MCP endpoint
### Summary
A self-hosted remote MCP server completes every step of the OAuth 2.1 + DCR + PKCE
flow successfully — including issuing an access token with `HTTP 200` — but Claude
never sends a single authenticated request to the MCP endpoint afterwards. The
connector then reports *"Authorization with MDS Gestion failed. You can check your
credentials and permissions."*
The same server, with the **static request header** path (`x-api-key`) instead of
OAuth, fails the same way: Claude reaches the MCP endpoint but sends **no
authentication header at all**, while the exact same credential completes a full
MCP handshake when sent by `curl` ten minutes apart against the same URL.
Both failure modes are visible in server-side access logs and are, from the
server's point of view, indistinguishable from "the client chose not to
authenticate".
### Support references
- `ofid_f24cc0edaccdc3f0`
- `ofid_3cce372cf1a0f181`
### Environment
| | |
|---|---|
| MCP server URL | `https://mds-gestion.mds-sdm.com/api/mcp` |
| Transport | Streamable HTTP, stateless (`@modelcontextprotocol/sdk` 1.30, `sessionIdGenerator: undefined`) |
| Stack | Node.js 20 + Express 4, behind nginx, behind Cloudflare (proxied) |
| Auth server | Same origin, OAuth 2.1 + PKCE S256 + RFC 7591 DCR |
| Claude surfaces tested | claude.ai web (connector added there), iOS app |
| Claude plan | Max |
| Date | 2026-08-28 (all timestamps UTC) |
---
## Part A — OAuth path: token issued, never used
### Server-side log, one complete attempt
```
14:55:25 POST /api/mcp 401 ← unauthenticated probe (expected)
14:55:25 GET /.well-known/oauth-protected-resource/api/mcp 200
14:55:26 GET /.well-known/oauth-authorization-server 200
14:55:26 POST /oauth/register 201
14:55:26 GET /oauth/authorize?... 200 ← browser, referer https://claude.ai/
14:55:32 POST /oauth/authorize 302 ← user approved; redirect to callback
14:55:32 POST /oauth/token 200 ← access token issued
(nothing further — no request to /api/mcp ever arrives)
```
Reproduced identically three times (14:47:51, 14:48:59, 14:55:25). In every case
the token exchange returns `200` in under one second, and no subsequent request —
authenticated or not — reaches the server.
### Spec conformance verified before reporting
Checked against
[Authentication for connectors](https://claude.com/docs/connectors/building/authentication):
| Requirement | Status |
|---|---|
| `401` + `WWW-Authenticate: Bearer resource_metadata="…"` | ✅ |
| Protected resource metadata `resource` matches the URL as entered, path included | ✅ `https://mds-gestion.mds-sdm.com/api/mcp` |
| Metadata reachable at the RFC 9728 path-inserted URL **and** at the root | ✅ both `200` |
| `authorization_servers` lists the issuer, primary first | ✅ single entry |
| RFC 8414 metadata served, `issuer` matches | ✅ |
| `code_challenge_methods_supported: ["S256"]` advertised, S256 required | ✅ |
| DCR (`registration_endpoint`), public client, `token_endpoint_auth_methods_supported: ["none"]` | ✅ |
| `/token` accepts `application/x-www-form-urlencoded` | ✅ |
| `/token` returns `Cache-Control: no-store` | ✅ |
| Refresh token rotation on public clients | ✅ old token revoked in the same response |
| Redirect URI `https://claude.ai/api/mcp/auth_callback` registered | ✅ |
| Discovery / registration / token responses under the 10 s budget | ✅ all sub-second |
Cloudflare was ruled out: every request above appears in the origin's nginx access
log, so nothing was blocked at the edge. Bot Fight Mode is **off**, AI Labyrinth is
**off**, and the AI bot policies are set to *Allow (do not block)* for Search, Agent
and Training.
### Matching reports
This is the same signature as
[anthropics/claude-ai-mcp#690](https://github.com/anthropics/claude-ai-mcp/issues/690)
(open), [modelcontextprotocol#2157](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/2157),
and [anthropics/claude-ai-mcp#291](https://github.com/anthropics/claude-ai-mcp/issues/291)
(closed as not planned).
---
## Part B — Static header path: no credential sent at all
The connector was re-created using request-header authentication, with the header
name `x-api-key` (offered by the connector UI) and a valid API key as the value.
### The same credential, ten minutes apart
```
15:27:02 POST /api/mcp 200 189 bytes curl/7.88.1 ← initialize
15:27:02 POST /api/mcp 200 7474 bytes curl/7.88.1 ← tools/list (full tool catalogue)
15:37:04 POST /api/mcp 401 120 bytes python-httpx/0.28.1 ← claude.ai
```
### Why the 120-byte response is conclusive
The server returns three distinct `401` bodies, of three distinct lengths. Measured
directly against production:
| Request | Response | Meaning |
|---|---|---|
| no authentication header | `401`, **120 bytes** | no credential was sent |
| `x-api-key: ` | `401`, 53 bytes | header received, key not recognised |
| `x-api-key: ` | `401`, 44 bytes | header received, value malformed |
Claude's request produced the **120-byte** body. The header was therefore not
present on the request at all — this is not a rejected key, a truncated value, or a
name-casing problem.
### Expected vs. actual
- **Expected**: Claude sends the configured static header on every request to the
MCP endpoint, as described under *Supported authentication types* → `static_headers`.
- **Actual**: the request arrives with no authentication header. Because the
server's `401` carries a `WWW-Authenticate` challenge (required for OAuth
clients), Claude then appears to fall back to the OAuth flow — which fails per
Part A — producing an *authorization* error message even though the connector was
configured with a static header.
---
## What we are asking
1. Whether the two `ofid_` traces show what Claude's backend did after
`POST /oauth/token` returned `200`, and why no request followed.
2. Whether `static_headers` is enabled for this account. If it is not, the
connector UI still offered the header fields and accepted them silently, which
is indistinguishable from a broken feature. If it is, why the configured header
is absent from the outgoing request.
3. Whether any server-side change on our side could work around either path. We
have found none: the MCP endpoint answers a complete `initialize` +
`tools/list` handshake correctly for `curl`, and the authorization server meets
every documented requirement.
### Reproduction script
The MCP endpoint can be verified independently with the script we use ourselves —
it replays exactly what an MCP client does, and passes against production:
```bash
curl -sS -X POST https://mds-gestion.mds-sdm.com/api/mcp \
-H "Authorization: Bearer " \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2025-06-18' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1.0.0"}}}'
```
Full server-side logs and the authorization server source can be provided on
request.
_No response_
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Valutazione
Questa issue non è ancora stata valutata.