MIT-licensed Rust framework for multi-agent systems in Rust A new MIT-licensed Rust framework, a2a-rs, implements the Agent2Agent (A2A) protocol end to end, letting developers write an agent as a single trait and serve it over ConnectRPC, JSON-RPC 2.0, and HTTP+JSON from the same agent. The framework supports MCP 2026-07-28 and 2025-11-25, exposes agents as MCP tools or hands them an MCP server's tools, and ships SQLite or PostgreSQL storage, JWT, OAuth2 and OpenID Connect auth, retries and tracing behind individual feature flags. Every crate is MIT licensed, and no adapter is load-bearing, so developers can embed it in a commercial product or build their own agent platform on top. 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.