claude-code - 💡(How to fix) Fix [mcp] HTTP transport POSTs to URL origin, ignoring configured path component [2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
anthropics/claude-code#55495Fetched 2026-05-03 04:51:52
View on GitHub
Comments
2
Participants
2
Timeline
6
Reactions
0
Timeline (top)
labeled ×3commented ×2cross-referenced ×1

Claude Code 2.1.126 strips the path component from the configured mcpServers.<name>.url when initiating the MCP HTTP transport POST and when constructing the OAuth resource indicator. A server configured at https://host.tld/mcp receives POST / instead of POST /mcp, resulting in a 404 from the upstream and an unhelpful "reconnecting failed" message that hides the actual cause. The path-stripping happens in two independent code paths, so changing the configured url to include the path has no effect — there is no client-side workaround.

Error Message

Proposed error-message improvement (independent of the fix)

Root Cause

  1. The configured url field was https://mcp.example.com (no path).
  2. Claude Code derived the OAuth resource indicator as https://mcp.example.com/ (origin + trailing slash).
  3. Claude Code POSTed the MCP initialize request to https://mcp.example.com/ (origin) — not to the protected MCP endpoint.
  4. The protected MCP endpoint is at /mcp. Hitting / returns 404.

The path-stripping behavior means there is no way to point Claude Code at an MCP server mounted on any path other than /. Servers that namespace MCP under /mcp, /api/mcp, etc. are unreachable from Claude Code's HTTP transport unless reverse-proxy rewrites are added on the server side as a workaround.

Fix Action

Fix / Workaround

Summary

Claude Code 2.1.126 strips the path component from the configured mcpServers.<name>.url when initiating the MCP HTTP transport POST and when constructing the OAuth resource indicator. A server configured at https://host.tld/mcp receives POST / instead of POST /mcp, resulting in a 404 from the upstream and an unhelpful "reconnecting failed" message that hides the actual cause. The path-stripping happens in two independent code paths, so changing the configured url to include the path has no effect — there is no client-side workaround.

The path-stripping behavior means there is no way to point Claude Code at an MCP server mounted on any path other than /. Servers that namespace MCP under /mcp, /api/mcp, etc. are unreachable from Claude Code's HTTP transport unless reverse-proxy rewrites are added on the server side as a workaround.

Both code paths — the OAuth resource indicator AND the MCP transport POST target — derive from the configured url after stripping the path. There is no client-side workaround: changing the url to include the path has no effect on either output.

Code Example

"my-mcp": {
  "type": "http",
  "url": "https://mcp.example.com",
  "oauth": {
    "clientId": "00000000-0000-0000-0000-000000000000",
    "callbackPort": 8080,
    "scopes": "my.read my.write openid"
  }
}

---

T+0   GET  /.well-known/oauth-protected-resource          [Bun/1.3.14]   200
T+1   POST /oauth2/revoke                                 [axios/1.13.6] 400×2 (token still valid; harmless)
T+1   GET  /.well-known/oauth-authorization-server        [Bun/1.3.14]   200
T+3   GET  /oauth2/auth?response_type=code&PKCE&prompt=consent
                                &resource=https%3A%2F%2Fmcp.example.com%2F  302
T+3   GET  /login?login_challenge=...                     [Chrome 147]   200  ← browser opens
T+4   GET  /api/login/google?login_challenge=...                         303
T+7   GET  /api/login/google/callback?code=...                           303SSO returns
T+7   GET  /oauth2/auth?...&login_verifier=...                           302
T+8   GET  /consent?consent_challenge=...                                200
T+9   POST /api/consent                                                  303
T+9   GET  /oauth2/auth?...&consent_verifier=...                         303  ← code minted
T+10  POST /oauth2/token                                  [Bun/1.3.14]   200, 2718 bytes  ← NEW BEARER
T+10  POST /                                              [claude-code/2.1.126 (cli)]  ⚠️ 404, 13 bytes

---

{
  "iss": "https://auth.example.com",
  "aud": ["https://mcp.example.com/"],
  "sub": "11111111-1111-1111-1111-111111111111",
  "client_id": "00000000-0000-0000-0000-000000000000",
  "scp": ["my.read", "my.write", "openid", "offline_access"]
}

---

curl -i -X POST https://mcp.example.com/mcp \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  --data-binary '{"jsonrpc":"2.0","id":1,"method":"initialize",
                  "params":{"protocolVersion":"2024-11-05","capabilities":{},
                            "clientInfo":{"name":"manual-probe","version":"1.0"}}}'

HTTP/1.1 200 OK
Content-Type: text/event-stream
event: message
data: {"result":{"protocolVersion":"2024-11-05",
                 "capabilities":{"logging":{},"resources":{...},"tools":{...},"prompts":{...},"tasks":{...}},
                 "serverInfo":{"name":"obsidian-mcp-server","version":"3.1.1"}},
       "jsonrpc":"2.0","id":1}

---

T+0   POST /oauth2/revoke                                 [axios/1.13.6]   400×2 (Clear Auth)
T+12  GET  /oauth2/auth?response_type=code&PKCE
                              &resource=https%3A%2F%2Fmcp.example.com%2F  302    ← path STILL stripped
