Skip to main content

HandleDispatchFrom

Trait HandleDispatchFrom 

Source
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§

Source

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.
  • Err if an internal error occurs (this will bring down the server).
Source

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.

Implementations on Foreign Types§

Source§

impl<Counterpart: Role, H> HandleDispatchFrom<Counterpart> for &mut H
where H: HandleDispatchFrom<Counterpart>,

Source§

fn handle_dispatch_from( &mut self, message: Dispatch, cx: ConnectionTo<Counterpart>, ) -> impl Future<Output = Result<Handled<Dispatch>, Error>> + Send

Source§

fn describe_chain(&self) -> impl Debug

Implementors§

Source§

impl<Counterpart: Role> HandleDispatchFrom<Counterpart> for NullHandler