anthropics / anthropics/claude-ai-mcp

Custom connector never echoes Mcp-Session-Id: server issues one on every initialize, client returns it on 0 of 30 requests

Ouverte
#975 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
bug
Langage dominant
Aucune donnée de langage
Étoiles
471
Forks
76
Métriques de merge des PR
Aucune PR mergée en 30 j

Description

### What happened?

A custom connector completes OAuth 2.1 successfully, then fails to establish an MCP session. The user sees only:

> "Your account was authorized, but returned an error when connecting."

Server-side instrumentation shows the MCP server issuing a valid `Mcp-Session-Id` on **every** `initialize` response, and the Claude backend **never** returning it on any subsequent request. The connector then re-runs `initialize` and loops until it gives up.

### Aggregate over one full reconnect cycle

| Measurement | Count |
|---|---|
| Requests from Anthropic backend (UA `Claude-User`) | **30** |
| `initialize` responses carrying `Mcp-Session-Id` | **23** |
| Responses not carrying it | 7 — all `server/discover` calls short-circuited by a local workaround that by design does not set the header |
| Requests where the client **sent** `Mcp-Session-Id` | **0** |

Zero of thirty. Not intermittent — the header is never echoed, including on the first request immediately after a successful `initialize`.

### Consecutive requests arrive from different backend nodes

Within a single handshake:

```
160.79.106.160 160.79.106.163 160.79.106.165 160.79.106.167
160.79.106.168 160.79.106.169 160.79.106.175 160.79.106.176
160.79.106.180 160.79.106.188
```

If the node issuing the post-initialize call is not the node that received the `initialize` response, a session ID that is issued, received, and never returned is exactly the observable result. Offered as a hypothesis, not a claim about internal architecture.

### What did you expect to happen?

Per the MCP Streamable HTTP transport, a client that receives `Mcp-Session-Id` on the `initialize` response should include that header on all subsequent requests in the session.

Expected: after `initialize` returns 200 with a session ID, the connector includes it on the next request, the session establishes, `tools/list` succeeds, and the connector reports connected.

Actual: the header is never sent back. The server correctly rejects sessionless calls that require state, the connector interprets that as a dead session, re-runs `initialize`, and loops until failure.

Secondary expectation: when the MCP server returns a specific JSON-RPC error, that message should reach the user. Here the server returned an actionable `error.message` that was replaced with a generic "returned an error when connecting" plus an `ofid_` code. Surfacing `error.message` and `error.code` would have cut diagnosis from about two hours to minutes.

### Steps to reproduce

1. Configure a custom connector against an MCP server implementing Streamable HTTP with session IDs (here: Royal MCP 1.4.45 on WordPress, OAuth 2.1 + PKCE + DCR).
2. Authorize the connector — OAuth completes successfully (`register` → `authorize` → `token` all succeed).
3. The connection step then fails with "returned an error when connecting."
4. On the server, log inbound requests and note (a) each `initialize` response carries `Mcp-Session-Id`, and (b) no inbound request from `Claude-User` ever carries that header.

Observed sequence:

```
initialize -> HTTP 200 (response carries Mcp-Session-Id)
server/discover -> no session sent -> HTTP 400 -32600 "Mcp-Session-Id header required"
initialize -> HTTP 200 (response carries a NEW Mcp-Session-Id)
server/discover -> no session sent -> HTTP 400
initialize -> HTTP 200
...client gives up
```

### Area

MCP Connector (adding/managing servers)

### MCP Server (if applicable)

Royal MCP 1.4.45 (WordPress) — custom connector, OAuth 2.1 / Streamable HTTP

### Error messages or logs

```shell
User-facing error (the only diagnostic surfaced):

Your account was authorized, but returned an error when connecting.
You can try again, or check that the server is working.
If this persists, share this reference with support: "ofid_..."

Server-side JSON-RPC error the client received but did not surface:

{"jsonrpc":"2.0","error":{"code":-32600,"message":"Mcp-Session-Id header required. Please initialize first."}}

Reference codes from successive failed attempts:

ofid_2b531869b387868c
ofid_de15cb077c037974
ofid_653f9ada09fb65d6
ofid_65d9688e4e4b2b92
ofid_f00fc708200d80fe
ofid_9ac33af870f665ea
ofid_0d9a5a4021db3ff5
```

### Additional context

## Server-side behaviour was verified healthy

Each measured directly, not inferred:

| Check | Result |
|---|---|
| `Mcp-Session-Id` emitted on `initialize` (OAuth Bearer path) | **Yes — 23 of 23** |
| Correctly listed in `Access-Control-Expose-Headers` | Yes |
| `tools/list` with a valid session ID | **HTTP 200**, full tool list returned |
| Does re-running `initialize` invalidate an earlier session? | **No** — session #1 served `tools/list` after two later `initialize` calls |
| Does `initialize` mint a fresh ID each time? | Yes — three calls, three distinct 32-char IDs |
| `Authorization: Bearer` survives nginx/PHP-FPM | Yes, confirmed present server-side |
| OAuth 2.1 DCR + PKCE | `register` → `authorize` → `token` all succeed |
| `/.well-known/oauth-authorization-server` | Valid metadata, correct issuer |
| `/.well-known/oauth-protected-resource` | Valid, `resource` correct per RFC 9728 |
| Edge/WAF/CDN interference | None — direct origin, no CDN, requests confirmed arriving |

Environment: WordPress 7.1, PHP 8.2.20, nginx, Nexcess managed hosting, no CDN or WAF in front of the origin.

## Questions

1. **Does the Claude.ai connector persist and echo `Mcp-Session-Id` across requests?** We observe it never being sent.
2. **Is session state shared across backend nodes?** Consecutive requests in one handshake arrive from different IPs.
3. **What is `server/discover`?** It is not part of the MCP specification, and it is issued immediately after `initialize`. What response does the connector expect, and should it carry a session ID? A server returning a spec-correct `-32601 Method not found` does not satisfy the client — it re-runs `initialize` rather than proceeding.

## Related

- Possibly overlaps #469 (defect 4, session handling; defect 2, error-message swallowing).
- The server-side half of this investigation, including a genuine defect found in the MCP server itself, is documented at royalplugins/royal-mcp#103.

Instrumentation remains in place on the affected site and can capture any additional field on request — full request/response headers, timing, or per-node correlation. Happy to test a candidate fix.
- Not a duplicate of #899, though GitHub flags it as a possible one. There OAuth itself fails on reconnect (double `/authorize`, no callback). Here OAuth completes cleanly every time — `register`, `authorize` and `token` all succeed — and the failure is one phase later, in MCP session establishment. Both do appear to involve a client-side retry loop, so they may share a cause upstream.

Guide de contribution

Aucun guide de contribution indexé pour ce dépôt

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.