o
    h"*                     @   s   d Z ddlZddlmZ ddlmZ ddlmZ ddlm	Z	m
Z
 ddlZddlmZmZ ddlmZ dd	lmZ dd
lmZ ddlmZ ddlmZmZmZ ddlmZ ddlmZmZ ddl m!Z!m"Z" e#e$Z%G dd dZ&dS )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)UUIDuuid4)MemoryObjectReceiveStreamMemoryObjectSendStream)ValidationError)EventSourceResponse)Request)Response)ReceiveScopeSend)TransportSecurityMiddlewareTransportSecuritySettings)ServerMessageMetadataSessionMessagec                       s   e Zd ZU dZeed< eeee	e
B  f ed< eed< ddededB ddf fd	d
ZedededefddZdedededdfddZ  ZS )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	_securityNendpointsecurity_settingsreturnc                    sx   t    d|v s|dsd|v sd|v rtd| d|ds'd| }|| _i | _t|| _t	d|  d	S )
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//?#zGiven 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
ValueErrorr   r   r   r   loggerdebug)selfr   r   	__class__ O/var/www/html/openai_agents/venv/lib/python3.10/site-packages/mcp/server/sse.pyr   P   s   
"


zSseServerTransport.__init__scopereceivesendc              	     s  |d dkrt d tdt||}| jj|ddI d H }|r/||||I d H  tdt d td	\}td	\}t	 | j
< t d
  |dd}|d| j }	t|	 dj  tjtttf  d	\ fddt 4 I d H 4}
dtdtdtffdd}t d |
|||| t d ||fV  W d   I d H  d S 1 I d H sw   Y  d S )Ntypehttpz%connect_sse received non-HTTP requestz)connect_sse can only handle HTTP requestsFis_postzRequest validation failedzSetting up SSE connectionr   zCreated new session with ID: 	root_path r   z?session_id=c               
      s   t d 4 I d H ^ 4 I d H A d dI d H  t d   2 z3 d H W } t d|   d| jjddddI d H  q)6 W d   I d H  n1 I d H sZw   Y  W d   I d H  d S 1 I d H spw   Y  d S )	NzStarting SSE writerr   )eventdatazSent endpoint event: zSending message via SSE: messageT)by_aliasexclude_none)r"   r#   r+   r4   model_dump_json)session_message)client_post_uri_datasse_stream_writerwrite_stream_readerr'   r(   
sse_writer   s   
Xz2SseServerTransport.connect_sse.<locals>.sse_writerr)   r*   r+   c                    sL   t d| ||I dH    I dH   I dH  td  dS )z
                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.
                )contentdata_sender_callableNzClient session disconnected )r
   acloseloggingr#   )r)   r*   r+   )read_stream_writer
session_idsse_stream_readerr<   r;   r'   r(   response_wrapper   s   

z8SseServerTransport.connect_sse.<locals>.response_wrapperzStarting SSE response taskzYielding read and write streams)r"   errorr!   r   r   validate_requestr#   anyiocreate_memory_object_streamr   r   getrstripr   r   hexdictstrr   create_task_groupr   r   r   
start_soon)r$   r)   r*   r+   requesterror_responseread_streamwrite_streamr0   full_message_path_for_clienttgrD   r'   )r9   rA   rB   rC   r:   r<   r;   r(   connect_ssey   s6   



"

.zSseServerTransport.connect_ssec              
      s  t d t||}| jj|ddI d H }|r!||||I d H S |jd}|d u r?t d tddd}||||I d H S zt	|d	}t d
|  W n  t
yn   t d|  tddd}||||I d H  Y S w | j|}	|	st d|  tddd}||||I d H S | I d H }
t d|
  ztj|
}t d|  W n/ ty } z#t d tddd}||||I d H  |	|I d H  W Y d }~d S d }~ww t|d}t||d}t d|  tddd}||||I d H  |	|I d H  d S )NzHandling POST messageTr.   rB   z#Received request without session_idzsession_id is requiredi  )status_code)rK   zParsed session ID: zReceived invalid session ID: zInvalid session IDzCould not find session for ID: zCould not find sessioni  zReceived JSON: zValidated client message: zFailed to parse messagezCould not parse message)request_context)metadataz#Sending session message to writer: Accepted   )r"   r#   r   r   rF   query_paramsrI   warningr   r   r!   r   bodytypesJSONRPCMessagemodel_validate_jsonr	   	exceptionr+   r   r   )r$   r)   r*   r+   rP   rQ   session_id_paramresponserB   writerr^   r4   errrY   r8   r'   r'   r(   handle_post_message   sV   





z&SseServerTransport.handle_post_message)N)__name__
__module____qualname____doc__rM   __annotations__rL   r   r   r   	Exceptionr   r   r   r   r   r   r   rV   rg   __classcell__r'   r'   r%   r(   r   @   s   
  )"Or   )'rk   r@   
contextlibr   typingr   urllib.parser   uuidr   r   rG   anyio.streams.memoryr   r   pydanticr	   sse_starletter
   starlette.requestsr   starlette.responsesr   starlette.typesr   r   r   	mcp.typesr_   mcp.server.transport_securityr   r   mcp.shared.messager   r   	getLoggerrh   r"   r   r'   r'   r'   r(   <module>   s$    '
