Skip to content

fix(web): break /mcp trailing-slash redirect loop that made the MCP endpoint unreachable - #353

Merged
arozumenko merged 1 commit into
mainfrom
fix/mcp-trailing-slash-redirect-loop
Jul 7, 2026
Merged

fix(web): break /mcp trailing-slash redirect loop that made the MCP endpoint unreachable#353
arozumenko merged 1 commit into
mainfrom
fix/mcp-trailing-slash-redirect-loop

Conversation

@arozumenko

Copy link
Copy Markdown
Owner

Fixes #352.

Problem

https://wikis.onetest.ai/mcp was unreachable from MCP clients — ConnectionRefused. It's a trailing-slash redirect loop, not network/auth:

  • The Starlette Mount("/mcp", …) (streamable route at /) 307-redirects bare /mcp/mcp/. Because web/src/middleware.ts rewrote to the raw upstream, the backend built that redirect from its own request URL and leaked the internal host: location: http://backend:8000/mcp/ — unreachable by the client.
  • Next.js (trailingSlash: false) 308-redirects /mcp//mcp, so the other direction loops.

Fix

Proxy the MCP path to the trailing-slash form the Mount expects, so the backend never issues the redirect. NextResponse.rewrite keeps the client URL as /mcp (no client-visible redirect, no loop).

if (pathname === '/mcp' || pathname.startsWith('/mcp/')) {
  const path = pathname === '/mcp' ? '/mcp/' : pathname;
  const url = new URL(path + request.nextUrl.search, BACKEND_URL);
  return NextResponse.rewrite(url);
}

Client config stays https://wikis.onetest.ai/mcp (no trailing slash) — the bug was purely server-side.

Verification

  • Before: curl -i https://wikis.onetest.ai/mcp307 location: http://backend:8000/mcp/; curl -i .../mcp/308 location: /mcp.
  • After deploy, the initialize POST to /mcp should return 200 with an SSE/JSON response and no Location header. (Requires a deploy to verify end-to-end.)

🤖 Generated with Claude Code

The remote MCP endpoint was unreachable (ConnectionRefused). The Starlette
Mount at /mcp (streamable route at "/") 307-redirects the bare /mcp to /mcp/,
and because the middleware rewrote to the raw upstream, that redirect leaked
the internal Docker host (http://backend:8000/mcp/) to the client. Next's
trailingSlash:false then 308-bounced /mcp/ back to /mcp, forming a loop.

Proxy /mcp to the trailing-slash form the Mount expects so the backend never
redirects. rewrite() keeps the client-facing URL as /mcp — no client-visible
redirect, no loop, no leaked internal host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@arozumenko
arozumenko merged commit b1b9860 into main Jul 7, 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.

MCP endpoint unreachable: /mcp trailing-slash redirect loop leaks internal backend host (ConnectionRefused)

1 participant