# MIT-licensed Rust framework for multi-agent systems in Rust

> Source: <https://emillindfors.github.io/a2a-rs/>
> Published: 2026-10-15 15:14:50+00:00

a2a-rs is a framework for agents that work together. Write an agent as one trait, serve it over the transport its peers speak, and reach MCP tools and LLMs through the same ports. It implements the Agent2Agent protocol end to end, and the MIT license means you can build your product on it.

`cargo add a2a-rs`
The protocol, the bridges, and the plumbing a multi-agent system needs, with every piece of infrastructure behind a port you can replace.

Messages, tasks, artifacts, push notifications, extended agent cards and card signatures, and every task state. The few divergences are opt-in, backward-compatible, and documented.

MCP 2026-07-28, and 2025-11-25 for peers that still initialize. Expose an agent as MCP tools, or hand an agent an MCP server's tools. A slow call becomes an MCP task; a question asked mid-call reaches the agent.

Serve ConnectRPC, JSON-RPC 2.0 and HTTP+JSON from the same agent. Clients negotiate from the agent card, and the spec bindings are crossed against the official A2A Rust SDK in both directions.

Agent cards to discover what a peer can do, contexts that carry a
          conversation across tasks, `input-required` hand-offs,
          streams that resume without gaps, and push webhooks.

SQLite or PostgreSQL storage, JWT, OAuth2 and OpenID Connect auth, retries and tracing. Each sits behind its own feature flag, so you compile only what you use.

Every crate here is MIT licensed: embed it in a commercial product, fork it, or build your own agent platform on top. No adapter is load-bearing, so nothing locks you in.

      Implement `Responder`: given the message and the task, return a
      reply and the state the task ends in. `ResponderMessageHandler`
      creates the task, keeps its history, and announces every transition to
      stream subscribers and push webhooks.
    

```
use a2a_rs::adapter::business::Responder;
use a2a_rs::domain::{A2AError, Message, Part, Role, Task, TaskState};

/// Reply with the message in capitals, and finish the task.
struct Shout;

#[async_trait::async_trait]
impl Responder for Shout {
    async fn respond(
        &self,
        message: &Message,
        task: &Task,
    ) -> Result<(Message, TaskState), A2AError> {
        let text: Vec<&str> = message.parts.iter().filter_map(|p| p.get_text()).collect();
        let reply = Message::builder()
            .role(Role::Agent)
            .parts(vec![Part::text(text.join(" ").to_uppercase())])
            .message_id(uuid::Uuid::new_v4().to_string())
            .task_id(task.id.clone())
            .context_id(message.context_id.clone())
            .build();
        Ok((reply, TaskState::Completed))
    }
}
use a2a_rs::adapter::business::ResponderMessageHandler;
use a2a_rs::adapter::{
    ConnectRpcAdapter, HttpServer, InMemoryStreamingHandler, InMemoryTaskStorage,
    SimpleAgentInfo,
};

// Storage, streaming and push delivery are ports; these are the in-memory adapters.
let storage = InMemoryTaskStorage::new();
let streaming = InMemoryStreamingHandler::new();
let handler = ResponderMessageHandler::new(
    storage.clone(),
    streaming.clone(),
    storage.push_notifier(),
    Shout,
);

let card = SimpleAgentInfo::new("shout".to_string(), "http://127.0.0.1:8080".to_string())
    .with_description("Repeats what you say, louder".to_string())
    .with_streaming();
let processor = ConnectRpcAdapter::new(handler, storage.clone(), storage, card.clone())
    .with_streaming_handler(streaming);

HttpServer::new(processor, card, "127.0.0.1:8080".to_string())
    .start()
    .await?;
use a2a_rs::{HttpClient, Message, Transport};
use a2a_rs::domain::SendCompletion;

let client = HttpClient::new("http://127.0.0.1:8080".to_string());
let message = Message::user_text("hello, agent".to_string(), uuid::Uuid::new_v4().to_string());

// `WhenSettled` is the A2A default: the server answers once the task has
// finished, so the reply is already in `task.history`.
let task = client
    .send_task_message(None, &message, None, None, SendCompletion::WhenSettled)
    .await?;

println!("task {} is {:?}", task.id, task.status.state);
// task 7725e89c-… is TASK_STATE_COMPLETED
cargo install a2acli

export A2A_URL=http://127.0.0.1:8080
a2acli card                           # what can this agent do?
a2acli send "hello, agent"            # send, and wait for the reply
a2acli list --state input-required    # tasks waiting on you
a2acli stream t1 --resilient          # follow a task, reconnecting on drop
[dependencies]
a2a-rs = { version = "0.10", features = ["http-server", "http-client"] }

# Or pick other features:
#   jsonrpc-server / jsonrpc-client   the spec's JSON-RPC 2.0 and HTTP+JSON bindings
#   auth                              JWT, OAuth2, OpenID Connect
#   sqlite / postgres                 SQLx task storage
#   full                              all of the above
```

      All of it runs as one program:
      `cargo run -p a2a-rs --example quickstart --features http-server,http-client`.
      An agent that acknowledges now and finishes later implements
      `AsyncMessageHandler` instead.
    

The protocol crate stands alone; the others extend it. Each is published to crates.io and released independently.

The protocol: domain types, ports, the ConnectRPC, JSON-RPC 2.0 and HTTP+JSON transports, auth, and in-memory and SQLx storage.

A command-line client that negotiates its transport from the agent card. Prebuilt for Linux, macOS and Windows; doubles as a cross-SDK interop harness.

A2A ↔ MCP in both directions, on MCP 2026-07-28 and 2025-11-25: expose an agent as MCP tools, or route an agent's tool calls to an MCP server. A slow call becomes an MCP task; a mid-call question reaches the A2A side.

Provider-neutral chat completions for OpenAI-compatible endpoints and Gemini, and decision models that answer typed questions with probabilities instead of text.

The Agent Payments Protocol: typed, validated mandates, payment requests and receipts, and helpers to carry them in A2A messages and agent cards.

      The A2A v1.0 surface, and how it reaches the wire. The full list of
      divergences is in the
      [spec compliance notes](https://github.com/EmilLindfors/a2a-rs/tree/master/a2a-rs#spec-compliance).
    

`SendMessage` · `SendStreamingMessage` Blocking until the task settles, or returning immediately`GetTask` · `ListTasks` Filtered by state and context, and paged`CancelTask` · `SubscribeToTask` With gap-free resume between a2a-rs peers`GetExtendedAgentCard` Plus agent card signatures
| Binding | Server | Client | 
|---|---|---|
| JSON-RPC 2.0 (spec) | ✓ | ✓ | 
| HTTP+JSON (spec) | ✓ | — | 
| ConnectRPC (a2a-rs default) | ✓ | ✓ | 
| gRPC (spec) | — | — | 

Clients negotiate from the agent card's supported interfaces: ConnectRPC when the agent offers it, JSON-RPC otherwise. Crossed against the official A2A Rust SDK's CLI and server over JSON-RPC and HTTP+JSON.

      Transports decode a request and hand it to `TaskService`, which
      only talks to ports. You implement the message handler; everything else
      has an in-tree adapter you can keep or replace.
    

a2a-rs is MIT licensed and published on crates.io, so it's free to use in commercial products and to build your own platform on. Issues and pull requests are welcome.
