22
33A Dispatcher turns a duplex message channel into two things:
44
5- * an outbound API: ``call (method, params)`` and ``notify(method, params)``
6- * an inbound pump: ``run(on_call , on_notify)`` that drives the receive loop and
7- invokes the supplied handlers for each incoming request/notification
5+ * an outbound API: ``send_request (method, params)`` and ``notify(method, params)``
6+ * an inbound pump: ``run(on_request , on_notify)`` that drives the receive loop
7+ and invokes the supplied handlers for each incoming request/notification
88
99It is deliberately *not* MCP-aware. Method names are strings, params and
1010results are ``dict[str, Any]``. The MCP type layer (request/result models,
2828 "DispatchContext" ,
2929 "DispatchMiddleware" ,
3030 "Dispatcher" ,
31- "OnCall" ,
3231 "OnNotify" ,
32+ "OnRequest" ,
33+ "Outbound" ,
3334 "ProgressFnT" ,
34- "RequestSender" ,
3535]
3636
3737TransportT_co = TypeVar ("TransportT_co" , bound = TransportContext , covariant = True )
3838
3939
4040class ProgressFnT (Protocol ):
41- """Callback invoked when a progress notification arrives for a pending call ."""
41+ """Callback invoked when a progress notification arrives for a pending request ."""
4242
4343 async def __call__ (self , progress : float , total : float | None , message : str | None ) -> None : ...
4444
4545
4646class CallOptions (TypedDict , total = False ):
47- """Per-call options for `RequestSender .send_request` / `Dispatcher.call `.
47+ """Per-call options for `Outbound .send_request`.
4848
4949 All keys are optional. Dispatchers ignore keys they do not understand.
5050 """
@@ -53,37 +53,51 @@ class CallOptions(TypedDict, total=False):
5353 """Seconds to wait for a result before raising and sending ``notifications/cancelled``."""
5454
5555 on_progress : ProgressFnT
56- """Receive ``notifications/progress`` updates for this call ."""
56+ """Receive ``notifications/progress`` updates for this request ."""
5757
5858 resumption_token : str
59- """Opaque token to resume a previously interrupted call (transport-dependent)."""
59+ """Opaque token to resume a previously interrupted request (transport-dependent)."""
6060
6161 on_resumption_token : Callable [[str ], Awaitable [None ]]
6262 """Receive a resumption token when the transport issues one."""
6363
6464
6565@runtime_checkable
66- class RequestSender (Protocol ):
67- """Anything that can send a request and await its result .
66+ class Outbound (Protocol ):
67+ """Anything that can send requests and notifications to the peer .
6868
69- `DispatchContext` satisfies this; `PeerMixin` (and `Connection`/`Peer`) wrap
70- a `RequestSender` to provide typed request methods.
69+ Both `Dispatcher` (top-level outbound) and `DispatchContext` (back-channel
70+ during an inbound request) extend this. `PeerMixin` wraps an `Outbound` to
71+ provide typed MCP request/notification methods.
7172 """
7273
7374 async def send_request (
7475 self ,
7576 method : str ,
7677 params : Mapping [str , Any ] | None ,
7778 opts : CallOptions | None = None ,
78- ) -> dict [str , Any ]: ...
79+ ) -> dict [str , Any ]:
80+ """Send a request and await its result.
81+
82+ Raises:
83+ MCPError: If the peer responded with an error, or the handler
84+ raised. Implementations normalize all handler exceptions to
85+ `MCPError` so callers see a single exception type.
86+ """
87+ ...
7988
89+ async def notify (self , method : str , params : Mapping [str , Any ] | None ) -> None :
90+ """Send a fire-and-forget notification."""
91+ ...
8092
81- class DispatchContext (Protocol [TransportT_co ]):
82- """Per-request context handed to ``on_call`` / ``on_notify``.
93+
94+ class DispatchContext (Outbound , Protocol [TransportT_co ]):
95+ """Per-request context handed to ``on_request`` / ``on_notify``.
8396
8497 Carries the transport metadata for the inbound message and provides the
8598 back-channel for sending requests/notifications to the peer while handling
86- it.
99+ it. `send_request` raises `NoBackChannelError` if
100+ ``transport.can_send_request`` is ``False``.
87101 """
88102
89103 @property
@@ -96,23 +110,6 @@ def cancel_requested(self) -> anyio.Event:
96110 """Set when the peer sends ``notifications/cancelled`` for this request."""
97111 ...
98112
99- async def notify (self , method : str , params : Mapping [str , Any ] | None ) -> None :
100- """Send a notification to the peer."""
101- ...
102-
103- async def send_request (
104- self ,
105- method : str ,
106- params : Mapping [str , Any ] | None ,
107- opts : CallOptions | None = None ,
108- ) -> dict [str , Any ]:
109- """Send a request to the peer on the back-channel and await its result.
110-
111- Raises:
112- NoBackChannelError: if ``transport.can_send_request`` is ``False``.
113- """
114- ...
115-
116113 async def progress (self , progress : float , total : float | None = None , message : str | None = None ) -> None :
117114 """Report progress for the inbound request, if the peer supplied a progress token.
118115
@@ -121,47 +118,28 @@ async def progress(self, progress: float, total: float | None = None, message: s
121118 ...
122119
123120
124- OnCall = Callable [[DispatchContext [TransportContext ], str , Mapping [str , Any ] | None ], Awaitable [dict [str , Any ]]]
121+ OnRequest = Callable [[DispatchContext [TransportContext ], str , Mapping [str , Any ] | None ], Awaitable [dict [str , Any ]]]
125122"""Handler for inbound requests: ``(ctx, method, params) -> result``. Raise ``MCPError`` to send an error response."""
126123
127124OnNotify = Callable [[DispatchContext [TransportContext ], str , Mapping [str , Any ] | None ], Awaitable [None ]]
128125"""Handler for inbound notifications: ``(ctx, method, params)``."""
129126
130- DispatchMiddleware = Callable [[OnCall ], OnCall ]
131- """Wraps an ``OnCall `` to produce another ``OnCall ``. Applied outermost-first."""
127+ DispatchMiddleware = Callable [[OnRequest ], OnRequest ]
128+ """Wraps an ``OnRequest `` to produce another ``OnRequest ``. Applied outermost-first."""
132129
133130
134- class Dispatcher (Protocol [TransportT_co ]):
131+ class Dispatcher (Outbound , Protocol [TransportT_co ]):
135132 """A duplex request/notification channel with call-return semantics.
136133
137- Implementations own correlation of outbound calls to inbound results, the
134+ Implementations own correlation of outbound requests to inbound results, the
138135 receive loop, per-request concurrency, and cancellation/progress wiring.
139136 """
140137
141- async def call (
142- self ,
143- method : str ,
144- params : Mapping [str , Any ] | None ,
145- opts : CallOptions | None = None ,
146- ) -> dict [str , Any ]:
147- """Send a request and await its result.
148-
149- Raises:
150- MCPError: If the peer responded with an error, or the handler
151- raised. Implementations normalize all handler exceptions to
152- `MCPError` so callers see a single exception type.
153- """
154- ...
155-
156- async def notify (self , method : str , params : Mapping [str , Any ] | None ) -> None :
157- """Send a fire-and-forget notification."""
158- ...
159-
160- async def run (self , on_call : OnCall , on_notify : OnNotify ) -> None :
138+ async def run (self , on_request : OnRequest , on_notify : OnNotify ) -> None :
161139 """Drive the receive loop until the underlying channel closes.
162140
163- Each inbound request is dispatched to ``on_call `` in its own task; the
164- returned dict (or raised ``MCPError``) is sent back as the response.
141+ Each inbound request is dispatched to ``on_request `` in its own task;
142+ the returned dict (or raised ``MCPError``) is sent back as the response.
165143 Inbound notifications go to ``on_notify``.
166144 """
167145 ...
0 commit comments