Build an A2A Protocol Agent in Python (2026)
١٦ يوليو ٢٠٢٦

The Agent2Agent (A2A) protocol is an open standard that lets AI agents built on different frameworks discover each other, delegate tasks, and exchange results — where MCP lets one agent call a tool, A2A lets one agent call another agent12. It reached its first stable release, v1.0, in April 2026, and is now backed by a technical steering committee with representatives from AWS, Cisco, Google, IBM Research, Microsoft, Salesforce, SAP, and ServiceNow2.
TL;DR
A2A went from a Google-only proposal to a Linux Foundation standard with 150+ supporting organizations, nearly 24,000 GitHub stars on its core spec repository, and production integrations in Azure AI Foundry, Copilot Studio, and Amazon Bedrock AgentCore Runtime1. Version 1.0 — the first stable, production-ready spec — shipped in April 2026 with signed Agent Cards, multi-tenancy, and version negotiation2. This tutorial builds a real A2A server with the official a2a-sdk (currently v1.1.0), using the SDK's own Agent Skill, Agent Card, and Agent Executor pattern, and calls it over JSON-RPC. Everything in the Steps and Verification sections below was actually installed and run, not just described. Requires Python 3.10+. Build time: 20-25 minutes.
What you'll learn
- What changed in A2A v1.0 — signed Agent Cards, multi-tenancy, and version negotiation — and why the ecosystem moved fast enough to reach 150+ backing organizations in about a year
- How an Agent Skill and Agent Card describe what your agent can do, and how another agent or client discovers that over HTTP
- How to implement the SDK's
AgentExecutorinterface to process an incoming task and stream status updates back - How to stand up an A2A server with Starlette and Uvicorn using the SDK's route factories
- How to call your agent over JSON-RPC — including one header the official quickstart doesn't spell out, which this tutorial found by testing against a live server
- Where A2A stops and MCP starts, so you know which one to reach for (or when you need both)
Prerequisites
- Python 3.10 or later — the SDK's PyPI metadata lists
Requires-Python: >=3.103 a2a-sdkwith the HTTP server extras:pip install "a2a-sdk[http-server]"(current version 1.1.0, released May 29, 2026)3uvicornandstarlettefor running the ASGI server (thehttp-serverextra pulls in Starlette; Uvicorn is installed separately, matching the official quickstart's own setup)4- Basic familiarity with
async/awaitin Python — every A2A server method in this tutorial is a coroutine
Why agent-to-agent, and why not just MCP?
MCP (Model Context Protocol) standardizes how a single agent reaches into tools, files, and data sources. A2A standardizes something different: how two independent agents — possibly built on different frameworks, by different vendors, running on different servers — find each other and hand off work. The A2A v1.0 announcement is explicit about this division: "MCP is commonly used for tool and context integration at the individual agent level. A2A focuses on communication and coordination between agents. In practice, many systems will use both: MCP inside agents, A2A between agents."2 The Linux Foundation's own framing draws the same line: "A2A defines how agents communicate and coordinate with each other across organizational boundaries, while MCP defines how agents connect to internal tools and data sources."1 If you've already built an MCP server for tool access, A2A is not a replacement for it — see MCP Goes Stateless for the tool-integration side of that pairing.
What's new in A2A v1.0
A2A started as a Google proposal in April 2025 and was contributed to the Linux Foundation for neutral governance two months later, in June 20255. Version 1.0, released in April 2026, is the first release the project calls stable and production-ready2. Three changes stand out:
- Signed Agent Cards. Agent Cards can now be cryptographically signed for identity verification: an
AgentCardSignaturewraps a JWS (JSON Web Signature, RFC 7515) over the card content, so a client can cryptographically confirm a card actually came from the agent it claims to and wasn't altered in transit6. - Multi-tenancy. A single A2A endpoint can now securely host many agents, instead of needing one endpoint per agent2.
- Version negotiation. The protocol added a defined migration path so v1.0 and the older v0.3 wire format can coexist — an Agent Card can advertise support for both, and clients migrate progressively rather than in one cutover2. (This tutorial's Verification section shows exactly where that negotiation shows up in a real request.)
The adoption numbers back up the "production-ready" framing: more than 150 organizations now support A2A, up from more than 50 a year earlier1. The core protocol repository, which the Linux Foundation put at more than 22,000 GitHub stars in its April 2026 announcement, has since grown to nearly 24,00017; and the SDK ecosystem, five production-ready languages as of that same announcement — Python, JavaScript, Java, Go, and .NET — has since added a sixth, Rust17. Microsoft has integrated A2A into Azure AI Foundry and Copilot Studio1: Foundry agents could already call out to other A2A agents as a tool, and Microsoft added the reverse direction — exposing a Foundry agent as an A2A endpoint other agents can call — in public preview at Build 20268. AWS added support through Amazon Bedrock AgentCore Runtime1. Two agent frameworks you may already use also speak A2A natively: LangGraph and CrewAI agents can now delegate sub-tasks to each other across frameworks without sharing internal memory1. As Google Cloud's Rao Surapaneni put it: "AI agents are only as useful as their ability to collaborate, and the adoption of A2A by more than 150 organizations underscores the widespread enthusiasm for an open, interoperable protocol."1
Step 1: Install the SDK
mkdir a2a-hello-agent && cd a2a-hello-agent
python3 -m venv .venv && source .venv/bin/activate
pip install "a2a-sdk[http-server]" uvicorn
This installs a2a-sdk 1.1.0, which implements A2A spec 1.0 natively and includes a compatibility mode for the older 0.3 wire format, across JSON-RPC, HTTP+JSON/REST, and gRPC transports for both client and server4.
Step 2: Define an Agent Skill and Agent Card
Before an A2A agent can do anything, it needs to declare what it can do (a Skill) and publish that as a machine-readable Agent Card — the "business card" other agents fetch to decide whether to talk to yours9. Save this as agent_card.py:
from a2a.types import AgentCapabilities, AgentCard, AgentInterface, AgentSkill
skill = AgentSkill(
id='echo_bot',
name='Echo Bot',
description='An example agent that acknowledges a request and echoes it back.',
input_modes=['text/plain'],
output_modes=['text/plain'],
tags=['a2a', 'echo-example'],
examples=['hi', 'how are you'],
)
public_agent_card = AgentCard(
name='Hello World Agent',
description='Just a hello world agent',
version='0.0.1',
default_input_modes=['text/plain'],
default_output_modes=['text/plain'],
capabilities=AgentCapabilities(streaming=True, extended_agent_card=True),
supported_interfaces=[
AgentInterface(
protocol_binding='JSONRPC',
url='http://127.0.0.1:9999',
protocol_version='1.0',
)
],
skills=[skill],
)
Every field here — id, input_modes/output_modes, tags, examples on the skill; supported_interfaces, capabilities, default_input_modes on the card — matches the SDK's own type definitions, confirmed by constructing these objects directly against the installed 1.1.0 package10. supported_interfaces is v1.0-specific: it's an ordered list of AgentInterface objects (protocol binding, URL, and protocol version), which is how a single card can now advertise multiple transports instead of one hardcoded URL. By default, an A2A server publishes this card at /.well-known/agent-card.json9.
Step 3: Write the Agent Executor
The Agent Executor is where your actual logic lives — it's the bridge between the protocol (which the SDK handles) and whatever your agent does. The SDK provides an abstract base class, AgentExecutor, with two methods to implement: execute() for handling a request, and cancel() for handling a cancellation9. Save this as agent_executor.py:
from a2a.server.agent_execution import AgentExecutor, RequestContext
from a2a.server.events import EventQueue
from a2a.server.tasks import TaskUpdater
from a2a.types import TaskState
from a2a.helpers import new_task_from_user_message, new_text_message, new_text_part, get_message_text
class HelloWorldAgent:
async def invoke(self, user_request: str) -> str:
return f'Hello, World! I have received your request ({user_request})'
class HelloWorldAgentExecutor(AgentExecutor):
def __init__(self) -> None:
self.agent = HelloWorldAgent()
async def execute(self, context: RequestContext, event_queue: EventQueue) -> None:
task = context.current_task or new_task_from_user_message(context.message)
if not context.current_task:
await event_queue.enqueue_event(task)
task_updater = TaskUpdater(event_queue=event_queue, task_id=task.id, context_id=task.context_id)
await task_updater.update_status(
state=TaskState.TASK_STATE_WORKING,
message=new_text_message('Processing request...'),
)
query = get_message_text(context.message)
result = await self.agent.invoke(user_request=query) if query else 'No text input is provided!'
await task_updater.add_artifact(parts=[new_text_part(text=result, media_type='text/plain')])
await task_updater.update_status(
state=TaskState.TASK_STATE_COMPLETED,
message=new_text_message('Request is completed!'),
)
async def cancel(self, context: RequestContext, event_queue: EventQueue) -> None:
raise NotImplementedError('cancel not supported')
Walking through execute(): it pulls the current task from the request context (or creates one for a brand-new conversation), moves the task to TASK_STATE_WORKING and pushes a status message, runs the actual business logic (self.agent.invoke()), attaches the result as an artifact, and finally marks the task TASK_STATE_COMPLETED. Every one of those calls goes through the EventQueue, which is how the SDK turns your synchronous-looking code into the streaming task-update events an A2A client receives9.
Step 4: Start the server
The SDK ships route factories that plug into Starlette (or FastAPI) rather than a bespoke server — that's what makes it possible to add your own auth or logging middleware around an A2A agent9. Save this as server.py:
import uvicorn
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.routes import create_agent_card_routes, create_jsonrpc_routes
from a2a.server.tasks import InMemoryTaskStore
from starlette.applications import Starlette
from agent_card import public_agent_card
from agent_executor import HelloWorldAgentExecutor
request_handler = DefaultRequestHandler(
agent_executor=HelloWorldAgentExecutor(),
task_store=InMemoryTaskStore(),
agent_card=public_agent_card,
)
routes = []
routes.extend(create_agent_card_routes(public_agent_card))
routes.extend(create_jsonrpc_routes(request_handler, '/'))
app = Starlette(routes=routes)
if __name__ == '__main__':
uvicorn.run(app, host='127.0.0.1', port=9999)
DefaultRequestHandler ties your executor to a TaskStore (here, an in-memory one) and the Agent Card, and routes incoming A2A calls to execute()/cancel() on your executor9. Run it with python server.py, and it listens on http://127.0.0.1:9999.
Step 5: Call the agent
With the server running, fetch its Agent Card and send it a task:
curl -s http://127.0.0.1:9999/.well-known/agent-card.json
curl -s -X POST http://127.0.0.1:9999/ \
-H "Content-Type: application/json" \
-H "A2A-Version: 1.0" \
-d '{"jsonrpc":"2.0","id":1,"method":"SendMessage","params":{"message":{"role":"ROLE_USER","parts":[{"text":"Say hello."}],"messageId":"test-msg-1"}}}'
Verification
This is not a description of code that should work — it was actually installed and run. a2a-sdk[http-server] and uvicorn were installed into a clean Python 3.10.12 virtual environment; the AgentSkill, AgentCard, AgentExecutor, TaskUpdater, and helper functions (new_task_from_user_message, new_text_message, new_text_part, get_message_text) were imported and their signatures inspected directly against the installed 1.1.0 package before writing the tutorial code above, to confirm every field and parameter name matches this SDK version rather than an older one10. The server from Step 4 was then started for real, and the agent card endpoint returned exactly the expected JSON:
{"name":"Hello World Agent","description":"Just a hello world agent","supportedInterfaces":[{"url":"http://127.0.0.1:9999","protocolBinding":"JSONRPC","protocolVersion":"1.0"}],"version":"0.0.1","capabilities":{"streaming":true,"extendedAgentCard":true},"defaultInputModes":["text/plain"],"defaultOutputModes":["text/plain"],"skills":[{"id":"echo_bot","name":"Echo Bot", ...}]}
The first attempt to send a message failed with {"error":{"code":-32601,"message":"Method not found"}} using the REST-style method name message/send — that name is only valid on the SDK's separate REST route, not JSON-RPC. Switching to the JSON-RPC method name SendMessage got further but hit a second, more interesting error: "A2A version '0.3' is not supported by this handler. Expected version '1.0'." The SDK defaults an unmarked JSON-RPC request to the older 0.3 wire format unless the request carries an explicit A2A-Version: 1.0 HTTP header — this is version negotiation from the v1.0 spec2 enforced in code, in a2a/utils/version_validator.py and the VERSION_HEADER constant in a2a/utils/constants.py10. The Python quickstart pages this tutorial follows don't spell out this header for raw JSON-RPC calls, because they call the server through the SDK's own client (create_client), which sets it internally; it only surfaced here by reading the installed package's source after the first two raw JSON-RPC attempts failed. Adding the header produced a complete, successful task:
{"result":{"task":{"id":"48afdef2-...","status":{"state":"TASK_STATE_COMPLETED", ...},"artifacts":[{"parts":[{"text":"Hello, World! I have received your request (Say hello.)"}]}]}},"id":1,"jsonrpc":"2.0"}
What was not run: the SDK's Python client (a2a.client.create_client) and its streaming mode were not exercised — the curl-based JSON-RPC call above was used instead, since it demonstrates the wire protocol directly without depending on the client library's own abstractions. If you need streaming task updates, create_client with ClientConfig(streaming=True) is the documented path9.
Troubleshooting
{"error":{"message":"Method not found"}} on message/send. You're using the REST method name against the JSON-RPC endpoint. Use SendMessage for JSON-RPC, or mount the REST routes instead if you want path-style methods.
VERSION_NOT_SUPPORTED / "Expected version '1.0'". Add an A2A-Version: 1.0 header to the request. Without it, the server assumes the older 0.3 JSON-RPC shape.
Agent Card fetch returns 404. Confirm you registered create_agent_card_routes(public_agent_card) on the Starlette app — it's a separate route list from create_jsonrpc_routes, and skipping it means /.well-known/agent-card.json was never mounted.
Executor's execute() never seems to finish. Every event (update_status, add_artifact) has to go through the EventQueue you were given — if a custom executor swallows exceptions before calling update_status(state=TaskState.TASK_STATE_COMPLETED, ...), the client will hang waiting for a terminal state.
Is A2A a replacement for MCP?
No. A2A and MCP solve different layers of the same problem and are designed to be used together, not as alternatives2. MCP governs how one agent calls tools, APIs, and data sources it owns; A2A governs how that same agent talks to a completely different agent — potentially one it doesn't control and can't see the internals of. A typical production system uses MCP inside each agent and A2A between agents12.
Do I need Google Cloud to use A2A?
No. A2A was originally proposed by Google but has been governed by the Linux Foundation since June 2025, under Apache 2.0, with a technical steering committee spanning AWS, Cisco, Google, IBM Research, Microsoft, Salesforce, SAP, and ServiceNow25. It now runs natively on Microsoft's Azure AI Foundry and Copilot Studio and on Amazon Bedrock AgentCore Runtime, alongside Google Cloud's own tooling1. The Python SDK used in this tutorial runs anywhere Python does — there's no cloud dependency to build or test an A2A agent locally.
Next steps and further reading
This tutorial covers the non-streaming path. The SDK also supports streaming task updates and multi-turn conversations over the same JSON-RPC connection — worth reaching for once a single request/response round trip isn't enough9. If your agent also needs to call tools or internal data sources (rather than another agent), that's the job MCP is built for; see MCP Goes Stateless for the current spec. And if any of the agents on either side of an A2A call should pause for a person to approve a risky action before it runs, the same kind of approval gate covered in Add Human-in-the-Loop Approval to Claude Agent SDK applies just as much to a remote A2A task as it does to a local tool call.
Footnotes
-
The Linux Foundation, "A2A Protocol Surpasses 150 Organizations, Lands in Major Cloud Platforms, and Sees Enterprise Production Use in First Year" — https://www.linuxfoundation.org/press/a2a-protocol-surpasses-150-organizations-lands-in-major-cloud-platforms-and-sees-enterprise-production-use-in-first-year (San Francisco, April 9, 2026; 150+ organizations, 22,000+ GitHub stars on the core repo, five-language SDK ecosystem, Azure AI Foundry/Copilot Studio and Amazon Bedrock AgentCore Runtime integration, LangGraph/CrewAI interop, AP2 60+ organizations, Rao Surapaneni quote; fetched 2026-07-16) ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12
-
A2A Protocol / The Linux Foundation, "Announcing Version 1.0" — https://a2a-protocol.org/latest/announcing-1.0/ (v1.0 features: signed Agent Cards, multi-tenancy, version negotiation, web-aligned architecture; eight-company technical steering committee; "complementary to MCP, not a replacement" quote; fetched 2026-07-16) ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11
-
PyPI,
a2a-sdkproject page — https://pypi.org/project/a2a-sdk/ (version 1.1.0 released May 29, 2026; version 1.0.0 released April 20, 2026;Requires-Python: >=3.10; Apache-2.0 license; author Google LLC; fetched 2026-07-16) ↩ ↩2 -
GitHub,
a2aproject/a2a-pythonREADME — https://github.com/a2aproject/a2a-python (SDK implements A2A spec 1.0 with 0.3 compatibility mode; JSON-RPC/HTTP+JSON/REST/gRPC transport support table; installation extras; fetched 2026-07-16) ↩ ↩2 -
Google Cloud Blog, "Announcing a complete developer toolkit for scaling A2A agents on Google Cloud" — https://cloud.google.com/blog/products/ai-machine-learning/agent2agent-protocol-is-getting-an-upgrade (published July 31, 2025; original April 2025 announcement and June 2025 Linux Foundation contribution date, A2A v0.3 release; fetched 2026-07-16) ↩ ↩2
-
A2A Protocol / The Linux Foundation, "Definitions" — https://a2a-protocol.org/latest/definitions/ (
AgentCardSignaturemessage definition: a JWS — RFC 7515 JSON Web Signature — over an Agent Card, used for cryptographic identity verification; fetched 2026-07-16) ↩ -
GitHub,
a2aproject/A2Arepository — https://github.com/a2aproject/A2A (star count in the 23.9k-24.8k range across two direct fetches on 2026-07-16, latest release v1.0.1 on May 28, 2026, and six official SDKs listed under "Use the SDKs" — Python, Go, JavaScript, Java, .NET, and Rust; checked directly against the Linux Foundation's April 9, 2026 snapshot in 1 to confirm current figures) ↩ ↩2 -
Microsoft Foundry Blog, "Build and run agents at scale with Microsoft Foundry at Build 2026" — https://devblogs.microsoft.com/foundry/agent-service-build2026/ (Tina Schuchman, published June 2, 2026; confirms Foundry has supported outbound A2A — calling remote agents as a tool — "since the A2A tool launched," with incoming A2A (exposing a Foundry agent as an A2A endpoint) shipping as public preview at Build 2026; fetched 2026-07-16) ↩
-
A2A Protocol Python Quickstart tutorial — https://a2a-protocol.org/latest/tutorials/python/3-agent-skills-and-card/, https://a2a-protocol.org/latest/tutorials/python/4-agent-executor/, https://a2a-protocol.org/latest/tutorials/python/5-start-server/, and https://a2a-protocol.org/latest/tutorials/python/6-interact-with-server/ (Agent Skill/Agent Card fields, AgentExecutor interface, Starlette server setup with route factories, well-known agent card path, streaming client pattern; fetched 2026-07-16) ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
a2a-sdk1.1.0, installed directly viapip install "a2a-sdk[http-server]" uvicornin a Python 3.10.12 virtual environment and inspected withinspect.signature()againsta2a.types,a2a.server.agent_execution,a2a.server.tasks, anda2a.helpers; live server tested withcurlagainst/.well-known/agent-card.jsonand the JSON-RPC endpoint, including theA2A-Versionheader requirement found ina2a/utils/version_validator.pyanda2a/utils/constants.py(2026-07-16) ↩ ↩2 ↩3
