Skip to content

[BUG][AUTH]: CORS preflight OPTIONS requests return 401 on /mcp endpoints #2152

@bradmcnew

Description

@bradmcnew

🐞 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

  1. Start the MCP Gateway with CORS enabled and ALLOWED_ORIGINS configured to include the client origin (e.g., http://localhost:3000)
  2. From a browser-based MCP client (e.g., mcp-use inspector at http://localhost:3000), attempt to connect to http://localhost:4444/servers/{server_uuid}/mcp/
  3. Browser sends OPTIONS preflight request
  4. Gateway returns 401 Unauthorized
  5. 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 True

This 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.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingsecurityImproves security

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions