diff --git a/README.md b/README.md index f2b001231..962b83084 100644 --- a/README.md +++ b/README.md @@ -713,6 +713,27 @@ The streamable HTTP transport supports: - JSON or SSE response formats - Better scalability for multi-node deployments +#### CORS Configuration for Browser-Based Clients + +If you'd like your server to be accessible by browser-based MCP clients, you'll need to configure CORS headers. The `Mcp-Session-Id` header must be exposed for browser clients to access it: + +```python +from starlette.middleware.cors import CORSMiddleware + +# Add CORS middleware to your Starlette app +app = CORSMiddleware( + app, + allow_origins=["*"], # Configure appropriately for production + allow_methods=["GET", "POST", "DELETE"], # MCP streamable HTTP methods + expose_headers=["Mcp-Session-Id"], +) +``` + +This configuration is necessary because: +- The MCP streamable HTTP transport uses the `Mcp-Session-Id` header for session management +- Browsers restrict access to response headers unless explicitly exposed via CORS +- Without this configuration, browser-based clients won't be able to read the session ID from initialization responses + ### Mounting to an Existing ASGI Server > **Note**: SSE transport is being superseded by [Streamable HTTP transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http). diff --git a/examples/servers/simple-streamablehttp-stateless/mcp_simple_streamablehttp_stateless/server.py b/examples/servers/simple-streamablehttp-stateless/mcp_simple_streamablehttp_stateless/server.py index 68f3ac6a6..df0cb39d4 100644 --- a/examples/servers/simple-streamablehttp-stateless/mcp_simple_streamablehttp_stateless/server.py +++ b/examples/servers/simple-streamablehttp-stateless/mcp_simple_streamablehttp_stateless/server.py @@ -8,6 +8,7 @@ from mcp.server.lowlevel import Server from mcp.server.streamable_http_manager import StreamableHTTPSessionManager from starlette.applications import Starlette +from starlette.middleware.cors import CORSMiddleware from starlette.routing import Mount from starlette.types import Receive, Scope, Send @@ -131,6 +132,14 @@ async def lifespan(app: Starlette) -> AsyncIterator[None]: ], lifespan=lifespan, ) + + # Add CORS middleware to expose Mcp-Session-Id header for browser-based clients + starlette_app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # Allow all origins - adjust as needed for production + allow_methods=["GET", "POST", "DELETE"], # MCP streamable HTTP methods + expose_headers=["Mcp-Session-Id"], + ) import uvicorn diff --git a/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py b/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py index 9c25cc569..125001d4c 100644 --- a/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py +++ b/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py @@ -9,6 +9,7 @@ from mcp.server.streamable_http_manager import StreamableHTTPSessionManager from pydantic import AnyUrl from starlette.applications import Starlette +from starlette.middleware.cors import CORSMiddleware from starlette.routing import Mount from starlette.types import Receive, Scope, Send @@ -159,6 +160,14 @@ async def lifespan(app: Starlette) -> AsyncIterator[None]: ], lifespan=lifespan, ) + + # Add CORS middleware to expose Mcp-Session-Id header for browser-based clients + starlette_app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # Allow all origins - adjust as needed for production + allow_methods=["GET", "POST", "DELETE"], # MCP streamable HTTP methods + expose_headers=["Mcp-Session-Id"], + ) import uvicorn