xj'.dZddlZddlmZddlmZddlmZddlm Z m Z ddl Z ddl Z ddlmZddlmZdd lmZdd lmZdd lmZmZmZdd lmZmZmZdd lmZm Z ddl!m"Z"m#Z#ddl$m%Z%m&Z&ejNe(Z)GddZ*y)aSSE Server Transport Module This module implements a Server-Sent Events (SSE) transport layer for MCP servers. Example: ```python # Create an SSE transport at an endpoint sse = SseServerTransport("/messages/") # Create Starlette routes for SSE and message handling routes = [ Route("/sse", endpoint=handle_sse, methods=["GET"]), Mount("/messages/", app=sse.handle_post_message), ] # Define handler functions async def handle_sse(request): async with sse.connect_sse( request.scope, request.receive, request._send ) as streams: await app.run( streams[0], streams[1], app.create_initialization_options() ) # Return empty response to avoid NoneType error return Response() # Create and run Starlette app starlette_app = Starlette(routes=routes) uvicorn.run(starlette_app, host="127.0.0.1", port=port) ``` Note: The handle_sse function must return a Response to avoid a "TypeError: 'NoneType' object is not callable" error when client disconnects. The example above returns an empty Response() after the SSE connection ends to fix this. See SseServerTransport class documentation for more details. N)asynccontextmanager)Any)quote)UUIDuuid4)ValidationError)EventSourceResponse)Request)Response)ReceiveScopeSend)AuthenticatedUserAuthorizationContextauthorization_context)TransportSecurityMiddlewareTransportSecuritySettings)ContextSendStreamcreate_context_streams)ServerMessageMetadataSessionMessageceZdZUdZeed<eeee e zfed<eee fed<e ed<ddede dzd dffd Zed ed ed efdZd ed ed ed dfdZxZS)SseServerTransportaSSE server transport for MCP. This class provides two ASGI applications, suitable for use with a framework like Starlette and a server like Hypercorn: 1. connect_sse() is an ASGI application which receives incoming GET requests, and sets up a new SSE stream to send server messages to the client. 2. handle_post_message() is an ASGI application which receives incoming POST requests, which should contain client messages that link to a previously-established SSE session. _endpoint_read_stream_writers_session_owners _securityNendpointsecurity_settingsreturnc t|d|vs|jdsd|vsd|vrtd|d|jdsd|z}||_i|_i|_t||_tjd|y ) aCreates a new SSE server transport, which will direct the client to POST messages to the relative path given. Args: endpoint: A relative path where messages should be posted (e.g., "/messages/"). security_settings: Optional security settings for DNS rebinding protection. Note: We use relative paths instead of full URLs for several reasons: 1. Security: Prevents cross-origin requests by ensuring clients only connect to the same origin they established the SSE connection with 2. Flexibility: The server can be mounted at any path without needing to know its full URL 3. Portability: The same endpoint configuration works across different environments (development, staging, production) Raises: ValueError: If the endpoint is a full URL instead of a relative path z://z//?#zGiven endpoint: z] is not a relative path (e.g., '/messages/'), expecting a relative path (e.g., '/messages/')./z.SseServerTransport initialized with endpoint: N) super__init__ startswith ValueErrorrrrrrloggerdebug)selfrr __class__s b/mnt/ssd/data/Dropbox/adrian/sandbox/mcp-query/venv/lib/python3.12/site-packages/mcp/server/sse.pyr&zSseServerTransport.__init__Rs,  H  3 3D 9SH_PSW_P_"8*-BB  ""3'X~H!$&!!45FG EhZPQscopereceivesendc K|ddk7r tjdtdt||}|jj |dd{}|r||||d{tdtj dtttzd \}ttd \}t|jd }t|trt||j<|j <tj d |jd d } | j#d|j$z} t'| dj( t+j,t.t0t2fd \ fd t+j44d{} dt6dt8dt:ffd } tj d| j=| |||tj d||fdddd{|j j?d|jj?dy7777E#1d{7swYUxYw#|j j?d|jj?dwxYww)Ntypehttpz%connect_sse received non-HTTP requestz)connect_sse can only handle HTTP requestsFis_postzRequest validation failedzSetting up SSE connectionruserzCreated new session with ID: root_pathr$z ?session_id=c 2Ktjd4d{4d{jddd{tjd23d{}tjd|jd|jj dddd{Y777{7Z7 6dddd{7n#1d{7swYnxYwdddd{7y#1d{7swYyxYww) NzStarting SSE writerr)eventdatazSent endpoint event: zSending message via SSE: messageT)by_alias exclude_unset)r)r*r1r=model_dump_json)session_messageclient_post_uri_datasse_stream_writerwrite_stream_readers r- sse_writerz2SseServerTransport.connect_sse..sse_writers LL. /(  *=  ',,zK_-`aaa 45I4JKL-@/LL#<_DDCDCCC%C)C *C-A C8C 9C>DDCCCC DCDC- !C$"C- )D0 D;C><DDD DDr/r0r1cKt|||d{jd{jd{jd{tjdy7e7O797#w)zThe EventSourceResponse returning signals a client close / disconnect. In this case we close our side of the streams to signal the client that the connection has been closed. )contentdata_sender_callableNzClient session disconnected )r acloseloggingr*)r/r0r1read_stream_writer session_idsse_stream_readerrErDs r-response_wrapperz8SseServerTransport.connect_sse..response_wrappers j-6G^hiw-33555-44666+22444MM$@ "MN 664sCB BB BB  B B $B%B B B B zStarting SSE response taskzYielding read and write streams) r)errorr(r rvalidate_requestr*rr Exceptionrget isinstancerrrrrstriprrhexanyiocreate_memory_object_streamdictstrrcreate_task_groupr r r start_soonpop)r+r/r0r1requesterror_response read_stream write_streamr7r8full_message_path_for_clienttgrNrBrKrLrMrCrErDs @@@@@@@r- connect_ssezSseServerTransport.connect_sse{s =F " LL@ AHI I%)#~~>>wPU>VV  6 6 689 9 01*@R[A[*\]^*_'K,B>,RST,U) )W yy  d- ./DT/JD  ,0B!!*- 4ZLAB IIk2. (1'7'7'?"L11' 2 2*  % % ) )*d ;  $ $Z 6QW 6` 2 2 2 2 2*  % % ) )*d ;  $ $Z 6sAKI7K2I:3D:K:K=J?JJJ JJ:KKcKtjdt||}|jj |dd{}|r||||d{S|j j d}|4tjdtdd}||||d{S t| }tjd ||jj |} | s7tjd |tdd}||||d{S|j d} t| tr t| nd} | |jj |k7r5tjd|tdd}||||d{S|j!d{} tjd|  t"j$j'| d} tjd| t/|}t1| |}tjd|tdd}||||d{| j-|d{y787'7#t$r;tjd |td d}||||d{7cYSwxYw77,7#t($rY}tj+dtdd}||||d{7| j-|d{7Yd}~yd}~wwxYw77ŭw)NzHandling POST messageTr5rLz#Received request without session_idzsession_id is requiredi) status_code)rUzParsed session ID: zReceived invalid session ID: zInvalid session IDzCould not find session for ID: zCould not find sessionir7z;Rejecting message for session %s: credential does not matchzReceived JSON: F)by_namezValidated client message: zFailed to parse messagezCould not parse message)request_context)metadataz#Sending session message to writer: Accepted)r)r*r rrP query_paramsrRwarningr rr(rrSrrrbodytypesjsonrpc_message_adapter validate_jsonr exceptionr1rr)r+r/r0r1r]r^session_id_paramresponserLwriterr7 requestorrmr=errrhrAs r-handle_post_messagez&SseServerTransport.handle_post_messages ,-%) $~~>>wPT>UU 'w== ="//33LA  # NN@ A 8cJH!%$77 7 8"23J LL.zl; < **..z: NNI<L>I?AL>)J*L>/$JAL>"K #BL>#K$L>;K<L>9KA L>L:L>6L<7L>?L>L>;K KK L>K  L>L>L> L7/L2 LL2'L*(L2-L>2L77L><L>)N)__name__ __module__ __qualname____doc__rY__annotations__rXrrrrQrrrr&rr r rrcrw __classcell__)r,s@r-rr@sNt%6~ 7Q%RRSS$ 4455**'R'R9RUY9Y'Rei'RRO7uO7wO7dO7O7b9+u9+w9+d9+W[9+r.r)+r{rJ contextlibrtypingr urllib.parseruuidrrrV mcp_typesrnpydanticr sse_starletter starlette.requestsr starlette.responsesr starlette.typesr r r&mcp.server.auth.middleware.bearer_authrrrmcp.server.transport_securityrrmcp.shared._context_streamsrrmcp.shared.messagerr getLoggerrxr)rr.r-rsf$L* $-&(00qqRD   8 $F+F+r.