From 28aaa02cbc504e411bc6ae0d38871623a778e4cc Mon Sep 17 00:00:00 2001 From: Jerome Date: Mon, 30 Jun 2025 16:41:29 +0100 Subject: [PATCH 1/2] feat: Add CORS configuration for browser-based MCP clients - Add CORSMiddleware to streamable HTTP example servers - Configure minimal CORS with Mcp-Session-Id exposed - Add CORS documentation section to README This enables browser-based clients to connect to MCP servers by properly exposing the Mcp-Session-Id header required for session management. Reported-by: Jerome --- README.md | 21 +++++++++++++++++++ .../server.py | 9 ++++++++ .../mcp_simple_streamablehttp/server.py | 9 ++++++++ 3 files changed, 39 insertions(+) diff --git a/README.md b/README.md index f2b001231..863f1cb2c 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.add_middleware( + CORSMiddleware, + 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 From 7b67d78642e8637d490fb431a71bf6881972de42 Mon Sep 17 00:00:00 2001 From: Jerome Date: Mon, 7 Jul 2025 13:53:03 +0100 Subject: [PATCH 2/2] Update README.md Co-authored-by: Marcelo Trylesinski --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 863f1cb2c..962b83084 100644 --- a/README.md +++ b/README.md @@ -721,8 +721,8 @@ If you'd like your server to be accessible by browser-based MCP clients, you'll from starlette.middleware.cors import CORSMiddleware # Add CORS middleware to your Starlette app -app.add_middleware( - CORSMiddleware, +app = CORSMiddleware( + app, allow_origins=["*"], # Configure appropriately for production allow_methods=["GET", "POST", "DELETE"], # MCP streamable HTTP methods expose_headers=["Mcp-Session-Id"],