Xj&0dZddlZddlmZddlmZddlmZddlm Z m Z ddl Z ddl m Z mZddlmZdd lmZdd lmZdd lmZdd lmZmZmZddlmZdd lmZmZm Z ddl!m"Z"m#Z#ddl$m%Z%m&Z&ejNe(Z)GddZ*y)a SSE Server Transport Module This module implements a Server-Sent Events (SSE) transport layer for MCP servers. Example usage: ``` # 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)MemoryObjectReceiveStreamMemoryObjectSendStream)ValidationError)EventSourceResponse)Request)Response)ReceiveScopeSend)AuthenticatedUserAuthorizationContextauthorization_context)TransportSecurityMiddlewareTransportSecuritySettings)ServerMessageMetadataSessionMessageceZdZUdZeed<eeee e zfed<eee fed<e ed<ddede dzd dffd Zed ed ed efdZd ed ed ed dfdZxZS)SseServerTransporta SSE server transport for MCP. This class provides _two_ ASGI applications, suitable to be used 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 ) a Creates 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/vault-secondbrain/venv/lib/python3.12/site-packages/mcp/server/sse.pyr&zSseServerTransport.__init__Ts.  H  3 3D 9SH_PSW_P_"8*-BB  ""3'X~H!$&!!45FG EhZPQscopereceivesendc K|ddk7r tjdtdt||}|jj |dd{}|r||||d{tdtj dtjd \}tjd \}t|jd }t|trt||j<|j<tj d |jd d } | j!d|j"z} t%| dj& tjt(t*t,fd \ fd tj.4d{} dt0dt2dt4ffd } tj d| j7| |||tj d||fdddd{|jj9d|jj9dy7777E#1d{7swYUxYw#|jj9d|jj9dwxYww)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_none)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)a The 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*anyiocreate_memory_object_streamrget isinstancerrrrrstriprrhexdictstrrcreate_task_grouprrr 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+0*K*KA*N'K,1,M,Ma,P) )W yy  d- ./DT/JD  ,0B!!*- 4ZLAB IIk2. (1'7'7'?"L11) 2 22  % % ) )*d ;  $ $Z 6cW 6j 2 2 2 2 22  % % ) )*d ;  $ $Z 6sAKI6K2I93D;K/JI<J A"J- J8I>9J=:K9K<J>JJJ JJ:KKc|Ktjdt||}|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'| } tjd| t/|}t1| |}tjd|tdd}||||d{| j-|d{y767%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)rVzParsed 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: zValidated client message: zFailed to parse messagezCould not parse message)request_context)metadataz#Sending session message to writer: Accepted)r)r*r rrP query_paramsrSwarningr rr(rrTrrrbodytypesJSONRPCMessagemodel_validate_jsonr exceptionr1rr)r+r/r0r1r\r]session_id_paramresponserLwriterr7 requestorrkr=errrfrAs r-handle_post_messagez&SseServerTransport.handle_post_messages ,-%) $~~>>wPT>UU 'w== ="//33LA  # NN@ A 8cJH!%$77 7 8"23J LL.zl; < **..z: NN>tDG LL5gY? @)A(8D :?:KLMJC8ugt,,,kk/***iV= 8  8 NN:;K:LM N 4#FH!%$777 7 888#     6 7 9sKH5'40 0 0++c" " "    -*sAL<I:L<I=AL<)J*L</$JAL<"K #BL<#K $L<;K<L<7KA L<L8L<4L:5L<=L<L<;K>K?KL<KL< L<L< L5/L0 L L0%L(&L0+L<0L55L<:L<)N)__name__ __module__ __qualname____doc__rX__annotations__rWrr r Exceptionrrrr&rrrrrbru __classcell__)r,s@r-rrAs Nt%;NYrsf%N* R$-&(00qqE   8 $Q+Q+r.