Per-request proxy for server-to-client requests and notifications. Built once per inbound request by the kernel's `_make_context`. Holds two `Outbound` channels: the request-scoped one (the per-request `DispatchContext`, which on streamable HTTP routes onto the originating POST's re
| 25 | |
| 26 | |
| 27 | class ServerSession: |
| 28 | """Per-request proxy for server-to-client requests and notifications. |
| 29 | |
| 30 | Built once per inbound request by the kernel's `_make_context`. Holds two |
| 31 | `Outbound` channels: the request-scoped one (the per-request |
| 32 | `DispatchContext`, which on streamable HTTP routes onto the originating |
| 33 | POST's response stream) and the connection's standalone channel |
| 34 | (`connection.outbound`). `related_request_id` on the public methods is the |
| 35 | selector — present means request-scoped, absent means standalone — and |
| 36 | never crosses the `Outbound` Protocol. |
| 37 | """ |
| 38 | |
| 39 | def __init__(self, request_outbound: DispatchContext[Any], connection: Connection) -> None: |
| 40 | self._request_outbound = request_outbound |
| 41 | self._connection = connection |
| 42 | |
| 43 | @property |
| 44 | def client_params(self) -> types.InitializeRequestParams | None: |
| 45 | """The client's `initialize` request params; `None` when no client info was supplied.""" |
| 46 | return self._connection.client_params |
| 47 | |
| 48 | @property |
| 49 | def protocol_version(self) -> str: |
| 50 | """The protocol version this connection speaks. |
| 51 | |
| 52 | Populated at `Connection` construction and overwritten once the |
| 53 | handshake commits on the loop path; never `None`. |
| 54 | """ |
| 55 | return self._connection.protocol_version |
| 56 | |
| 57 | async def send_request( |
| 58 | self, |
| 59 | request: types.ServerRequest, |
| 60 | result_type: type[ResultT], |
| 61 | request_read_timeout_seconds: float | None = None, |
| 62 | metadata: ServerMessageMetadata | None = None, |
| 63 | progress_callback: ProgressFnT | None = None, |
| 64 | ) -> ResultT: |
| 65 | """Send a typed server-to-client request and validate the result. |
| 66 | |
| 67 | Raises: |
| 68 | MCPError: The peer responded with an error. |
| 69 | NoBackChannelError: The connection has no back-channel for |
| 70 | server-initiated requests (raised by the held `Outbound`). |
| 71 | pydantic.ValidationError: The peer's result does not match `result_type`. |
| 72 | """ |
| 73 | related = metadata.related_request_id if metadata is not None else None |
| 74 | channel = self._request_outbound if related is not None else self._connection.outbound |
| 75 | data = request.model_dump(by_alias=True, mode="json", exclude_none=True) |
| 76 | opts: CallOptions = {} |
| 77 | if request_read_timeout_seconds is not None: |
| 78 | opts["timeout"] = request_read_timeout_seconds |
| 79 | if progress_callback is not None: |
| 80 | opts["on_progress"] = progress_callback |
| 81 | result = await channel.send_raw_request(data["method"], data.get("params"), opts or None) |
| 82 | try: |
| 83 | _methods.validate_client_result(request.method, self.protocol_version, result) |
| 84 | except KeyError: |
no outgoing calls