T+16  GET  /oauth2/auth?...&login_verifier=...                            302
T+18  GET  /oauth2/auth?...&consent_verifier=...                          303
T+19  POST /oauth2/token                                  [Bun/1.3.14]    200, 2718 bytes
T+19  POST /                                              [claude-code/2.1.126 (cli)]  ⚠️ 404, 13 bytes
                                                                              ← path STILL stripped

---

{
  "iss": "https://auth.example.com",
  "aud": ["https://mcp.example.com/"],   // ← path stripped here too
  "iat": <22 seconds ago>
}

---

# Workaround for Claude Code 2.1.126 path-stripping bug — the client POSTs to
# the URL origin instead of the configured /mcp path. Exact-match takes priority
# over the prefix location below, so only bare "/" is rewritten; "/mcp",
# "/.well-known/*", etc. continue to flow through unchanged.
# Remove this block once the upstream client fix ships.
location = / {
    proxy_pass http://127.0.0.1:<upstream-port>/mcp;
}

location / {
    proxy_pass http://127.0.0.1:<upstream-port>;
}
RAW_BUFFERClick to expand / collapse

[mcp] HTTP transport POSTs to URL origin, ignoring configured path component

Summary

Claude Code 2.1.126 strips the path component from the configured mcpServers.<name>.url when initiating the MCP HTTP transport POST and when constructing the OAuth resource indicator. A server configured at https://host.tld/mcp receives POST / instead of POST /mcp, resulting in a 404 from the upstream and an unhelpful "reconnecting failed" message that hides the actual cause. The path-stripping happens in two independent code paths, so changing the configured url to include the path has no effect — there is no client-side workaround.

Environment

  • Claude Code: 2.1.126 (Windows native)
  • OS: Windows 11
  • Transport: type: "http" (Streamable HTTP / SSE)
  • MCP server: cyanheads obsidian-mcp-server v3.1.1 (any path-mounted MCP server reproduces)
  • Auth server: Ory Hydra (any OAuth 2.1 + PKCE provider reproduces)

Repro

.claude.json mcpServers entry:

"my-mcp": {
  "type": "http",
  "url": "https://mcp.example.com",
  "oauth": {
    "clientId": "00000000-0000-0000-0000-000000000000",
    "callbackPort": 8080,
    "scopes": "my.read my.write openid"
  }
}

Trigger /mcp → highlight server → Reconnect.

UI reports: Got new credentials, but reconnecting to my-mcp failed. Restart Claude Code to retry.

Wire trace (nginx access.log on the MCP server)

Full Reconnect flow from Claude Code 2.1.126:

T+0   GET  /.well-known/oauth-protected-resource          [Bun/1.3.14]   200
T+1   POST /oauth2/revoke                                 [axios/1.13.6] 400×2 (token still valid; harmless)
T+1   GET  /.well-known/oauth-authorization-server        [Bun/1.3.14]   200
T+3   GET  /oauth2/auth?response_type=code&PKCE&prompt=consent
                                &resource=https%3A%2F%2Fmcp.example.com%2F  302
T+3   GET  /login?login_challenge=...                     [Chrome 147]   200  ← browser opens
T+4   GET  /api/login/google?login_challenge=...                         303
T+7   GET  /api/login/google/callback?code=...                           303  ← SSO returns
T+7   GET  /oauth2/auth?...&login_verifier=...                           302
T+8   GET  /consent?consent_challenge=...                                200
T+9   POST /api/consent                                                  303
T+9   GET  /oauth2/auth?...&consent_verifier=...                         303  ← code minted
T+10  POST /oauth2/token                                  [Bun/1.3.14]   200, 2718 bytes  ← NEW BEARER
T+10  POST /                                              [claude-code/2.1.126 (cli)]  ⚠️ 404, 13 bytes

Decoded token (server side, all green)

{
  "iss": "https://auth.example.com",
  "aud": ["https://mcp.example.com/"],
  "sub": "11111111-1111-1111-1111-111111111111",
  "client_id": "00000000-0000-0000-0000-000000000000",
  "scp": ["my.read", "my.write", "openid", "offline_access"]
}

Token verifies cleanly. Server logs show OAuth token verification successful + Authentication successful. Auth context populated. for every request that reaches the upstream.

Confirmation that auth + server are not at fault

Same bearer, same headers, manual probe to the correct path:

curl -i -X POST https://mcp.example.com/mcp \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  --data-binary '{"jsonrpc":"2.0","id":1,"method":"initialize",
                  "params":{"protocolVersion":"2024-11-05","capabilities":{},
                            "clientInfo":{"name":"manual-probe","version":"1.0"}}}'

HTTP/1.1 200 OK
Content-Type: text/event-stream
event: message
data: {"result":{"protocolVersion":"2024-11-05",
                 "capabilities":{"logging":{},"resources":{...},"tools":{...},"prompts":{...},"tasks":{...}},
                 "serverInfo":{"name":"obsidian-mcp-server","version":"3.1.1"}},
       "jsonrpc":"2.0","id":1}

