Fix SSE endpoint accepting POST requests instead of returning 405 (#1953)

This commit is contained in:
Jeremiah Lowin 2025-09-29 20:12:59 -04:00 committed by GitHub
commit d9fe373358
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 48 additions and 65 deletions

View file

@ -3,10 +3,7 @@ from __future__ import annotations
from typing import Any
from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
from mcp.server.auth.middleware.bearer_auth import (
BearerAuthBackend,
RequireAuthMiddleware,
)
from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend
from mcp.server.auth.provider import (
AccessToken as _SDKAccessToken,
)
@ -81,7 +78,6 @@ class AuthProvider(TokenVerifierProtocol):
def get_routes(
self,
mcp_path: str | None = None,
mcp_endpoint: Any | None = None,
) -> list[Route]:
"""Get the routes for this authentication provider.
@ -93,30 +89,13 @@ class AuthProvider(TokenVerifierProtocol):
Args:
mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
mcp_endpoint: The MCP endpoint handler to protect with auth
This is used to advertise the resource URL in metadata, but the
provider does not create the actual MCP endpoint route.
Returns:
List of routes for this provider, including protected MCP endpoints if provided
List of routes for this provider (excluding the MCP endpoint itself)
"""
routes = []
# Add protected MCP endpoint if provided
if mcp_path and mcp_endpoint:
resource_metadata_url = self._get_resource_url(
"/.well-known/oauth-protected-resource"
)
routes.append(
Route(
mcp_path,
endpoint=RequireAuthMiddleware(
mcp_endpoint, self.required_scopes, resource_metadata_url
),
)
)
return routes
return []
def get_middleware(self) -> list:
"""Get HTTP application-level middleware for this auth provider.
@ -225,14 +204,13 @@ class RemoteAuthProvider(AuthProvider):
def get_routes(
self,
mcp_path: str | None = None,
mcp_endpoint: Any | None = None,
) -> list[Route]:
"""Get OAuth routes for this provider.
Creates protected resource metadata routes and optionally wraps MCP endpoints with auth.
Creates protected resource metadata routes.
"""
# Start with base routes (protected MCP endpoint)
routes = super().get_routes(mcp_path, mcp_endpoint)
# Start with base routes
routes = super().get_routes(mcp_path)
# Get the resource URL based on the MCP path
resource_url = self._get_resource_url(mcp_path)
@ -326,14 +304,12 @@ class OAuthProvider(
def get_routes(
self,
mcp_path: str | None = None,
mcp_endpoint: Any | None = None,
) -> list[Route]:
"""Get OAuth authorization server routes and optional protected resource routes.
This method creates the full set of OAuth routes including:
- Standard OAuth authorization server routes (/.well-known/oauth-authorization-server, /authorize, /token, etc.)
- Optional protected resource routes
- Protected MCP endpoints if provided
Returns:
List of OAuth routes
@ -366,7 +342,7 @@ class OAuthProvider(
)
oauth_routes.extend(protected_routes)
# Add protected MCP endpoint from base class
oauth_routes.extend(super().get_routes(mcp_path, mcp_endpoint))
# Add base routes
oauth_routes.extend(super().get_routes(mcp_path))
return oauth_routes

View file

@ -873,7 +873,6 @@ class OAuthProxy(OAuthProvider):
def get_routes(
self,
mcp_path: str | None = None,
mcp_endpoint: Any | None = None,
) -> list[Route]:
"""Get OAuth routes with custom proxy token handler.
@ -882,10 +881,10 @@ class OAuthProxy(OAuthProvider):
Args:
mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
mcp_endpoint: The MCP endpoint handler to protect with auth
This is used to advertise the resource URL in metadata.
"""
# Get standard OAuth routes from parent class
routes = super().get_routes(mcp_path, mcp_endpoint)
routes = super().get_routes(mcp_path)
custom_routes = []
token_route_found = False

View file

@ -7,8 +7,6 @@ for seamless MCP client authentication.
from __future__ import annotations
from typing import Any
import httpx
from pydantic import AnyHttpUrl
from pydantic_settings import BaseSettings, SettingsConfigDict
@ -127,7 +125,6 @@ class DescopeProvider(RemoteAuthProvider):
def get_routes(
self,
mcp_path: str | None = None,
mcp_endpoint: Any | None = None,
) -> list[Route]:
"""Get OAuth routes including Descope authorization server metadata forwarding.
@ -136,10 +133,10 @@ class DescopeProvider(RemoteAuthProvider):
Args:
mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
mcp_endpoint: The MCP endpoint handler to protect with auth
This is used to advertise the resource URL in metadata.
"""
# Get the standard protected resource routes from RemoteAuthProvider
routes = super().get_routes(mcp_path, mcp_endpoint)
routes = super().get_routes(mcp_path)
async def oauth_authorization_server_metadata(request):
"""Forward Descope OAuth authorization server metadata with FastMCP customizations."""

View file

@ -7,8 +7,6 @@ authentication for seamless MCP client authentication.
from __future__ import annotations
from typing import Any
import httpx
from pydantic import AnyHttpUrl
from pydantic_settings import BaseSettings, SettingsConfigDict
@ -135,7 +133,6 @@ class ScalekitProvider(RemoteAuthProvider):
def get_routes(
self,
mcp_path: str | None = None,
mcp_endpoint: Any | None = None,
) -> list[Route]:
"""Get OAuth routes including Scalekit authorization server metadata forwarding.
@ -144,10 +141,10 @@ class ScalekitProvider(RemoteAuthProvider):
Args:
mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
mcp_endpoint: The MCP endpoint handler to protect with auth
This is used to advertise the resource URL in metadata.
"""
# Get the standard protected resource routes from RemoteAuthProvider
routes = super().get_routes(mcp_path, mcp_endpoint)
routes = super().get_routes(mcp_path)
async def oauth_authorization_server_metadata(request):
"""Forward Scalekit OAuth authorization server metadata with FastMCP customizations."""

View file

@ -10,8 +10,6 @@ Choose based on your WorkOS setup and authentication requirements.
from __future__ import annotations
from typing import Any
import httpx
from pydantic import AnyHttpUrl, SecretStr, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
@ -364,7 +362,6 @@ class AuthKitProvider(RemoteAuthProvider):
def get_routes(
self,
mcp_path: str | None = None,
mcp_endpoint: Any | None = None,
) -> list[Route]:
"""Get OAuth routes including AuthKit authorization server metadata forwarding.
@ -373,10 +370,10 @@ class AuthKitProvider(RemoteAuthProvider):
Args:
mcp_path: The path where the MCP endpoint is mounted (e.g., "/mcp")
mcp_endpoint: The MCP endpoint handler to protect with auth
This is used to advertise the resource URL in metadata.
"""
# Get the standard protected resource routes from RemoteAuthProvider
routes = super().get_routes(mcp_path, mcp_endpoint)
routes = super().get_routes(mcp_path)
async def oauth_authorization_server_metadata(request):
"""Forward AuthKit OAuth authorization server metadata with FastMCP customizations."""

View file

@ -167,16 +167,25 @@ def create_sse_app(
# Get auth middleware from the provider
auth_middleware = auth.get_middleware()
# Get auth routes including protected MCP endpoint
auth_routes = auth.get_routes(
mcp_path=sse_path,
mcp_endpoint=handle_sse,
)
# Get auth provider's own routes (OAuth endpoints, metadata, etc)
auth_routes = auth.get_routes(mcp_path=sse_path)
server_routes.extend(auth_routes)
server_middleware.extend(auth_middleware)
# Manually wrap the SSE message endpoint with RequireAuthMiddleware
# Create protected SSE endpoint route with GET method only
server_routes.append(
Route(
sse_path,
endpoint=RequireAuthMiddleware(
handle_sse,
auth.required_scopes,
auth._get_resource_url("/.well-known/oauth-protected-resource"),
),
methods=["GET"],
)
)
# Wrap the SSE message endpoint with RequireAuthMiddleware
server_routes.append(
Mount(
message_path,
@ -274,14 +283,22 @@ def create_streamable_http_app(
# Get auth middleware from the provider
auth_middleware = auth.get_middleware()
# Get auth routes including protected MCP endpoint
auth_routes = auth.get_routes(
mcp_path=streamable_http_path,
mcp_endpoint=streamable_http_app,
)
# Get auth provider's own routes (OAuth endpoints, metadata, etc)
auth_routes = auth.get_routes(mcp_path=streamable_http_path)
server_routes.extend(auth_routes)
server_middleware.extend(auth_middleware)
# Create protected HTTP endpoint route
server_routes.append(
Route(
streamable_http_path,
endpoint=RequireAuthMiddleware(
streamable_http_app,
auth.required_scopes,
auth._get_resource_url("/.well-known/oauth-protected-resource"),
),
)
)
else:
# No auth required
server_routes.append(