A2A Agents
Example agent implementations for the A2A Protocol with production-ready patterns and declarative configuration.
π Quick Start (New Builder API)
Create a production-ready agent in just ~30 lines of code instead of ~300!
1. Define your agent (agent.toml)
[]
= "My Agent"
= "A helpful agent"
[[]]
= "my_skill"
= "My Skill"
= "What this skill does"
2. Implement your handler
use AsyncMessageHandler;
use async_trait;
;
3. Build and run!
use AgentBuilder;
use InMemoryTaskStorage;
async
That's it! The framework handles servers, agent cards, authentication, and more.
π See complete Builder API documentation β
Overview
This crate provides two approaches for building agents:
β¨ New: Declarative Builder API (Recommended)
- 90% less boilerplate - ~30 lines vs ~300 lines
- TOML configuration - Define agents declaratively
- Environment-aware - Built-in env var interpolation
- Type-safe - Configuration validated at load time
- Production-ready - Batteries included
Examples:
examples/minimal_agent.rs- Echo agent (~50 lines)examples/reimbursement_builder.rs- Full-featured agent
Traditional Approach
The original hexagonal architecture approach with manual wiring:
- Hexagonal Architecture: Clean separation between domain logic and adapters
- Framework Integration: Uses
DefaultRequestProcessorand storage backends - Protocol Compliance: Full A2A protocol support with HTTP transport
- Modern Patterns: Async/await, builder patterns, and structured error handling
π Model Context Protocol (MCP) Integration
You can expose any declarative A2A Agent as a Model Context Protocol (MCP) server over stdio (for local clients like Claude Desktop) or Streamable HTTP (for networked clients) transport. Either way, MCP-compatible clients can invoke the agent's skills as tools.
The bridge dispatches tool calls to the agent's message handler in-process, which means:
- No backing HTTP server is required (you can set
http_port = 0for a pure-stdio server). - Authentication checks are bypassed for local stdio calls (secure by design as it is run locally by the client), while HTTP endpoints can still use standard Bearer/OAuth2 token auth.
1. Enable the MCP Server in agent.toml
Add the [features.mcp_server] section to your config:
[]
= "My MCP Agent"
= "1.0.0"
[]
= "127.0.0.1"
= 0 # Can be 0 for pure-stdio mode
[]
= true
= true
= "Custom MCP Service Name" # Optional override
= "2.0.0" # Optional override
2. Run the MCP Agent
Compile and run your agent with the mcp-server Cargo feature enabled:
3. Claude Desktop Configuration
To connect Claude Desktop to your agent, add the following to your Claude Desktop configuration file (usually located at %APPDATA%\Claude\claude_desktop_config.json on Windows):
4. Streamable HTTP transport
For networked MCP clients, serve the agent over MCP's Streamable HTTP transport
instead of stdio. Add a [features.mcp_server.http] section β when enabled,
it takes precedence over stdio:
[]
= true
= false
[]
= true
= "127.0.0.1" # default
= 8000 # default
= "/mcp" # default mount path
The server then accepts MCP requests at http://127.0.0.1:8000/mcp.
DNS-rebinding protection. By default the transport only accepts inbound
Host headers for loopback (localhost, 127.0.0.1, ::1). For a public
bind, list the hostnames you serve under β and optionally restrict browser
origins:
[]
= true
= "0.0.0.0"
= 8000
= ["mcp.example.com", "mcp.example.com:8000"]
= ["https://app.example.com"] # omit to disable Origin checks
Setting allowed_hosts = [] disables Host validation entirely (accepts any
host) β only do this behind a trusted reverse proxy.
5. MCP client (consume external MCP tools)
The other direction: let your agent call out to MCP servers and use their
tools while it serves A2A requests. Enable the mcp-client Cargo feature and
declare the servers to connect to under [features.mcp_client]. Each server is
spawned as a child process:
[]
= true
[[]]
= "echo"
= "cargo"
= ["run", "-q", "-p", "a2a-agents", "--features", "mcp-client", "--bin", "mcp_echo_server"]
# `env = { KEY = "value" }` and `cwd = "β¦"` are also supported.
In code, connect the config-declared servers into an McpClientManager and
hand it to the handler that will use the tools. The handler owns the manager and
reaches tools through the McpToolsExt trait:
use ;
use ;
// inside process_message:
// let result = self.call_mcp_tool("echo", "echo", Some(json!({ "text": text }))).await?;
// let reply = extract_tool_result_text(&result);
let config = from_file?;
let mcp = connect.await?; // connects + discovers tools
new
.with_handler
.with_storage
.build?
.run
.await?;
Connection is lenient β a server that fails to start is logged and skipped, and
connect only errors if servers were configured but none could be reached.
The example connects to the bundled mcp_echo_server, so it runs with no
external setup; point command/args at any MCP stdio server to talk to
something real.
π€ LLM agents & multi-agent platform
Beyond single agents, a2a-agents ships the building blocks for an
LLM-driven, multi-agent platform β defined as ports in the platform
layer so the pure a2a-rs protocol crate stays infrastructure-free. The
a2a binary wires them together; it requires the llm, mcp-server, and
schema features:
| Subcommand | What it does |
|---|---|
run --config <toml>β¦ |
Run one or more agents from TOML configs |
validate --config <toml>β¦ |
Load + validate configs without serving |
control-plane --bind β¦ --config-dir β¦ --runtime local|container |
Serve the deploy/list/status/undeploy HTTP API |
print-schema |
Print the AgentConfig JSON Schema to stdout |
Config-driven LLM handler (llm feature)
Set type = "llm" and the framework drives a generic tool-calling LLM handler β
no Rust code. The model provider is picked up from the environment
(OPENAI_API_KEY / GEMINI_API_KEY / OPENROUTER_API_KEY):
[]
= "llm"
[]
= "You are a helpful assistant."
= 4
The llm feature is independent of MCP: an LLM agent that only delegates to peer
A2A agents builds without pulling in rmcp. Add mcp-server as well to also
feed it the tools of connected MCP servers.
Agent-as-tool delegation
List peer agents under [[handler.llm.agents]] and each is exposed to the model
as one ask_<slug> tool, so an orchestrator delegates work to specialists. Name
a peer by exactly one of url, skill, or agent_id:
[[]]
= "Weather Agent"
= "http://127.0.0.1:8081" # dial directly, orβ¦
[[]]
= "Billing"
= "invoice-lookup" # β¦resolve by advertised skill, orβ¦
[[]]
= "Scheduler"
= "scheduler-agent" # β¦by registry id (slug of the name)
skill / agent_id are resolved against the agent registry at startup, so
peers are found by capability instead of a hard-coded URL. See
examples/orchestrator_agent.toml and
examples/registry_orchestrator.toml.
Control plane
control-plane serves an HTTP API that composes the runtime and registry:
POST /agents deploys an agent from rendered TOML (provision + start + register
its card so peers discover it), GET /agents lists, GET /agents/{id} reports
health, DELETE /agents/{id} tears down. Pick the backend with --runtime:
local supervises child a2a run processes, container runs each agent in a
docker/podman container (--engine, --image).
See the workspace DECLARATIVE_AGENTS.md for the
platform design and roadmap.
Architecture
ReimbursementMessageHandler
The core business logic implementing AsyncMessageHandler:
- Processes reimbursement requests using the A2A message format
- Generates interactive forms for expense submissions
- Validates and approves reimbursement requests
- Returns structured responses with proper task states
ModernReimbursementServer
The server implementation using framework components:
- Integrates with
DefaultBusinessHandlerfor request processing - Uses
InMemoryTaskStoragefor task persistence - Configures
SimpleAgentInfowith agent capabilities - Supports both HTTP transport
Usage
Quick Start - Unified Demo (Recommended)
Run the complete demo with both agent backend and web frontend in a single command:
# Run everything (agent backend + web UI)
# Open your browser to http://localhost:3000
This starts:
- Agent Backend on
http://localhost:8080(HTTP) - Web Frontend on
http://localhost:3000
The frontend automatically connects to the local agent and provides an interactive interface for submitting expenses and viewing tasks.
Advanced Usage
Run specific components:
# Run only the agent backend (HTTP)
# Run only the web frontend (point it to an existing agent)
AGENT_HTTP_URL=http://localhost:8080
# Customize ports
# Run only HTTP transport for agent
Available Endpoints
Agent Backend:
- HTTP API:
http://localhost:8080(ConnectRPC) - Agent Card:
http://localhost:8080/agent-card
Web Frontend:
- Main UI:
http://localhost:3000 - Task List:
http://localhost:3000/tasks - Expense Form:
http://localhost:3000/expense/new
Example Conversation
Here's an example conversation with the reimbursement agent:
-
User: "Can you reimburse me $50 for the team lunch yesterday?"
-
Agent: Returns a form
-
User: Submits the filled form
-
Agent: "Your reimbursement request has been approved. Request ID: request_id_1234567"
Current Limitations
The reimbursement reference agent deliberately keeps its business logic
simple to showcase the framework architecture (the generic llm handler above
is the path to real model-driven agents):
- Message Processing: Basic pattern matching (use
type = "llm"for LLM-driven agents) - Storage: In-memory storage (framework supports SQLx for production)
- Authentication: Not implemented (framework supports Bearer/OAuth2)
- Form Processing: Simple JSON forms without complex validation
Future Enhancements
See the workspace ROADMAP.md for deferred themes and planned work.
Framework Features Demonstrated
- β AsyncMessageHandler trait implementation
- β DefaultBusinessHandler integration
- β InMemoryTaskStorage for task persistence
- β SimpleAgentInfo for agent metadata
- β HTTP transport support
- β Structured error handling with A2AError
- β Modern async/await patterns
- β Builder patterns for complex objects