From cd326d0f2d5b74ab593115e8f576a5bb153428c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Mar 2026 04:56:33 +0000 Subject: [PATCH] Fix TypeError: CORSMiddleware.__call__() missing 2 required positional arguments The _wrap_well_known_endpoint function assumed all route endpoints are regular request handlers (async def handler(request) -> Response). However, the MCP SDK's cors_middleware wraps handlers with CORSMiddleware, which is an ASGI app expecting (scope, receive, send). When the wrapper called `await endpoint(request)` on a CORSMiddleware instance, it passed only 1 argument instead of the required 3 ASGI args. The fix detects whether the endpoint is a regular handler function or an ASGI app (using the same inspect check as Starlette's Route constructor), and uses the appropriate calling convention: - Regular handlers: called as `await endpoint(request)` (existing behavior) - ASGI apps: invoked via the ASGI interface `await endpoint(scope, receive, send)` with response capture to apply cache-busting headers https://claude.ai/code/session_011S5zFTWRfKBJBUEanrhvQg --- core/server.py | 64 +++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/core/server.py b/core/server.py index d21c9d5..e2ffd79 100644 --- a/core/server.py +++ b/core/server.py @@ -57,15 +57,67 @@ def _wrap_well_known_endpoint(endpoint, etag: str): The wrapper overrides the header to ``no-store`` and adds an ``ETag`` derived from the current scope set so intermediary caches that ignore ``no-store`` still see a fingerprint change. + + Handles both regular request handlers (``async def handler(request)``) + and ASGI app endpoints (e.g. ``CORSMiddleware``) which expect + ``(scope, receive, send)`` instead. """ + import functools + import inspect - async def _no_cache_endpoint(request: Request) -> Response: - response = await endpoint(request) - response.headers["Cache-Control"] = "no-store, must-revalidate" - response.headers["ETag"] = etag - return response + # Determine if endpoint is a regular handler function or an ASGI app. + # Starlette's Route uses the same check (see starlette/routing.py). + endpoint_handler = endpoint + while isinstance(endpoint_handler, functools.partial): + endpoint_handler = endpoint_handler.func + is_regular_handler = inspect.isfunction(endpoint_handler) or inspect.ismethod( + endpoint_handler + ) - return _no_cache_endpoint + if is_regular_handler: + + async def _no_cache_endpoint(request: Request) -> Response: + response = await endpoint(request) + response.headers["Cache-Control"] = "no-store, must-revalidate" + response.headers["ETag"] = etag + return response + + return _no_cache_endpoint + else: + # ASGI app (e.g. CORSMiddleware wrapping a handler). + # Invoke via the ASGI interface and capture the response so we can + # override cache headers before returning. + async def _no_cache_asgi_endpoint(request: Request) -> Response: + status_code = 200 + raw_headers: list[tuple[bytes, bytes]] = [] + body_parts: list[bytes] = [] + + async def send(message): + nonlocal status_code, raw_headers + if message["type"] == "http.response.start": + status_code = message["status"] + raw_headers = list(message.get("headers", [])) + elif message["type"] == "http.response.body": + body_parts.append(message.get("body", b"")) + + await endpoint(request.scope, request._receive, send) + + headers = { + (k.decode() if isinstance(k, bytes) else k): ( + v.decode() if isinstance(v, bytes) else v + ) + for k, v in raw_headers + } + response = Response( + content=b"".join(body_parts), + status_code=status_code, + headers=headers, + ) + response.headers["Cache-Control"] = "no-store, must-revalidate" + response.headers["ETag"] = etag + return response + + return _no_cache_asgi_endpoint # Custom FastMCP that adds secure middleware stack for OAuth 2.1