anthropics / anthropics/claude-ai-mcp

Custom connector fails on both auth paths: Claude never sends credentials to the MCP endpoint

Aperta
#967 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub
auth bug user-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 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.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.