|
6 | 6 | from functools import reduce |
7 | 7 | from operator import or_ |
8 | 8 | from types import TracebackType |
9 | | -from typing import Annotated, Any, Final, Literal, Protocol, cast, overload |
| 9 | +from typing import Annotated, Any, Final, Literal, Protocol, TypeAlias, cast, overload |
10 | 10 |
|
11 | 11 | import anyio |
12 | 12 | import anyio.abc |
|
53 | 53 | ) |
54 | 54 | from mcp.shared.jsonrpc_dispatcher import JSONRPCDispatcher, cancelled_request_id_from_params |
55 | 55 | from mcp.shared.message import ClientMessageMetadata, SessionMessage |
56 | | -from mcp.shared.session import RequestResponder |
57 | 56 | from mcp.shared.subscriptions import SUBSCRIPTION_ID_META_KEY, event_from_wire |
58 | 57 | from mcp.shared.transport_context import TransportContext |
59 | 58 |
|
@@ -172,16 +171,20 @@ class LoggingFnT(Protocol): |
172 | 171 | async def __call__(self, params: types.LoggingMessageNotificationParams) -> None: ... # pragma: no branch |
173 | 172 |
|
174 | 173 |
|
| 174 | +IncomingMessage: TypeAlias = types.ServerNotification | Exception |
| 175 | +"""What `message_handler` receives: the server notifications the session surfaces, plus transport-level exceptions. |
| 176 | +
|
| 177 | +`notifications/cancelled` is applied by the dispatcher and never surfaced, and a |
| 178 | +`notifications/subscriptions/acknowledged` for a live `listen()` stream is consumed by that |
| 179 | +stream, so neither reaches the handler. |
| 180 | +""" |
| 181 | + |
| 182 | + |
175 | 183 | class MessageHandlerFnT(Protocol): |
176 | | - async def __call__( |
177 | | - self, |
178 | | - message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception, |
179 | | - ) -> None: ... # pragma: no branch |
| 184 | + async def __call__(self, message: IncomingMessage) -> None: ... # pragma: no branch |
180 | 185 |
|
181 | 186 |
|
182 | | -async def _default_message_handler( |
183 | | - message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception, |
184 | | -) -> None: |
| 187 | +async def _default_message_handler(message: IncomingMessage) -> None: |
185 | 188 | await anyio.lowlevel.checkpoint() |
186 | 189 |
|
187 | 190 |
|
@@ -331,8 +334,10 @@ class ClientSession: |
331 | 334 | `dispatcher=`), enter as an async context manager, then call |
332 | 335 | `initialize()`. The dispatcher owns the receive loop and request |
333 | 336 | correlation; this class owns the typed MCP layer and the constructor |
334 | | - callbacks. Transport `Exception` items reach `message_handler` only when |
335 | | - the session builds its own dispatcher from a stream pair. |
| 337 | + callbacks. Transport `Exception` items reach `message_handler` on any |
| 338 | + stream-backed dispatcher (`JSONRPCDispatcher`), whether built here from a |
| 339 | + stream pair or supplied without a stream-exception hook of its own; an |
| 340 | + in-process `DirectDispatcher` carries none. |
336 | 341 |
|
337 | 342 | Extension `result_claims` fold into tools/call parsing at `adopt()`; |
338 | 343 | `notification_bindings` observe vendor notifications via bounded FIFOs. |
|
0 commit comments