A minimal, runnable example of two open protocols becoming the standard for interoperable AI agent systems:
- A2A (Agent2Agent) — how agents talk to each other
- MCP (Model Context Protocol) — how agents talk to tools
Three different frameworks (Google ADK, LangGraph, BeeAI) all communicate through A2A — that's the point.
A2A is an open HTTP protocol that lets AI agents built with different frameworks discover and call each other without custom integration code. Launched by Google Cloud in April 2025, donated to the Linux Foundation.
An A2A agent is just an HTTP server. Any other agent that speaks A2A can call it.
Three core concepts:
| Concept | What it is | Where you see it |
|---|---|---|
| AgentCard | A JSON document at GET /.well-known/agent.json — the agent's name, URL, version, and skills. How clients discover an agent. |
a2a_policy_agent.py → LlmAgent(name=..., description=...) |
| AgentSkill | A declared capability inside the AgentCard. Routers and orchestrators read skills to decide which agent to call. | a2a_provider_agent.py → AgentSkill(...) |
| AgentExecutor | The bridge between an incoming A2A request and your business logic. Either implement execute() manually or let a framework like ADK handle it via to_a2a(). |
a2a_provider_agent.py → handled by langgraph-a2a-server |
MCP is an open protocol standardizing how LLM agents connect to external tools and data sources. An MCP server exposes typed functions any MCP client can call — regardless of framework.
Three core concepts:
| Concept | What it is | Where you see it |
|---|---|---|
| MCP Server | Exposes one or more tools. Framework-agnostic. | mcpserver.py |
| MCP Client | The agent-side adapter that discovers and calls MCP tools. | a2a_provider_agent.py → MultiServerMCPClient |
| Tool | A typed Python function with a docstring. The LLM reads it and decides when to call it. | mcpserver.py → list_doctors() |
A2A = agents talking to agents. MCP = agents talking to tools.
An agent can simultaneously be an A2A server (accepting tasks) and an MCP client (calling tools to fulfill them). The Provider Agent does exactly this.
┌─────────────────────────────────────────────────┐
│ Option A: client.py (GPT-4o router) │
│ Option B: a2a_orchestrator_client.py (BeeAI) │
└────────────────┬────────────────────────────────┘
│ A2A
│
┌────────────────▼────────────────────────────────┐
│ a2a_orchestrator.py :9996 │
│ BeeAI RequirementAgent │
│ │
│ Fetches AgentCards at startup → creates │
│ HandoffTools → routes via LLM reasoning │
└──────────────┬──────────────┬───────────────────┘
│ A2A │ A2A
┌───────────────▼──┐ ┌───▼──────────────────────┐
│ Policy Agent │ │ Provider Agent │
│ :9999 │ │ :9997 │
│ Google ADK │ │ LangGraph + MCP │
│ │ │ │
│ LlmAgent │ │ ReAct agent loop │
│ + to_a2a() │ │ │ MCP (stdio) │
│ │ │ │ ▼ │
│ │ LiteLLM │ │ mcpserver.py │
│ ▼ │ │ │ │
│ Gemini + PDF │ │ ▼ │
└──────────────────┘ │ doctors.json │
└──────────────────────────┘
Files: policy_agent.py + a2a_policy_agent.py | Port: 9999
Answers questions about what the insurance plan covers.
User question (via A2A)
│
▼
Google ADK LlmAgent receives request
│
├── Decides to call answer_policy_question() tool
│
▼
policy_agent.py → LiteLLM → Gemini API + PDF (base64)
│
▼
to_a2a() sends response back over A2A
Key pattern — to_a2a() vs manual A2A SDK:
| Manual A2A SDK | Google ADK |
|---|---|
Define AgentCard yourself |
Generated from LlmAgent(name=..., description=...) |
Implement AgentExecutor.execute() |
ADK handles it internally |
Wire DefaultRequestHandler + A2AStarletteApplication |
to_a2a(agent) does it all |
| ~80 lines | ~20 lines |
The trade-off: ADK hides the protocol details. The manual approach is more educational.
Files: a2a_provider_agent.py + mcpserver.py | Port: 9997
Finds healthcare providers by location and tells you what insurance they accept.
User question (via A2A)
│
▼
LangGraph ReAct agent loop
├── LLM thinks: "I need to search for doctors"
├── Calls list_doctors() via MCP ──► mcpserver.py subprocess (stdio)
│ └── filters doctors.json
├── LLM receives results
└── LLM formats readable answer
│
▼
A2A response (result in task.artifacts)
Key pattern: Three protocols in one agent:
- A2A — outer layer, accepts tasks and serves responses (
langgraph-a2a-server) - LangGraph — middle layer, runs the ReAct reasoning loop
- MCP — inner layer, connects LangGraph to the
list_doctorstool via stdio
mcpserver.py is launched automatically as a subprocess by MultiServerMCPClient — never start it manually.
File: a2a_orchestrator.py | Port: 9996
Coordinates both sub-agents to answer complex questions that span both domains.
User question (via A2A)
│
▼
BeeAI RequirementAgent
│
├── ThinkTool: reasons about what's needed
│
├── HandoffTool → Policy Agent (A2A :9999)
│ "What does the plan cover for mental health?"
│
├── ThinkTool: reasons about next step
│
├── HandoffTool → Provider Agent (A2A :9997)
│ "Find psychiatrists in Boston, MA"
│
└── Synthesizes both answers into one response
│
▼
A2A response
Key pattern — BeeAI's elegance:
- At startup,
check_agent_exists()fetches each sub-agent's AgentCard HandoffToolwraps each A2A agent as a callable tool — the description comes directly from the AgentCardConditionalRequirementprevents infinite loops (each agent called max once per request)- Zero hardcoded routing — the LLM decides which tools to call based on AgentCard descriptions
File: client.py
Interactive client that calls the two sub-agents directly (bypasses the orchestrator). Uses GPT-4o to read AgentCards and route intelligently.
Startup: fetch AgentCards from both agents → show discovery output
Loop:
User types question
→ GPT-4o reads AgentCards, reasons about which agent fits
→ Routes to chosen agent via A2A
→ Prints response
Use this to: demonstrate A2A discovery and show how any LLM can act as a router purely from AgentCard metadata.
a2a/
│
├── policy_agent.py # Plain Python class — no A2A, no framework
│ # Reads PDF at startup, answers via Gemini/LiteLLM
│
├── a2a_policy_agent.py # Google ADK: LlmAgent + to_a2a() (port 9999)
│
├── a2a_provider_agent.py # LangGraph + MCP as A2A server (port 9997)
│
├── mcpserver.py # MCP server — exposes list_doctors() tool (stdio)
│
├── a2a_orchestrator.py # BeeAI RequirementAgent orchestrator (port 9996)
│ # Reads AgentCards, coordinates sub-agents
│
├── a2a_orchestrator_client.py # Interactive client for the BeeAI orchestrator
│ # Keeps conversation memory across follow-ups
│
├── client.py # Interactive client with GPT-4o router
│ # Calls sub-agents directly (no orchestrator)
│
├── helpers.py # setup_env() — loads .env, suppresses warnings
│
├── data/
│ ├── 2026AnthemgHIPSBC.pdf # Insurance policy PDF (Policy Agent)
│ └── doctors.json # Doctor dataset (MCP server)
│
├── example.env # Environment variable template
└── pyproject.toml # Project dependencies
| Key | Used by | Get it at |
|---|---|---|
GEMINI_API_KEY |
Policy Agent, Provider Agent, Orchestrator | Google AI Studio |
OPENAI_API_KEY |
Router in client.py (GPT-4o) |
OpenAI Platform |
cp example.env .env
# Edit .env and fill in both API keysuv syncCmd+Shift+P → Python: Select Interpreter → choose .venv in this folder.
# Terminal 1
uv run a2a_policy_agent.py # Google ADK, port 9999
# Terminal 2
uv run a2a_provider_agent.py # LangGraph + MCP, port 9997
# Terminal 3
uv run client.py # GPT-4o router, interactive# Terminal 1
uv run a2a_policy_agent.py # Google ADK, port 9999
# Terminal 2
uv run a2a_provider_agent.py # LangGraph + MCP, port 9997
# Terminal 3 — start AFTER 1 & 2 are running
uv run a2a_orchestrator.py # BeeAI orchestrator, port 9996
# Terminal 4
uv run a2a_orchestrator_client.py # Interactive, with memoryImportant: Start the orchestrator (Terminal 3) only after the sub-agents are running — it calls
check_agent_exists()at startup to fetch both AgentCards.
| Question | Best client |
|---|---|
| "Does my plan cover mental health?" | client.py → Policy Agent |
| "What's my deductible?" | client.py → Policy Agent |
| "Find a psychiatrist in Boston" | client.py → Provider Agent |
| "What insurance does Dr. Amanda Foster accept?" | client.py → Provider Agent |
| "I'm in Boston. What mental health coverage do I have AND find me a psychiatrist near me?" | a2a_orchestrator_client.py (calls both) |
| Package | Role |
|---|---|
a2a-sdk |
A2A protocol — AgentCard, AgentExecutor, ClientFactory |
google-adk |
Google Agent Development Kit — LlmAgent, to_a2a() |
mcp |
MCP protocol — FastMCP server toolkit |
langchain-mcp-adapters |
MultiServerMCPClient — LangGraph ↔ MCP bridge |
langgraph |
ReAct agent loop in the Provider Agent |
langgraph-a2a-server |
Wraps a LangGraph graph as an A2A HTTP server |
beeai-framework |
RequirementAgent, HandoffTool, A2AServer |
litellm |
Unified LLM API — Gemini + OpenAI with one interface |
langchain-litellm |
LangChain adapter for LiteLLM |