Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Get started quickly with these practical examples:
- **[Creating an agent with MCP](https://mozilla-ai.github.io/any-agent/cookbook/mcp_agent/)** - Integrate Model Context Protocol tools.
- **[Serve an Agent with A2A](https://mozilla-ai.github.io/any-agent/cookbook/serve_a2a/)** - Deploy agents with Agent-to-Agent communication.
- **[Building Multi-Agent Systems with A2A](https://mozilla-ai.github.io/any-agent/cookbook/a2a_as_tool/)** - Using an agent as a tool for another agent to interact with.
- **[Bridge MCP servers to A2A](https://mozilla-ai.github.io/any-agent/cookbook/mcp_a2a_bridge/)** - Expose MCP tools as A2A-compatible services for protocol interoperability.

## Contributions

Expand Down
273 changes: 273 additions & 0 deletions docs/cookbook/mcp_a2a_bridge.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Bridge MCP Servers to A2A Protocol\n",
"\n",
"This cookbook demonstrates how to expose MCP (Model Context Protocol) servers as A2A-compatible services, enabling seamless interoperability between different agent ecosystems.\n",
"\n",
"## Why Bridge MCP to A2A?\n",
"\n",
"- **Protocol Interoperability**: Make MCP tools available to Google's A2A ecosystem\n",
"- **Enterprise Integration**: Connect local tools with enterprise agent systems\n",
"- **Identity Support**: Leverage AGNTCY Identity for secure tool access\n",
"- **Zero Changes**: MCP servers work as-is without modifications\n",
"\n",
"This tutorial assumes familiarity with both MCP and A2A. If you're new to these protocols:\n",
"- MCP: See [Creating an agent with MCP](./mcp_agent.ipynb)\n",
"- A2A: See [Serve an Agent with A2A](./serve_a2a.ipynb)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Install Dependencies\n",
"\n",
"You'll need both MCP and A2A support:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%pip install 'any-agent[mcp,a2a]'\n",
"\n",
"import nest_asyncio\n",
"nest_asyncio.apply()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"from getpass import getpass\n",
"\n",
"# This example uses Mistral models\n",
"if \"MISTRAL_API_KEY\" not in os.environ:\n",
" print(\"MISTRAL_API_KEY not found in environment!\")\n",
" api_key = getpass(\"Please enter your MISTRAL_API_KEY: \")\n",
" os.environ[\"MISTRAL_API_KEY\"] = api_key\n",
" print(\"MISTRAL_API_KEY set for this session!\")\n",
"else:\n",
" print(\"MISTRAL_API_KEY found in environment.\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Basic Bridge: Single MCP Server\n",
"\n",
"Let's start by bridging a single MCP server (time server) to A2A:"
]
},
{
"cell_type": "code",
"metadata": {},
"outputs": [],
"source": "from any_agent.config import MCPStdio\nfrom any_agent.serving import (\n MCPToA2ABridgeConfig,\n serve_mcp_as_a2a_async,\n)\n\n# Configure MCP server\nmcp_config = MCPStdio(\n command=\"uvx\",\n args=[\"mcp-server-time\", \"--local-timezone=America/New_York\"],\n tools=[\"get_current_time\"],\n)\n\n# Configure bridge\nbridge_config = MCPToA2ABridgeConfig(\n mcp_config=mcp_config,\n port=0, # Use dynamic port\n endpoint=\"/time-bridge\",\n server_name=\"time-server\",\n)\n\n# Start the bridge\nbridge_handle = await serve_mcp_as_a2a_async(mcp_config, bridge_config)\nbridge_port = bridge_handle.port\n\nprint(f\"✓ MCP time server bridged to A2A at: http://localhost:{bridge_port}/time-bridge\")"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Use the Bridged MCP Tool via A2A\n",
"\n",
"Now we can access the MCP tool through A2A protocol:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from any_agent import AgentConfig, AnyAgent\n",
"from any_agent.tools import a2a_tool_async\n",
"\n",
"# Create an A2A tool from the bridged MCP server\n",
"time_tool = await a2a_tool_async(\n",
" f\"http://localhost:{bridge_port}/time-bridge\",\n",
" toolname=\"get_time_via_a2a\",\n",
" http_kwargs={\"timeout\": 30},\n",
")\n",
"\n",
"# Create an agent that uses the bridged tool\n",
"agent = await AnyAgent.create_async(\n",
" \"tinyagent\",\n",
" AgentConfig(\n",
" model_id=\"mistral/mistral-small-latest\",\n",
" instructions=\"Use the available tools to answer questions about time.\",\n",
" tools=[time_tool],\n",
" ),\n",
")\n",
"\n",
"# Use the MCP tool through A2A\n",
"result = await agent.run_async(\"What time is it in New York?\")\n",
"print(f\"Agent response: {result.final_output}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": "## Advanced: Bridge with AGNTCY Identity\n\nIf you're using mcpd with AGNTCY Identity support, you can include identity information in the bridge configuration. While the identity won't be exposed through the A2A protocol (due to AgentCard limitations), it will be logged and can be used for internal tracking:",
"outputs": []
},
{
"cell_type": "code",
"metadata": {},
"outputs": [],
"source": "import json\nfrom pathlib import Path\n\n# Check if identity exists (from mcpd)\nidentity_path = Path(\"~/.config/mcpd/identity/time-server.json\").expanduser()\nidentity_id = None\n\nif identity_path.exists():\n with open(identity_path) as f:\n identity_data = json.load(f)\n identity_id = identity_data.get(\"id\")\n print(f\"Found identity: {identity_id}\")\nelse:\n print(\"No identity found - proceeding without identity\")\n\n# Create bridge with identity\nbridge_config_with_id = MCPToA2ABridgeConfig(\n mcp_config=mcp_config,\n port=0,\n endpoint=\"/secure-time\",\n server_name=\"time-server\",\n identity_id=identity_id, # Will be logged but not exposed via A2A\n)\n\n# The identity will be logged when the bridge starts\nsecure_bridge = await serve_mcp_as_a2a_async(mcp_config, bridge_config_with_id)\nprint(f\"✓ Secure bridge started (check logs for identity verification)\")"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Multi-Bridge: Orchestrating Multiple MCP Tools\n",
"\n",
"Let's bridge multiple MCP servers and orchestrate them:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Bridge multiple MCP servers\n",
"bridges = []\n",
"tools = []\n",
"\n",
"# Time server\n",
"time_bridge = await serve_mcp_as_a2a_async(\n",
" MCPStdio(\n",
" command=\"uvx\",\n",
" args=[\"mcp-server-time\"],\n",
" tools=[\"get_current_time\"],\n",
" ),\n",
" MCPToA2ABridgeConfig(\n",
" mcp_config=None, # Will be set by serve function\n",
" port=0,\n",
" endpoint=\"/time\",\n",
" server_name=\"time\",\n",
" ),\n",
")\n",
"bridges.append(time_bridge)\n",
"tools.append(\n",
" await a2a_tool_async(\n",
" f\"http://localhost:{time_bridge.port}/time\",\n",
" toolname=\"time_tool\",\n",
" )\n",
")\n",
"\n",
"# Add more MCP servers here (filesystem, github, etc.)\n",
"# Example: filesystem_bridge = await serve_mcp_as_a2a_async(...)\n",
"\n",
"print(f\"✓ Bridged {len(bridges)} MCP servers to A2A\")\n",
"\n",
"# Create orchestrator agent with all bridged tools\n",
"orchestrator = await AnyAgent.create_async(\n",
" \"tinyagent\",\n",
" AgentConfig(\n",
" model_id=\"mistral/mistral-small-latest\",\n",
" name=\"orchestrator\",\n",
" instructions=\"Coordinate multiple tools to answer complex questions.\",\n",
" tools=tools,\n",
" ),\n",
")\n",
"\n",
"# Complex query using multiple tools\n",
"result = await orchestrator.run_async(\n",
" \"What's the current time? Format it nicely for a report.\"\n",
")\n",
"print(f\"\\nOrchestrator response:\\n{result.final_output}\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Direct Tool Invocation\n",
"\n",
"The bridge also supports direct tool invocation using a simple protocol:"
]
},
{
"cell_type": "code",
"metadata": {},
"outputs": [],
"source": "import httpx\nfrom a2a.client import A2AClient, A2ACardResolver\nfrom a2a.types import (\n Message,\n MessageSendParams,\n Part,\n Role,\n SendMessageRequest,\n TextPart,\n)\nfrom uuid import uuid4\n\n# Get the agent card\nbridge_url = f\"http://localhost:{bridge_port}/time-bridge\"\nasync with httpx.AsyncClient() as client:\n resolver = A2ACardResolver(httpx_client=client, base_url=bridge_url)\n agent_card = await resolver.get_agent_card()\n print(f\"Agent: {agent_card.name}\")\n print(f\"Skills: {[s.name for s in agent_card.skills]}\")\n\n# Direct tool call\nasync with httpx.AsyncClient() as client:\n a2a_client = A2AClient(httpx_client=client, agent_card=agent_card)\n \n # For single tool bridges, just send the input\n request = SendMessageRequest(\n id=str(uuid4()),\n params=MessageSendParams(\n message=Message(\n role=Role.user,\n parts=[Part(root=TextPart(text=\"current time\"))],\n message_id=str(uuid4()),\n )\n ),\n )\n \n response = await a2a_client.send_message(request)\n print(f\"\\nDirect response: {response}\")"
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Cleanup\n",
"\n",
"Don't forget to clean up the bridges:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Shutdown all bridges\n",
"for bridge in bridges:\n",
" await bridge.shutdown()\n",
"\n",
"await bridge_handle.shutdown()\n",
"await secure_bridge.shutdown()\n",
"\n",
"print(\"✓ All bridges shut down\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Next Steps\n",
"\n",
"1. **Production Deployment**: Deploy bridges as microservices\n",
"2. **Service Discovery**: Register bridges with AGNTCY Directory\n",
"3. **Authentication**: Add proper auth between bridges and agents\n",
"4. **Monitoring**: Add observability for bridge operations\n",
"\n",
"For more information:\n",
"- [MCP Documentation](https://modelcontextprotocol.io/)\n",
"- [A2A Protocol](https://github.com/google/a2a)\n",
"- [AGNTCY Identity](https://github.com/agntcy/identity)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.0"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
82 changes: 80 additions & 2 deletions docs/serving.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

- [Model Context Protocol (MCP)](https://modelcontextprotocol.io/specification/2025-03-26), via the [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk).

- **MCP to A2A Bridge**: Expose MCP servers as A2A-compatible services, enabling protocol interoperability. Requires both extras: `pip install 'any-agent[mcp,a2a]'`.

## Configuring and Serving Agents

You can configure and serve an agent using the [`A2AServingConfig`][any_agent.serving.A2AServingConfig] or [`MCPServingConfig`][any_agent.serving.MCPServingConfig] and the [`AnyAgent.serve_async`][any_agent.AnyAgent.serve_async] method.
Expand Down Expand Up @@ -152,10 +154,86 @@ if __name__ == "__main__":

```

## Bridging MCP to A2A

any-agent provides a bridge that allows you to expose MCP servers as A2A-compatible services. This enables:

- **Protocol Interoperability**: Make MCP tools available to Google's A2A ecosystem
- **Enterprise Integration**: Connect local tools with enterprise agent systems
- **Identity Support**: Leverage AGNTCY Identity for secure tool access

### Basic Bridge Example

```python
import asyncio
from any_agent.config import MCPStdio
from any_agent.serving import (
MCPToA2ABridgeConfig,
serve_mcp_as_a2a_async,
)

async def bridge_mcp_server():
# Configure MCP server
mcp_config = MCPStdio(
command="uvx",
args=["mcp-server-time"],
tools=["get_current_time"],
)

# Configure bridge
bridge_config = MCPToA2ABridgeConfig(
mcp_config=mcp_config,
port=8080,
endpoint="/time-bridge",
server_name="time-server",
)

# Start the bridge
bridge_handle = await serve_mcp_as_a2a_async(mcp_config, bridge_config)
print(f"MCP server bridged to A2A at: http://localhost:8080/time-bridge")

# Keep running
await bridge_handle.task

if __name__ == "__main__":
asyncio.run(bridge_mcp_server())
```

### Using the Bridged MCP Tool

Once bridged, the MCP tool can be accessed via A2A protocol:

```python
from any_agent import AgentConfig, AnyAgent
from any_agent.tools import a2a_tool_async

async def use_bridged_tool():
# Create A2A tool from bridged MCP server
time_tool = await a2a_tool_async(
"http://localhost:8080/time-bridge",
toolname="get_time_via_bridge",
)

# Use in an agent
agent = await AnyAgent.create_async(
"tinyagent",
AgentConfig(
model_id="mistral/mistral-small-latest",
instructions="Use tools to answer questions.",
tools=[time_tool],
),
)

result = await agent.run_async("What time is it?")
print(result.final_output)
```

## More Examples

Check out our cookbook example for building and serving an agent via A2A:
Check out our cookbook examples:

👉 [Serve an Agent with A2A (Jupyter Notebook)](./cookbook/serve_a2a.ipynb)

👉 [Use an A2a Agent as a tool (Jupyter Notebook)](./cookbook/serve_a2a.ipynb)
👉 [Use an A2A Agent as a tool (Jupyter Notebook)](./cookbook/a2a_as_tool.ipynb)

👉 [Bridge MCP servers to A2A (Jupyter Notebook)](./cookbook/mcp_a2a_bridge.ipynb)
Loading