5252logger = get_logger (__name__ )
5353
5454
55- class SilentResponse (Response ):
56- """A response that does not send any HTTP response back to the client."""
57-
58- def __init__ (self ) -> None :
59- super ().__init__ ()
60-
61- async def __call__ (self , scope : Scope , receive : Receive , send : Send ) -> None :
62- return
63-
64-
6555class Settings (BaseSettings , Generic [LifespanResultT ]):
6656 """FastMCP server settings.
6757
@@ -745,20 +735,25 @@ def sse_app(self, mount_path: str | None = None) -> Starlette:
745735 security_settings = self .settings .transport_security ,
746736 )
747737
748- async def handle_sse (scope : Scope , receive : Receive , send : Send ):
749- # Add client ID from auth context into request context if available
750-
751- async with sse .connect_sse (
752- scope ,
753- receive ,
754- send ,
755- ) as streams :
756- await self ._mcp_server .run (
757- streams [0 ],
758- streams [1 ],
759- self ._mcp_server .create_initialization_options (),
760- )
761- return SilentResponse ()
738+ async def handle_sse (request : Request ) -> Response :
739+ """Handle SSE connection using Starlette's EventSourceResponse."""
740+ # Create a custom Response class that wraps the SSE connection
741+ class SSEConnectionResponse (Response ):
742+ def __init__ (self , sse_transport : SseServerTransport , server : MCPServer ) -> None :
743+ super ().__init__ ()
744+ self .sse_transport = sse_transport
745+ self .server = server
746+
747+ async def __call__ (self , scope : Scope , receive : Receive , send : Send ) -> None :
748+ async with self .sse_transport .connect_sse (scope , receive , send ) as streams :
749+ await self .server .run (
750+ streams [0 ],
751+ streams [1 ],
752+ self .server .create_initialization_options (),
753+ )
754+
755+ # Return the Response object for Starlette to handle
756+ return SSEConnectionResponse (sse , self ._mcp_server )
762757
763758 # Create routes
764759 routes : list [Route | Mount ] = []
@@ -796,7 +791,7 @@ async def handle_sse(scope: Scope, receive: Receive, send: Send):
796791 )
797792 )
798793
799- # When auth is configured, require authentication
794+ # Create auth wrapper if needed
800795 if self ._token_verifier :
801796 # Determine resource metadata URL
802797 resource_metadata_url = None
@@ -807,11 +802,16 @@ async def handle_sse(scope: Scope, receive: Receive, send: Send):
807802 str (self .settings .auth .resource_server_url ).rstrip ("/" ) + "/.well-known/oauth-protected-resource"
808803 )
809804
810- # Auth is enabled, wrap the endpoints with RequireAuthMiddleware
805+ # # Auth is enabled, wrap the endpoints with RequireAuthMiddleware
806+ async def handle_sse_auth (scope : Scope , receive : Receive , send : Send ) -> None :
807+ request = Request (scope , receive )
808+ response = await handle_sse (request )
809+ await response (scope , receive , send )
810+
811811 routes .append (
812812 Route (
813813 self .settings .sse_path ,
814- endpoint = RequireAuthMiddleware (handle_sse , required_scopes , resource_metadata_url ),
814+ endpoint = RequireAuthMiddleware (handle_sse_auth , required_scopes , resource_metadata_url ),
815815 methods = ["GET" ],
816816 )
817817 )
@@ -824,14 +824,10 @@ async def handle_sse(scope: Scope, receive: Receive, send: Send):
824824 else :
825825 # Auth is disabled, no need for RequireAuthMiddleware
826826 # Since handle_sse is an ASGI app, we need to create a compatible endpoint
827- async def sse_endpoint (request : Request ) -> Response :
828- # Convert the Starlette request to ASGI parameters
829- return await handle_sse (request .scope , request .receive , request ._send ) # type: ignore[reportPrivateUsage]
830-
831827 routes .append (
832828 Route (
833829 self .settings .sse_path ,
834- endpoint = sse_endpoint ,
830+ endpoint = handle_sse ,
835831 methods = ["GET" ],
836832 )
837833 )
0 commit comments