From 0bf3521b2c1003af500474defe561651dbd1ee85 Mon Sep 17 00:00:00 2001 From: 633WHU Date: Thu, 22 May 2025 17:00:41 +0800 Subject: [PATCH 1/9] Update streamable_http_path to /mcp/ to avoid 307 Temporary Redirect --- src/mcp/server/fastmcp/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 3282baae6..2b3f96688 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -92,7 +92,7 @@ class Settings(BaseSettings, Generic[LifespanResultT]): mount_path: str = "/" # Mount path (e.g. "/github", defaults to root path) sse_path: str = "/sse" message_path: str = "/messages/" - streamable_http_path: str = "/mcp" + streamable_http_path: str = "/mcp/" # StreamableHTTP settings json_response: bool = False From d89e7370721f7911b75c797d61a188f521446394 Mon Sep 17 00:00:00 2001 From: 633WHU Date: Mon, 26 May 2025 10:58:33 +0800 Subject: [PATCH 2/9] Streamable HTTP Trailing Slash Compatibility --- src/mcp/server/fastmcp/server.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 2b3f96688..17c930636 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -92,7 +92,7 @@ class Settings(BaseSettings, Generic[LifespanResultT]): mount_path: str = "/" # Mount path (e.g. "/github", defaults to root path) sse_path: str = "/sse" message_path: str = "/messages/" - streamable_http_path: str = "/mcp/" + streamable_http_path: str = "/mcp" # StreamableHTTP settings json_response: bool = False @@ -830,6 +830,18 @@ async def handle_streamable_http( ) ) + # Always mount both /mcp and /mcp/ for full compatibility, regardless of default + _main_path = self.settings.streamable_http_path + if _main_path.endswith("/"): + _alt_path = _main_path.rstrip("/") + else: + _alt_path = _main_path + "/" + if _alt_path != _main_path: + if self._auth_server_provider: + routes.append(Mount(_alt_path, app=RequireAuthMiddleware(handle_streamable_http, required_scopes))) + else: + routes.append(Mount(_alt_path, app=handle_streamable_http)) + routes.extend(self._custom_starlette_routes) return Starlette( From 7d6e14ff2480ef032e6e409bf50e574a2c094c7f Mon Sep 17 00:00:00 2001 From: 633WHU Date: Mon, 26 May 2025 11:00:09 +0800 Subject: [PATCH 3/9] Streamable HTTP Trailing Slash Compatibility --- .../mcp_simple_streamablehttp_stateless/server.py | 1 + 1 file changed, 1 insertion(+) 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 bbf3dc64c..935e796b9 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 @@ -130,6 +130,7 @@ async def lifespan(app: Starlette) -> AsyncIterator[None]: debug=True, routes=[ Mount("/mcp", app=handle_streamable_http), + Mount("/mcp/", app=handle_streamable_http), ], lifespan=lifespan, ) From a84399b4a40ea5ee04f014a493cf94a6046ff466 Mon Sep 17 00:00:00 2001 From: 633WHU Date: Mon, 26 May 2025 11:00:44 +0800 Subject: [PATCH 4/9] Streamable HTTP Trailing Slash Compatibility --- .../simple-streamablehttp/mcp_simple_streamablehttp/server.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py b/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py index bf6f51e5c..41ee52de3 100644 --- a/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py +++ b/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py @@ -158,6 +158,7 @@ async def lifespan(app: Starlette) -> AsyncIterator[None]: debug=True, routes=[ Mount("/mcp", app=handle_streamable_http), + Mount("/mcp/", app=handle_streamable_http), ], lifespan=lifespan, ) From 1a638adeb9c4fe58062edcff7b45773c34e80fa3 Mon Sep 17 00:00:00 2001 From: 633WHU Date: Mon, 26 May 2025 11:05:44 +0800 Subject: [PATCH 5/9] Streamable HTTP Trailing Slash Compatibility --- src/mcp/server/fastmcp/server.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 17c930636..eaab02b06 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -838,9 +838,22 @@ async def handle_streamable_http( _alt_path = _main_path + "/" if _alt_path != _main_path: if self._auth_server_provider: - routes.append(Mount(_alt_path, app=RequireAuthMiddleware(handle_streamable_http, required_scopes))) + routes.append( + Mount( + _alt_path, + app=RequireAuthMiddleware( + handle_streamable_http, + required_scopes, + ), + ) + ) else: - routes.append(Mount(_alt_path, app=handle_streamable_http)) + routes.append( + Mount( + _alt_path, + app=handle_streamable_http, + ) + ) routes.extend(self._custom_starlette_routes) From b3bf4c9e8b2356376adb21f777833dcae015172e Mon Sep 17 00:00:00 2001 From: 633WHU Date: Mon, 26 May 2025 11:09:47 +0800 Subject: [PATCH 6/9] Streamable HTTP Trailing Slash Compatibility --- .../mcp_simple_streamablehttp_stateless/server.py | 1 - 1 file changed, 1 deletion(-) 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 935e796b9..bbf3dc64c 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 @@ -130,7 +130,6 @@ async def lifespan(app: Starlette) -> AsyncIterator[None]: debug=True, routes=[ Mount("/mcp", app=handle_streamable_http), - Mount("/mcp/", app=handle_streamable_http), ], lifespan=lifespan, ) From 645e1abf20f9eedf73f45a5acc43eb59bc7548f6 Mon Sep 17 00:00:00 2001 From: 633WHU Date: Mon, 26 May 2025 11:10:16 +0800 Subject: [PATCH 7/9] Streamable HTTP Trailing Slash Compatibility --- .../simple-streamablehttp/mcp_simple_streamablehttp/server.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py b/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py index 41ee52de3..bf6f51e5c 100644 --- a/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py +++ b/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py @@ -158,7 +158,6 @@ async def lifespan(app: Starlette) -> AsyncIterator[None]: debug=True, routes=[ Mount("/mcp", app=handle_streamable_http), - Mount("/mcp/", app=handle_streamable_http), ], lifespan=lifespan, ) From 7ef5f8d458456db46dc37c83e9bd34f163b86f21 Mon Sep 17 00:00:00 2001 From: Vincenzo Maria Calandra <65887821+vectorstain@users.noreply.github.com> Date: Tue, 27 May 2025 11:05:18 +0200 Subject: [PATCH 8/9] Clean logic (#3) --- src/mcp/server/fastmcp/server.py | 51 +++++++++++++------------------- 1 file changed, 20 insertions(+), 31 deletions(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index eaab02b06..ee0942382 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -790,6 +790,12 @@ async def handle_streamable_http( middleware: list[Middleware] = [] required_scopes = [] + # Always mount both /mcp and /mcp/ for full compatibility, regardless of default + # Verify that _main_path has root format -> /mcp + _main_path = self.settings.streamable_http_path.removesuffix("/") + # Format _alt_path so it ends with '/' -> /mcp/ + _alt_path = _main_path + "/" + # Add auth endpoints if auth provider is configured if self._auth_server_provider: assert self.settings.auth @@ -815,46 +821,29 @@ async def handle_streamable_http( revocation_options=self.settings.auth.revocation_options, ) ) - routes.append( + routes.extend([ Mount( - self.settings.streamable_http_path, + _main_path, app=RequireAuthMiddleware(handle_streamable_http, required_scopes), - ) + ), + Mount( + _alt_path, + app=RequireAuthMiddleware(handle_streamable_http, required_scopes), + )] ) else: # Auth is disabled, no wrapper needed - routes.append( + routes.extend([ Mount( - self.settings.streamable_http_path, + _main_path, app=handle_streamable_http, - ) + ), + Mount( + _alt_path, + app=handle_streamable_http, + )] ) - # Always mount both /mcp and /mcp/ for full compatibility, regardless of default - _main_path = self.settings.streamable_http_path - if _main_path.endswith("/"): - _alt_path = _main_path.rstrip("/") - else: - _alt_path = _main_path + "/" - if _alt_path != _main_path: - if self._auth_server_provider: - routes.append( - Mount( - _alt_path, - app=RequireAuthMiddleware( - handle_streamable_http, - required_scopes, - ), - ) - ) - else: - routes.append( - Mount( - _alt_path, - app=handle_streamable_http, - ) - ) - routes.extend(self._custom_starlette_routes) return Starlette( From 46e5a6f1789984f037589329eaf9f263afcb0e8f Mon Sep 17 00:00:00 2001 From: Vincenzo Maria Calandra Date: Tue, 27 May 2025 20:05:50 +0200 Subject: [PATCH 9/9] Update --- src/mcp/server/fastmcp/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index ee0942382..e1a1e85f0 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -1065,4 +1065,4 @@ async def warning(self, message: str, **extra: Any) -> None: async def error(self, message: str, **extra: Any) -> None: """Send an error log message.""" - await self.log("error", message, **extra) + await self.log("error", message, **extra) \ No newline at end of file