Use trailing slashes

This commit is contained in:
Jeremiah Lowin 2025-06-20 12:52:42 -04:00
commit b37686cf15
2 changed files with 16 additions and 51 deletions

View file

@ -310,28 +310,12 @@ def create_streamable_http_app(
# Re-raise other RuntimeErrors if they don't match the specific message
raise
# Create an ASGI app wrapper that normalizes paths and handles both /mcp and /mcp/
class PathNormalizingASGIApp:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
# Normalize path to remove trailing slash for MCP SDK
if scope["type"] == "http":
path = scope["path"]
if path.endswith("/") and len(path) > 1:
scope = dict(scope)
scope["path"] = path.rstrip("/")
await self.app(scope, receive, send)
# Ensure the streamable_http_path ends with a trailing slash to avoid automatic redirects
# when mounting the application
if not streamable_http_path.endswith("/"):
streamable_http_path = streamable_http_path + "/"
# Create the path-normalizing wrapper
normalized_handler = PathNormalizingASGIApp(handle_streamable_http)
# Create path pattern without trailing slash
path_pattern = streamable_http_path.rstrip("/")
# Add StreamableHTTP routes with or without auth - add both with and without trailing slash
# Add StreamableHTTP routes with or without auth
if auth:
auth_middleware, auth_routes, required_scopes = (
setup_auth_middleware_and_routes(auth)
@ -340,38 +324,19 @@ def create_streamable_http_app(
server_routes.extend(auth_routes)
server_middleware.extend(auth_middleware)
# Auth is enabled, wrap app with RequireAuthMiddleware
wrapped_app = RequireAuthMiddleware(normalized_handler, required_scopes)
# Add routes for both with and without trailing slash
# Auth is enabled, wrap endpoint with RequireAuthMiddleware
server_routes.append(
Route(
path_pattern,
endpoint=wrapped_app,
methods=["GET", "POST"]
)
)
server_routes.append(
Route(
path_pattern + "/",
endpoint=wrapped_app,
methods=["GET", "POST"]
Mount(
streamable_http_path,
app=RequireAuthMiddleware(handle_streamable_http, required_scopes),
)
)
else:
# No auth required - add routes for both with and without trailing slash
# No auth required
server_routes.append(
Route(
path_pattern,
endpoint=normalized_handler,
methods=["GET", "POST"]
)
)
server_routes.append(
Route(
path_pattern + "/",
endpoint=normalized_handler,
methods=["GET", "POST"]
Mount(
streamable_http_path,
app=handle_streamable_http,
)
)
@ -400,6 +365,6 @@ def create_streamable_http_app(
# Store the FastMCP server instance on the Starlette app state
app.state.fastmcp_server = server
app.state.path = streamable_http_path.rstrip("/")
app.state.path = streamable_http_path
return app

View file

@ -192,9 +192,9 @@ class Settings(BaseSettings):
# HTTP settings
host: str = "127.0.0.1"
port: int = 8000
sse_path: str = "/sse"
sse_path: str = "/sse/"
message_path: str = "/messages/"
streamable_http_path: str = "/mcp"
streamable_http_path: str = "/mcp/"
debug: bool = False
# error handling