Skip to main content

a2a_mcp/
lib.rs

1//! # A2A-MCP Integration
2//!
3//! This crate provides **bidirectional integration** between the Agent-to-Agent (A2A) protocol
4//! and the Model Context Protocol (MCP), enabling seamless communication between these protocols.
5//!
6//! ## Core Features
7//!
8//! ### 1. A2A Agents → MCP Tools (`AgentToMcpBridge`)
9//!
10//! Expose A2A agent skills as callable MCP tools, allowing MCP clients (like Claude Desktop)
11//! to invoke A2A agent capabilities.
12//!
13//! ```no_run
14//! use a2a_mcp::AgentToMcpBridge;
15//! use a2a_rs::adapter::transport::http::HttpClient;
16//! use a2a_rs::domain::AgentCard;
17//! use rmcp::{transport::stdio, ServiceExt};
18//!
19//! #[tokio::main]
20//! async fn main() -> anyhow::Result<()> {
21//!     // Point an A2A client at the agent's HTTP endpoint.
22//!     let client = HttpClient::new("https://my-agent.example.com".to_string());
23//!
24//!     // The agent card describes the agent's skills. In a real client
25//!     // you'll fetch it from `/.well-known/agent-card.json`; this is a
26//!     // stand-in to keep the example self-contained.
27//!     let agent_card: AgentCard = AgentCard::builder()
28//!         .name("My Agent".to_string())
29//!         .description("Does things".to_string())
30//!         .url("https://my-agent.example.com".to_string())
31//!         .version("0.1.0".to_string())
32//!         .capabilities(Default::default())
33//!         .default_input_modes(vec!["text".to_string()])
34//!         .default_output_modes(vec!["text".to_string()])
35//!         .skills(vec![])
36//!         .build();
37//!
38//!     // MCP tool names are namespaced by agent_card.url.
39//!     let bridge = AgentToMcpBridge::new(client, agent_card);
40//!
41//!     // Serve as an MCP server over stdio.
42//!     bridge.serve(stdio()).await?.waiting().await?;
43//!     Ok(())
44//! }
45//! ```
46//!
47//! ### 2. MCP Tools → A2A Agents (`McpToA2ABridge`)
48//!
49//! Augment A2A agents with MCP tool capabilities, allowing agents to call external MCP tools
50//! as part of their processing.
51//!
52//! ```no_run
53//! use a2a_mcp::{create_tool_call_message, McpToA2ABridge};
54//! use a2a_rs::domain::{error::A2AError, Message, Task};
55//! use a2a_rs::port::AsyncMessageHandler;
56//! use async_trait::async_trait;
57//! use rmcp::{transport::stdio, ServiceExt};
58//!
59//! // Your existing A2A handler — the bridge wraps it so non-tool-call
60//! // messages keep flowing through your normal business logic.
61//! #[derive(Clone)]
62//! struct MyHandler;
63//!
64//! #[async_trait]
65//! impl AsyncMessageHandler for MyHandler {
66//!     async fn process_message(
67//!         &self,
68//!         _task_id: &str,
69//!         _message: &Message,
70//!         _session_id: Option<&str>,
71//!     ) -> Result<Task, A2AError> {
72//!         unimplemented!("your business logic here")
73//!     }
74//! }
75//!
76//! #[tokio::main]
77//! async fn main() -> anyhow::Result<()> {
78//!     // Connect to an MCP server. In production you'll typically use
79//!     // `rmcp::transport::TokioChildProcess` to spawn one; stdio works
80//!     // when this process is itself the MCP client end of a pipe.
81//!     let mcp_client = ().serve(stdio()).await?;
82//!
83//!     // Wrap your handler so messages carrying an `a2a_rs_tool_call`
84//!     // metadata envelope are routed to the MCP server.
85//!     let bridge = McpToA2ABridge::new(mcp_client.peer().clone(), MyHandler).await?;
86//!
87//!     // Build a tool-call message. The envelope rides in metadata, not text:
88//!     //   metadata["a2a_rs_tool_call"] = { "name": "...", "arguments": {...} }
89//!     let tool_msg = create_tool_call_message("add", serde_json::json!({"a": 5, "b": 7}));
90//!     let _result = bridge.process_message("task-1", &tool_msg, None).await?;
91//!     Ok(())
92//! }
93//! ```
94//!
95//! ## Tool-call wire format
96//!
97//! `McpToA2ABridge` does not inspect message text. To trigger a tool call,
98//! attach an [`McpToolCall`] envelope to `Message.metadata` under the
99//! [`MCP_TOOL_CALL_METADATA_KEY`] key (`"a2a_rs_tool_call"`):
100//!
101//! ```text
102//! Message {
103//!   role: User,
104//!   metadata: {
105//!     "a2a_rs_tool_call": { "name": "calculator_add", "arguments": {"a":5,"b":3} }
106//!   },
107//!   ...
108//! }
109//! ```
110//!
111//! Messages without this metadata key are forwarded unchanged to the inner
112//! `AsyncMessageHandler`. Use [`create_tool_call_message`] or
113//! [`attach_tool_call`] to construct one without touching the constant by hand.
114//!
115//! ## Architecture
116//!
117//! ```text
118//! ┌─────────────────────────────────────────────────────────────┐
119//! │                      a2a-mcp Crate                          │
120//! ├──────────────────────┬──────────────────────────────────────┤
121//! │  Direction 1:        │  Direction 2:                        │
122//! │  A2A → MCP           │  MCP → A2A                           │
123//! ├──────────────────────┼──────────────────────────────────────┤
124//! │  AgentToMcpBridge    │  McpToA2ABridge                      │
125//! │  - Wraps A2A agent   │  - Wraps MCP ServerHandler           │
126//! │  - Implements        │  - Implements AsyncMessageHandler    │
127//! │    ServerHandler     │  - Calls MCP tools from A2A tasks    │
128//! │  - Maps skills to    │  - Augments agents with MCP tools    │
129//! │    MCP tools         │                                      │
130//! └──────────────────────┴──────────────────────────────────────┘
131//! ```
132//!
133//! ## Protocol Converters
134//!
135//! The crate provides transparent conversion between A2A and MCP types:
136//!
137//! - **Messages**: `A2A Message` ↔ `MCP Content`
138//! - **Skills/Tools**: `A2A AgentSkill` ↔ `MCP Tool`
139//! - **Results**: `A2A Task` ↔ `MCP CallToolResult`
140
141pub mod bridge;
142pub mod converters;
143pub mod error;
144
145// Re-export key types
146pub use bridge::mcp_to_a2a::{
147    MCP_TOOL_CALL_METADATA_KEY, McpToolCall, attach_tool_call, create_tool_call_message,
148};
149pub use bridge::{AgentToMcpBridge, McpToA2ABridge};
150pub use converters::{MessageConverter, SkillToolConverter, TaskResultConverter};
151pub use error::{A2aMcpError, Result};
152
153/// Current crate version
154pub const VERSION: &str = env!("CARGO_PKG_VERSION");