Skip to main content

Crate a2a_mcp

Crate a2a_mcp 

Source
Expand description

§A2A-MCP Integration

This crate provides bidirectional integration between the Agent-to-Agent (A2A) protocol and the Model Context Protocol (MCP), enabling seamless communication between these protocols.

§Core Features

§1. A2A Agents → MCP Tools (AgentToMcpBridge)

Expose A2A agent skills as callable MCP tools, allowing MCP clients (like Claude Desktop) to invoke A2A agent capabilities.

use a2a_mcp::AgentToMcpBridge;
use a2a_rs::adapter::transport::http::HttpClient;
use a2a_rs::domain::AgentCard;
use rmcp::{transport::stdio, ServiceExt};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Point an A2A client at the agent's HTTP endpoint.
    let client = HttpClient::new("https://my-agent.example.com".to_string());

    // The agent card describes the agent's skills. In a real client
    // you'll fetch it from `/.well-known/agent-card.json`; this is a
    // stand-in to keep the example self-contained.
    let agent_card: AgentCard = AgentCard::builder()
        .name("My Agent".to_string())
        .description("Does things".to_string())
        .url("https://my-agent.example.com".to_string())
        .version("0.1.0".to_string())
        .capabilities(Default::default())
        .default_input_modes(vec!["text".to_string()])
        .default_output_modes(vec!["text".to_string()])
        .skills(vec![])
        .build();

    // MCP tool names are namespaced by agent_card.url.
    let bridge = AgentToMcpBridge::new(client, agent_card);

    // Serve as an MCP server over stdio.
    bridge.serve(stdio()).await?.waiting().await?;
    Ok(())
}

§2. MCP Tools → A2A Agents (McpToA2ABridge)

Augment A2A agents with MCP tool capabilities, allowing agents to call external MCP tools as part of their processing.

use a2a_mcp::{create_tool_call_message, McpToA2ABridge};
use a2a_rs::domain::{error::A2AError, Message, Task};
use a2a_rs::port::AsyncMessageHandler;
use async_trait::async_trait;
use rmcp::{transport::stdio, ServiceExt};

// Your existing A2A handler — the bridge wraps it so non-tool-call
// messages keep flowing through your normal business logic.
#[derive(Clone)]
struct MyHandler;

#[async_trait]
impl AsyncMessageHandler for MyHandler {
    async fn process_message(
        &self,
        _task_id: &str,
        _message: &Message,
        _session_id: Option<&str>,
    ) -> Result<Task, A2AError> {
        unimplemented!("your business logic here")
    }
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Connect to an MCP server. In production you'll typically use
    // `rmcp::transport::TokioChildProcess` to spawn one; stdio works
    // when this process is itself the MCP client end of a pipe.
    let mcp_client = ().serve(stdio()).await?;

    // Wrap your handler so messages carrying an `a2a_rs_tool_call`
    // metadata envelope are routed to the MCP server.
    let bridge = McpToA2ABridge::new(mcp_client.peer().clone(), MyHandler).await?;

    // Build a tool-call message. The envelope rides in metadata, not text:
    //   metadata["a2a_rs_tool_call"] = { "name": "...", "arguments": {...} }
    let tool_msg = create_tool_call_message("add", serde_json::json!({"a": 5, "b": 7}));
    let _result = bridge.process_message("task-1", &tool_msg, None).await?;
    Ok(())
}

§Tool-call wire format

McpToA2ABridge does not inspect message text. To trigger a tool call, attach an McpToolCall envelope to Message.metadata under the MCP_TOOL_CALL_METADATA_KEY key ("a2a_rs_tool_call"):

Message {
  role: User,
  metadata: {
    "a2a_rs_tool_call": { "name": "calculator_add", "arguments": {"a":5,"b":3} }
  },
  ...
}

Messages without this metadata key are forwarded unchanged to the inner AsyncMessageHandler. Use create_tool_call_message or attach_tool_call to construct one without touching the constant by hand.

§Architecture

┌─────────────────────────────────────────────────────────────┐
│                      a2a-mcp Crate                          │
├──────────────────────┬──────────────────────────────────────┤
│  Direction 1:        │  Direction 2:                        │
│  A2A → MCP           │  MCP → A2A                           │
├──────────────────────┼──────────────────────────────────────┤
│  AgentToMcpBridge    │  McpToA2ABridge                      │
│  - Wraps A2A agent   │  - Wraps MCP ServerHandler           │
│  - Implements        │  - Implements AsyncMessageHandler    │
│    ServerHandler     │  - Calls MCP tools from A2A tasks    │
│  - Maps skills to    │  - Augments agents with MCP tools    │
│    MCP tools         │                                      │
└──────────────────────┴──────────────────────────────────────┘

§Protocol Converters

The crate provides transparent conversion between A2A and MCP types:

  • Messages: A2A MessageMCP Content
  • Skills/Tools: A2A AgentSkillMCP Tool
  • Results: A2A TaskMCP CallToolResult

Re-exports§

pub use bridge::mcp_to_a2a::MCP_TOOL_CALL_METADATA_KEY;
pub use bridge::mcp_to_a2a::McpToolCall;
pub use bridge::mcp_to_a2a::attach_tool_call;
pub use bridge::mcp_to_a2a::create_tool_call_message;
pub use bridge::AgentToMcpBridge;
pub use bridge::McpToA2ABridge;
pub use converters::MessageConverter;
pub use converters::SkillToolConverter;
pub use converters::TaskResultConverter;
pub use error::A2aMcpError;
pub use error::Result;

Modules§

bridge
Bridge implementations for A2A-MCP integration
converters
Protocol converters between A2A and MCP
error
Error types for A2A-MCP integration

Constants§

VERSION
Current crate version