pub trait HandleDispatchFrom<Counterpart: Role>: Send {
// Required methods
fn handle_dispatch_from(
&mut self,
message: Dispatch,
connection: ConnectionTo<Counterpart>,
) -> impl Future<Output = Result<Handled<Dispatch>, Error>> + Send;
fn describe_chain(&self) -> impl Debug;
}Expand description
Handlers process incoming JSON-RPC messages on a connection.
When messages arrive, they flow through a chain of handlers. Each handler can either claim the message (handle it) or decline it (pass to the next handler).
§Message Flow
Messages flow through three layers of handlers in order:
┌─────────────────────────────────────────────────────────────────┐
│ Incoming Message │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 1. User Handlers (registered via on_receive_request, etc.) │
│ - Tried in registration order │
│ - First handler to return Handled::Yes claims the message │
└─────────────────────────────────────────────────────────────────┘
│ Handled::No
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. Dynamic Handlers (added at runtime) │
│ - Used for session-specific message handling │
│ - Added via ConnectionTo::add_dynamic_handler │
└─────────────────────────────────────────────────────────────────┘
│ Handled::No
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. Role Default Handler │
│ - Fallback based on the connection's Role │
│ - Handles protocol-level messages (e.g., proxy forwarding) │
└─────────────────────────────────────────────────────────────────┘
│ Handled::No
▼
┌─────────────────────────────────────────────────────────────────┐
│ Unhandled: Error response sent (or queued if retry=true) │
└─────────────────────────────────────────────────────────────────┘§The Handled Return Value
Each handler returns Handled to indicate whether it processed the message:
Handled::Yes- Message was handled. No further handlers are invoked.Handled::No { message, retry }- Message was not handled. The message (possibly modified) is passed to the next handler in the chain.
For convenience, handlers can return () which is equivalent to Handled::Yes.
§The Retry Mechanism
The retry flag in Handled::No controls what happens when no handler claims a message:
retry: false(default) - Send a “method not found” error response immediately.retry: true- Queue the message and retry it when new dynamic handlers are added.
This mechanism exists because of a timing issue with sessions: when a session/new
response is being processed, the dynamic handler for that session hasn’t been registered
yet, but session/update notifications for that session may already be arriving.
By setting retry: true, these early notifications are queued until the session’s
dynamic handler is added.
§Handler Registration
Most users register handlers using the builder methods on Builder:
Agent.builder()
.on_receive_request(async |req: InitializeRequest, responder, cx| {
responder.respond(
InitializeResponse::new(req.protocol_version)
.agent_capabilities(AgentCapabilities::new()),
)
}, agent_client_protocol::on_receive_request!())
.on_receive_notification(async |notif: StatusUpdate, cx| {
// Process notification
Ok(())
}, agent_client_protocol::on_receive_notification!())
.connect_to(transport)
.await?;The type parameter on the closure determines which messages are dispatched to it. Messages that don’t match the type are automatically passed to the next handler.
§Implementing Custom Handlers
For advanced use cases, you can implement HandleMessageAs directly:
struct MyHandler;
impl HandleMessageAs<Agent> for MyHandler {
async fn handle_dispatch(
&mut self,
message: Dispatch,
cx: ConnectionTo<Self::Role>,
) -> Result<Handled<Dispatch>, Error> {
if message.method() == "my/custom/method" {
// Handle it
Ok(Handled::Yes)
} else {
// Pass to next handler
Ok(Handled::No { message, retry: false })
}
}
fn describe_chain(&self) -> impl std::fmt::Debug {
"MyHandler"
}
}§Important: Handlers Must Not Block
The connection processes messages on a single async task. While a handler is running,
no other messages can be processed. For expensive operations, use ConnectionTo::spawn
to run work concurrently:
cx.spawn({
let connection = cx.clone();
async move {
let result = expensive_operation("data").await?;
connection.send_notification(ProcessComplete { result })?;
Ok(())
}
})?;A handler for incoming JSON-RPC messages.
This trait is implemented by types that can process incoming messages on a connection.
Handlers are registered with a Builder and are called in order until
one claims the message.
The type parameter R is the role this handler plays - who I am.
For an agent handler, R = Agent (I handle messages as an agent).
For a client handler, R = Client (I handle messages as a client).
Required Methods§
Sourcefn handle_dispatch_from(
&mut self,
message: Dispatch,
connection: ConnectionTo<Counterpart>,
) -> impl Future<Output = Result<Handled<Dispatch>, Error>> + Send
fn handle_dispatch_from( &mut self, message: Dispatch, connection: ConnectionTo<Counterpart>, ) -> impl Future<Output = Result<Handled<Dispatch>, Error>> + Send
Attempt to claim an incoming message (request or notification).
§Important: do not block
The server will not process new messages until this handler returns.
You should avoid blocking in this callback unless you wish to block the server (e.g., for rate limiting).
The recommended approach to manage expensive operations is to the ConnectionTo::spawn method available on the message context.
§Parameters
message- The incoming message to handle.connection- The connection, used to send messages and access connection state.
§Returns
Ok(Handled::Yes)if the message was claimed. It will not be propagated further.Ok(Handled::No(message))if not; the (possibly changed) message will be passed to the remaining handlers.Errif an internal error occurs (this will bring down the server).
Sourcefn describe_chain(&self) -> impl Debug
fn describe_chain(&self) -> impl Debug
Returns a debug description of the registered handlers for diagnostics.
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.