SSE 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 mes
| 62 | |
| 63 | |
| 64 | class SseServerTransport: |
| 65 | """SSE server transport for MCP. This class provides two ASGI applications, |
| 66 | suitable for use with a framework like Starlette and a server like Hypercorn: |
| 67 | |
| 68 | 1. connect_sse() is an ASGI application which receives incoming GET requests, |
| 69 | and sets up a new SSE stream to send server messages to the client. |
| 70 | 2. handle_post_message() is an ASGI application which receives incoming POST |
| 71 | requests, which should contain client messages that link to a |
| 72 | previously-established SSE session. |
| 73 | """ |
| 74 | |
| 75 | _endpoint: str |
| 76 | _read_stream_writers: dict[UUID, ContextSendStream[SessionMessage | Exception]] |
| 77 | # Identity of the credential that created each session; requests for a |
| 78 | # session must present the same credential. |
| 79 | _session_owners: dict[UUID, AuthorizationContext] |
| 80 | _security: TransportSecurityMiddleware |
| 81 | |
| 82 | def __init__(self, endpoint: str, security_settings: TransportSecuritySettings | None = None) -> None: |
| 83 | """Creates a new SSE server transport, which will direct the client to POST |
| 84 | messages to the relative path given. |
| 85 | |
| 86 | Args: |
| 87 | endpoint: A relative path where messages should be posted |
| 88 | (e.g., "/messages/"). |
| 89 | security_settings: Optional security settings for DNS rebinding protection. |
| 90 | |
| 91 | Note: |
| 92 | We use relative paths instead of full URLs for several reasons: |
| 93 | 1. Security: Prevents cross-origin requests by ensuring clients only connect |
| 94 | to the same origin they established the SSE connection with |
| 95 | 2. Flexibility: The server can be mounted at any path without needing to |
| 96 | know its full URL |
| 97 | 3. Portability: The same endpoint configuration works across different |
| 98 | environments (development, staging, production) |
| 99 | |
| 100 | Raises: |
| 101 | ValueError: If the endpoint is a full URL instead of a relative path |
| 102 | """ |
| 103 | |
| 104 | super().__init__() |
| 105 | |
| 106 | # Validate that endpoint is a relative path and not a full URL |
| 107 | if "://" in endpoint or endpoint.startswith("//") or "?" in endpoint or "#" in endpoint: |
| 108 | raise ValueError( |
| 109 | f"Given endpoint: {endpoint} is not a relative path (e.g., '/messages/'), " |
| 110 | "expecting a relative path (e.g., '/messages/')." |
| 111 | ) |
| 112 | |
| 113 | # Ensure endpoint starts with a forward slash |
| 114 | if not endpoint.startswith("/"): |
| 115 | endpoint = "/" + endpoint |
| 116 | |
| 117 | self._endpoint = endpoint |
| 118 | self._read_stream_writers = {} |
| 119 | self._session_owners = {} |
| 120 | self._security = TransportSecurityMiddleware(security_settings) |
| 121 | logger.debug(f"SseServerTransport initialized with endpoint: {endpoint}") |
no outgoing calls