The only delta between the working probe and the failing Claude Code reconnect is the request path (/mcp vs /).

Root cause analysis

  1. The configured url field was https://mcp.example.com (no path).
  2. Claude Code derived the OAuth resource indicator as https://mcp.example.com/ (origin + trailing slash).
  3. Claude Code POSTed the MCP initialize request to https://mcp.example.com/ (origin) — not to the protected MCP endpoint.
  4. The protected MCP endpoint is at /mcp. Hitting / returns 404.

The path-stripping behavior means there is no way to point Claude Code at an MCP server mounted on any path other than /. Servers that namespace MCP under /mcp, /api/mcp, etc. are unreachable from Claude Code's HTTP transport unless reverse-proxy rewrites are added on the server side as a workaround.

Confirmation: changing the configured url to include /mcp does NOT change behavior

After the first capture, the .claude.json url was changed from https://mcp.example.com to https://mcp.example.com/mcp, Claude Code was fully restarted, auth was cleared, and Reconnect was triggered to mint a fresh token from scratch.

Wire trace from the second test:

T+0   POST /oauth2/revoke                                 [axios/1.13.6]   400×2 (Clear Auth)
T+12  GET  /oauth2/auth?response_type=code&PKCE
                              &resource=https%3A%2F%2Fmcp.example.com%2F  302    ← path STILL stripped
T+16  GET  /oauth2/auth?...&login_verifier=...                            302
T+18  GET  /oauth2/auth?...&consent_verifier=...                          303
T+19  POST /oauth2/token                                  [Bun/1.3.14]    200, 2718 bytes
T+19  POST /                                              [claude-code/2.1.126 (cli)]  ⚠️ 404, 13 bytes
                                                                              ← path STILL stripped

Decoded fresh token:

{
  "iss": "https://auth.example.com",
  "aud": ["https://mcp.example.com/"],   // ← path stripped here too
  "iat": <22 seconds ago>
}

Both code paths — the OAuth resource indicator AND the MCP transport POST target — derive from the configured url after stripping the path. There is no client-side workaround: changing the url to include the path has no effect on either output.

Expected behavior

The MCP transport POST target must preserve the path component of the configured url. If url: https://host.tld/api/mcp, the request should be POST /api/mcp — not POST /.

(Separately: the OAuth resource indicator per RFC 8707 may legitimately be either the origin or the full path; that is server policy. The two should not be conflated by the client.)

Proposed error-message improvement (independent of the fix)

On any non-2xx response from the MCP transport endpoint, surface the URL and HTTP status to the user instead of the generic "reconnecting failed":

MCP transport returned HTTP 404 from POST https://mcp.example.com/ — check that the configured URL points to your MCP endpoint.

This single change would have made the present bug self-diagnosable without nginx access-log access.

Server-side workaround (in case other users are blocked)

For path-mounted MCP servers behind nginx, add an exact-match rewrite to forward bare / POSTs to the actual MCP endpoint until the upstream client fix ships:

# Workaround for Claude Code 2.1.126 path-stripping bug — the client POSTs to
# the URL origin instead of the configured /mcp path. Exact-match takes priority
# over the prefix location below, so only bare "/" is rewritten; "/mcp",
# "/.well-known/*", etc. continue to flow through unchanged.
# Remove this block once the upstream client fix ships.
location = / {
    proxy_pass http://127.0.0.1:<upstream-port>/mcp;
}

location / {
    proxy_pass http://127.0.0.1:<upstream-port>;
}

Confirmed working: POST / and POST /mcp both return the expected 200 OK SSE initialize response after this rewrite.

Suggested labels

mcp, bug, transport, dx

extent analysis

TL;DR

The issue can be fixed by modifying the Claude Code to preserve the path component of the configured url when initiating the MCP HTTP transport POST and constructing the OAuth resource indicator.

Guidance

  • The root cause of the issue is the path-stripping behavior in Claude Code, which removes the path component from the configured url.
  • To fix this, the code needs to be modified to preserve the path component when constructing the MCP transport POST target and the OAuth resource indicator.
  • A server-side workaround can be implemented using an exact-match rewrite in nginx to forward bare / POSTs to the actual MCP endpoint.
  • The error message can be improved to surface the URL and HTTP status to the user on non-2xx responses from the MCP transport endpoint.

Example

No code snippet is provided as the issue is related to the behavior of the Claude Code and not a specific code block.

Notes

The issue is specific to Claude Code version 2.1.126 and may not affect other versions. The server-side workaround is a temporary solution until the upstream client fix is shipped.

Recommendation

Apply the server-side workaround using nginx rewrite rules until the upstream client fix is available. This will allow path-mounted MCP servers to work with Claude Code 2.1.126.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

FAQ

Expected behavior

The MCP transport POST target must preserve the path component of the configured url. If url: https://host.tld/api/mcp, the request should be POST /api/mcp — not POST /.

(Separately: the OAuth resource indicator per RFC 8707 may legitimately be either the origin or the full path; that is server policy. The two should not be conflated by the client.)

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

claude-code - 💡(How to fix) Fix [mcp] HTTP transport POSTs to URL origin, ignoring configured path component [2 comments, 2 participants]