Fix 307 Temporary Redirect when use streamable_http
The default mount path of Streamable HTTP is /mcp, but in some environments, a 307 Temporary Redirect to /mcp/mcp/ will appear, causing the request to hang or fail. When the streamable_http path is set to /mcp/, everything works fine.
Code Analysis Mount Path
In the server.py and simple-streamablehttp-stateless examples, the ASGI routes are Mount("/mcp", app=handle_streamable_http), that is, there is no trailing slash. In the Settings class of server.py, the default value of streamable_http_path is "/mcp" (also without a slash). Redirection Issue
Mount("/mcp", ...) route of Starlette/FastAPI, if /mcp is requested, will automatically 307 redirect to /mcp/ (with a slash). But if the client requests /mcp/ and the server only mounts /mcp, a redirect chain of /mcp/mcp/ will appear in some environments, resulting in "unreachable". This is related to the "strict slash matching" of ASGI routing. Solution
Recommended practice: Always use /mcp/ as the mounting path (with a slash) and let the client request /mcp/, so there will be no redirection problem. Or, when mounting /mcp, make sure the client only requests /mcp without a slash.
Motivation and Context
How Has This Been Tested?
Breaking Changes
Types of changes
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [ ] Documentation update
Checklist
- [ ] I have read the MCP Documentation
- [ ] My code follows the repository's style guidelines
- [ ] New and existing tests pass locally
- [ ] I have added appropriate error handling
- [ ] I have added or updated documentation as needed
Additional context
#732
when we set the default /mcp, the log will be, in some env will
INFO: Started server process [55547]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
INFO: 10.183.170.230:53048 - "POST /mcp HTTP/1.1" 307 Temporary Redirect
INFO: 10.183.170.230:53054 - "POST /mcp/ HTTP/1.1" 200 OK
INFO: 10.183.170.230:53048 - "POST /mcp HTTP/1.1" 307 Temporary Redirect
INFO: 10.183.170.230:53054 - "POST /mcp/ HTTP/1.1" 202 Accepted
INFO: 10.183.170.230:53048 - "GET /mcp HTTP/1.1" 307 Temporary Redirect
INFO: 10.183.170.230:53054 - "GET /mcp/ HTTP/1.1" 200 OK
INFO: 10.183.170.230:34296 - "POST /mcp HTTP/1.1" 307 Temporary Redirect
INFO: 10.183.170.230:34312 - "POST /mcp/ HTTP/1.1" 200 OK
in some env will be hanging, because 307 Temporary Redirect to /byoa/mcp/mcp/, this is unreachable
INFO: Started server process [35]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
INFO: 100.67.31.17:58472 - "POST /mcp HTTP/1.1" 307 Temporary Redirect
INFO: 100.67.30.95:57600 - "GET /mcp HTTP/1.1" 307 Temporary Redirect
when we set the streamable_http to '/mcp/', it will be good, as the log:
[05/22/25 01:52:28] INFO Starting server "StatelessServer"... server.py:214
INFO: Started server process [80154]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8080 (Press CTRL+C to quit)
INFO: 10.183.170.230:36166 - "POST /mcp/ HTTP/1.1" 200 OK
INFO: 10.183.170.230:36166 - "POST /mcp/ HTTP/1.1" 202 Accepted
INFO: 10.183.170.230:36182 - "GET /mcp/ HTTP/1.1" 200 OK
INFO: 10.183.170.230:33648 - "POST /mcp/ HTTP/1.1" 200 OK
@simonw @sheffler @comfuture @shimizukawa
@ihrpr
Thanks for identifying and reporting this redirect issue! However, I don't think changing the default streamable_http_path is the right approach here.
The core issue: This change creates inconsistency across the codebase rather than solving the root problem.
- All client code constructs URLs with /mcp (no trailing slash) - see examples/clients/simple-auth-client/main.py:344
- All example servers mount at /mcp (no trailing slash) - see both simple-streamablehttp examples
- All tests use /mcp (no trailing slash) consistently - see tests/shared/test_streamable_http.py:238
- Only FastMCP would have a different default with this change
Problems with this approach:
- Breaking consistency: Makes FastMCP behave differently from all examples and existing usage patterns
- Incomplete fix: The examples and tests still use /mcp without trailing slash, so the inconsistency remains
- Wrong direction: The entire ecosystem already standardized on /mcp - we shouldn't change that
Alterative:
- Keep the default as /mcp and fix the ASGI mounting logic to handle redirects properly
- Add logic to FastMCP to handle both /mcp and /mcp/ transparently at the mount level
- Document the trailing slash behavior so users can choose the approach that works for their environment
The redirect issue you've identified is real and needs fixing, but changing the default path creates more problems than it solves. Let's find a solution that maintains consistency while addressing the underlying ASGI routing behavior.
Thank you for your review and helpful suggestions. @ihrpr The following changes have been made to completely solve the trailing slash compatibility issue:
- Keep the default path as /mcp to avoid disrupting the existing ecosystem.
- In the ASGI route registration logic of FastMCP, automatically mount handlers for both /mcp and /mcp/, and clients can access them in any form without redirection.
In this way, whether the client, example, or test uses /mcp or /mcp/, it can be seamlessly compatible without modifying the existing code. The problem has been completely solved.
@chi2liu please update the branch with master and check again tests results
Why are we even mounting applications? We can have a Router and set redirect_slashes to False.
Why are we even mounting applications? We can have a
Routerand setredirect_slashestoFalse.
I have modified it according to your suggestion. Could you please review it? @Kludex
Updated the latest code, please help review @Kludex @vectorstain @mroch @ihrpr
You marked my comments as resolved, but you actually ignore them.
You marked my comments as resolved, but you actually ignore them.
Restored, please check one by one
🙏🏼
@chi2liu
Hi everyone! Could you provide an update on when this fix might be merged and released?
I'm experiencing the 307 redirect issue described in this PR.
Thanks for your work on fixing this!
Hi @alenprodan, we are waiting for a feedback @ihrpr
Thank you for working on the fix! We merged https://github.com/modelcontextprotocol/python-sdk/pull/1115 and this PR (test from this PR) and merged https://github.com/modelcontextprotocol/python-sdk/pull/1115. Closing this PR.