Skip to content

Serve POST /api/v1/mcp without trailing-slash redirect (fixes Claude Desktop connection)#64

Merged
JanSchm merged 1 commit into
mainfrom
fix/mcp-no-slash-route
Jun 10, 2026
Merged

Serve POST /api/v1/mcp without trailing-slash redirect (fixes Claude Desktop connection)#64
JanSchm merged 1 commit into
mainfrom
fix/mcp-no-slash-route

Conversation

@JanSchm

@JanSchm JanSchm commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Problem

Claude Desktop fails to connect to the Studio MCP server ("Authorization with the MCP server failed", ref ofid_bd54e6c2d76dfd45). Production logs show the failure chain:

  1. Claude Desktop sends POST /api/v1/mcp — the URL the RFC 9728 protected-resource metadata and the README advertise (no trailing slash).
  2. The ninja route only existed at /api/v1/mcp/, so CommonMiddleware (APPEND_SLASH=True default) answered 301/api/v1/mcp/.
  3. Per HTTP semantics for 301, the client re-issues the request as GET, which hits the POST-only route → 405.

No POST ever reached the handler — including the unauthenticated one that must receive the 401 + WWW-Authenticate: Bearer resource_metadata="…" challenge that starts Desktop's OAuth flow (#61/#62). The OAuth machinery is fine; the transport URL was the blocker.

Fix

Register the MCP endpoint at "" alongside "/" (validated on django-ninja 1.6.2 — produces both mcp and mcp/ URL patterns). The alias is include_in_schema=False so OpenAPI stays clean; router-level McpAuth, rate limits, and audit logging apply identically since it's the same view function. This mirrors the sibling repo's outcome (social-intelligence-app serves both /mcp and /mcp/ with no redirect).

Request Before After
POST /api/v1/mcp 301 → GET → 405 dispatches (401 challenge / 200)
POST /api/v1/mcp/ dispatches dispatches (unchanged)
GET /api/v1/mcp{,/} 301/405 405 directly (spec-compliant, no SSE stream offered)

Verification

  • pytest apps/mcp — 63 passed; the 3 new regression tests fail without the alias (confirmed by stashing the fix) and pin the exact production failure.
  • pytest apps/api apps/oauth_server — 124 passed.
  • Local runserver smoke test: curl -X POST http://localhost:8765/api/v1/mcp401 with WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/api/v1/mcp" (previously 301); GET → 405.

After deploy

curl -i -X POST https://studio.brightbean.xyz/api/v1/mcp -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"ping"}' should return 401 + WWW-Authenticate (not 301), and retrying the connector in Claude Desktop should complete: POST → 401 → OAuth → POST → 200 in the Heroku logs.

🤖 Generated with Claude Code

Claude Desktop POSTs to /api/v1/mcp — the URL the RFC 9728 metadata and
README advertise — but the ninja route only existed at /api/v1/mcp/, so
CommonMiddleware's APPEND_SLASH answered with a 301. Clients follow a
301 by re-issuing the request as GET, which hit the POST-only route and
died with 405 before auth ever ran: the unauthenticated handshake never
received the 401 + WWW-Authenticate challenge that starts the OAuth
flow, and Desktop surfaced "Authorization with the MCP server failed".

Register the endpoint at "" alongside "/" so both URL forms dispatch
directly (the no-slash alias is hidden from the OpenAPI schema). GETs
on either form now return a spec-compliant 405 with no redirect.
Regression tests cover the exact production failure and red-bar
without the alias.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@JanSchm JanSchm merged commit b956bf3 into main Jun 10, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant