FastMCP streamable_http_app mounted in FastAPI serves at /prefix/mcp, not /prefix
FastMCP streamable_http_app mounted in FastAPI serves at /prefix/mcp, not /prefix — probes return 403/404. Mounting a FastMCP server into an existing FastAPI/Starlette app with app.mount('/mcp', mcp_server.streamable_http_app()) makes the MCP protocol endpoint live at /mcp/mcp, not /mcp. Probing the mount path itself returns 403 or 404 (e.g. POST /mcp -> 403, POST /mcp/ -> 404), which looks like the server is down or blocked by middleware, even though MCP clients configured with the full nested URL work fine. Verified live against two production deployments (2026-08): the nested /mcp/mcp URL returns a proper initialize result while the mount root fails.
streamable_http_app() builds a Starlette app whose protocol route is at settings.streamable_http_path, default /mcp. Mounting that app under a prefix compounds the paths: /prefix + /mcp = /prefix/mcp. Options: (1) accept it and publish the nested URL (https://api.example.com/mcp/mcp) — several production servers do this and MCP directories accept any URL; (2) pass streamable_http_path='/' when constructing FastMCP(...) so the endpoint lands at the mount root (watch for Starlette's trailing-slash redirect: /mcp may 307 to /mcp/). When diagnosing, curl the nested path with BOTH accept types before concluding the server is down: `curl -X POST <url> -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"probe","version":"0"}}}'