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");