-
Notifications
You must be signed in to change notification settings - Fork 615
[BUG][AUTH]: CORS preflight OPTIONS requests return 401 on /mcp endpoints #2152
Description
🐞 Bug Summary
CORS preflight OPTIONS requests to /servers/{id}/mcp endpoints return 401 Unauthorized, causing browser-based MCP clients to fail with CORS errors.
The streamable_http_auth function in mcpgateway/transports/streamablehttp_transport.py does not exempt OPTIONS requests from authentication. Per RFC 7231 Section 4.3.7, OPTIONS requests (used for CORS preflight) cannot carry Authorization headers and must be allowed through for CORS to function.
🧩 Affected Component
- Federation or Transports
-
mcpgateway- API
🔁 Steps to Reproduce
- Start the MCP Gateway with CORS enabled and
ALLOWED_ORIGINSconfigured to include the client origin (e.g.,http://localhost:3000) - From a browser-based MCP client (e.g., mcp-use inspector at
http://localhost:3000), attempt to connect tohttp://localhost:4444/servers/{server_uuid}/mcp/ - Browser sends OPTIONS preflight request
- Gateway returns 401 Unauthorized
- Browser blocks the actual request due to CORS failure
🤔 Expected Behavior
OPTIONS preflight requests should be allowed through without authentication, returning appropriate CORS headers (Access-Control-Allow-Origin, etc.). The actual POST/GET requests that follow should still require authentication.
📓 Logs / Error Output
Gateway logs show:
[http_gateway] Request completed: OPTIONS /servers/2e44c8eec1024d4b838d2b80594e9f92/mcp - 401
Browser console shows:
Access to fetch at 'http://localhost:4444/servers/.../mcp' from origin 'http://localhost:3000'
has been blocked by CORS policy: Response to preflight request doesn't pass access control check:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
🧠 Environment Info
| Key | Value |
|---|---|
| Version or commit | main branch |
| Runtime | Python 3.11, Granian |
| Platform / OS | macOS (Darwin 25.1.0) |
| Container | Docker Compose |
🧩 Additional Context
The fix should add an OPTIONS method check at the beginning of the streamable_http_auth function:
# Skip auth for OPTIONS preflight requests (CORS requires this per RFC 7231)
method = scope.get("method", "")
if method == "OPTIONS":
return TrueThis is consistent with how DocsAuthMiddleware already handles OPTIONS requests (line 1347-1349 in main.py).
Note: The CORSMiddleware is added first in the middleware stack but runs last due to how Starlette middleware ordering works. Since MCPPathRewriteMiddleware calls streamable_http_auth directly before the request reaches CORSMiddleware, the preflight fails.