What Is the Agent2Agent (A2A) Protocol? A Developer Guide

Building an agent that needs the live web? Get a free TinyFish API key: TinySearch and TinyFetch are free, and new accounts get $8 for TinyAgent and TinyBrowser.
The A2A protocol (Agent2Agent) is an open standard that lets one AI agent find another agent, ask it to do a job, and get the result back, even when the two agents were built by different teams, on different frameworks, running on different servers. Each agent publishes an Agent Card that says what it can do and how to reach it. A client agent sends it a message, the remote agent tracks the work as a task, and the results come back as artifacts. Google introduced A2A in April 2025, the Linux Foundation has hosted it since June 2025, and the current specification is version 1.0.
TL;DR
- What it is: an open protocol for agent-to-agent communication. Agents collaborate as peers without sharing their memory, prompts or tools.
- Discovery: every A2A agent publishes an Agent Card, usually at
https://{domain}/.well-known/agent-card.json, listing its skills, endpoints and auth requirements. - Work: a client sends a message. The remote agent answers directly or creates a task that moves through states like
TASK_STATE_WORKINGandTASK_STATE_COMPLETED, and returns artifacts. - Transport: JSON-RPC 2.0, gRPC or HTTP+JSON over HTTPS, with streaming over Server-Sent Events and push notifications to a webhook for long jobs.
- A2A vs MCP: MCP connects one agent to its tools and data. A2A connects agents to other agents. Most real systems use both, and plain APIs sit underneath both.
This guide is for developers who are new to agent protocols. We're TinyFish, and we build the web layer that agents call to search, read and use websites, so the worked example at the end uses our API as the web layer of a remote agent. The protocol details apply whatever you build with.
At a glance: A2A, MCP and plain APIs
| A2A protocol | Model Context Protocol (MCP) | Plain API | |
|---|---|---|---|
| Connects | An agent to another agent | An agent (AI app) to tools and data | Any program to a service |
| Who's on the other side | An autonomous system that reasons, plans and keeps state | A tool, resource or prompt with defined inputs | Fixed endpoints written for developers |
| Discovery | Agent Card at a well-known URL, a registry or direct config | server/discover and tools/list | Docs, OpenAPI file |
| Unit of work | Task with a lifecycle (submitted, working, input required, completed) | Tool call with a result | Request and response |
| Long-running work | Built in: streaming, polling, push notifications | Optional Tasks extension | Up to each API |
| Wire format | JSON-RPC 2.0, gRPC or HTTP+JSON | JSON-RPC 2.0 over stdio or Streamable HTTP | Usually REST or gRPC |
| Governance | Linux Foundation (contributed by Google) | Agentic AI Foundation under the Linux Foundation (contributed by Anthropic) | The API's owner |
Where A2A came from
Google announced the Agent2Agent protocol on 9 April 2025, with more than 50 technology partners backing it. The problem it set out to solve was simple to describe: companies were building agents on different frameworks, and those agents had no common way to talk to each other. Every pairing needed custom glue.
On 23 June 2025, at Open Source Summit North America, the Linux Foundation launched the Agent2Agent project to give the protocol vendor-neutral governance. The specification and SDKs live on GitHub under the Apache 2.0 licence.
By its first anniversary, the project reported more than 150 supporting organisations, including AWS, Cisco, Google, IBM, Microsoft, Salesforce, SAP and ServiceNow. Microsoft built A2A into Azure AI Foundry and Copilot Studio, and AWS added support through Amazon Bedrock AgentCore Runtime. The same update highlighted version 1.0, the first stable specification, which added multi-protocol support, multi-tenancy, modernised security flows and signed Agent Cards.
If you read older tutorials, keep the version in mind. Pre-1.0 posts describe JSON-RPC methods like message/send and parts named TextPart and FilePart. In 1.0 the operations are SendMessage, GetTask and so on, task states are written as TASK_STATE_COMPLETED, and a part is a single object holding one of text, raw, url or data. The examples in this guide follow the current specification.
How A2A works, in one picture
There are two roles:
- A2A client (client agent): the app or agent that needs help. It finds a remote agent, sends it work and reads the results.
- A2A server (remote agent): the agent that does the work. It exposes an HTTP endpoint that speaks A2A and publishes an Agent Card.
A typical exchange has three steps:
- Discover. The client fetches the remote agent's Agent Card and checks its skills, endpoints and auth requirements.
- Authenticate. The client gets credentials out of band, following the security schemes the card declares, and sends them with each request.
- Communicate. The client sends a message. The remote agent replies with a message, or creates a task and reports progress until it finishes, fails or needs more input.

The key design choice is opacity. The remote agent never exposes its prompts, memory or tools. The client only sees the Agent Card, the task status and the artifacts. That lets a company offer an agent to partners without handing over its internals, which is exactly why A2A treats the other side as an agent and not as a tool.
Agent Cards: how agents discover each other
An Agent Card is a JSON document that works like a business card for an agent. In version 1.0 the required fields are name, description, version, supportedInterfaces, capabilities, defaultInputModes, defaultOutputModes and skills. Optional fields include provider, documentationUrl, securitySchemes, securityRequirements, signatures and iconUrl.
The specification lists three ways to find a card:
- Well-known URI:
https://{server_domain}/.well-known/agent-card.json - Registries or catalogs: curated lists of agents your organisation trusts
- Direct configuration: you hard-code the card URL or its contents
Here's a card for a hypothetical web research agent. It follows the 1.0 field names:
{
"name": "Web Research Agent",
"description": "Finds, reads and summarises current information from the public web, with sources.",
"version": "1.0.0",
"provider": {
"organization": "Example Co",
"url": "https://example.com"
},
"supportedInterfaces": [
{
"url": "https://research.example.com/a2a/v1",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0"
},
{
"url": "https://research.example.com/a2a/json",
"protocolBinding": "HTTP+JSON",
"protocolVersion": "1.0"
}
],
"capabilities": {
"streaming": true,
"pushNotifications": true
},
"securitySchemes": {
"apiKey": {
"apiKeySecurityScheme": {
"location": "header",
"name": "X-Agent-Key"
}
}
},
"securityRequirements": [{ "schemes": { "apiKey": { "list": [] } } }],
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/markdown", "application/json"],
"skills": [
{
"id": "live-web-research",
"name": "Live web research",
"description": "Searches the web, reads the most relevant pages and returns a sourced brief.",
"tags": ["research", "web", "citations"],
"examples": ["What changed in the EU AI Act implementation timeline this month?"]
},
{
"id": "site-task",
"name": "Interactive site task",
"description": "Operates a website to collect information that sits behind filters, forms or pagination.",
"tags": ["browser", "web automation"],
"examples": ["List the five cheapest flights from SFO to JFK on 4 February."]
}
]
}A few things worth knowing about cards:
- `supportedInterfaces` is ordered. The first entry is the preferred interface, and clients pick the first one they support.
- Skills are descriptive. They tell a client agent (or the model inside it) what this agent is good at, so it can pick the right agent for a job. They are not function signatures.
- Cards can be signed. Version 1.0 supports JSON Web Signatures over the card so a client can verify who published it before trusting it.
- There can be an extended card. If
capabilities.extendedAgentCardis true, an authenticated client can callGetExtendedAgentCardand see skills or details that the public card leaves out.
Messages, parts, tasks and artifacts
Four objects carry almost everything in A2A.
Message. One turn of communication. It has a role (ROLE_USER when the client sends it, ROLE_AGENT when the server does), a messageId, and one or more parts.
Part. A piece of content. In version 1.0 a part holds exactly one of:
text: a stringraw: file bytes, base64-encoded in JSONurl: a link to a filedata: any structured JSON value
Each part can also carry a mediaType, a filename and metadata.
Task. The unit of work. When a request needs more than a quick reply, the remote agent creates a task with an id, a contextId that groups related tasks and messages, and a status. The client can check it, list it, subscribe to it or cancel it.
Artifact. The output of a task, such as a report, a JSON dataset or an image. Artifacts are made of parts, have an artifactId, and can be streamed in pieces.
The task lifecycle
Version 1.0 defines these task states:
| State | What it means | Terminal? |
|---|---|---|
| TASK_STATE_SUBMITTED | Accepted and acknowledged | No |
| TASK_STATE_WORKING | Being processed | No |
| TASK_STATE_INPUT_REQUIRED | The agent needs more information from the client | Interrupted |
| TASK_STATE_AUTH_REQUIRED | The agent needs authentication to continue | Interrupted |
| TASK_STATE_COMPLETED | Finished successfully | Yes |
| TASK_STATE_FAILED | Finished with an error | Yes |
| TASK_STATE_CANCELED | Canceled before completion | Yes |
| TASK_STATE_REJECTED | The agent decided not to do it | Yes |
The two interrupted states are what make A2A feel like working with a colleague. A remote agent can stop and ask "which date did you mean?" and the client answers by sending another message on the same task.
How agents communicate
Three protocol bindings
The core operations are the same whichever binding you use. Version 1.0 defines three:
| Operation | JSON-RPC method | HTTP+JSON (REST) endpoint |
|---|---|---|
| Send a message | SendMessage | POST /message:send |
| Send and stream updates | SendStreamingMessage | POST /message:stream |
| Get a task | GetTask | GET /tasks/{id} |
| List tasks | ListTasks | GET /tasks |
| Cancel a task | CancelTask | POST /tasks/{id}:cancel |
| Resubscribe to a task's stream | SubscribeToTask | POST /tasks/{id}:subscribe |
| Register a webhook for a task | CreateTaskPushNotificationConfig | POST /tasks/{id}/pushNotificationConfigs |
| Get the extended Agent Card | GetExtendedAgentCard | GET /extendedAgentCard |
gRPC uses the same method names. An agent declares which bindings it supports in supportedInterfaces, and a client can send an A2A-Version header so both sides agree on the protocol version.
A basic request and response
Using the HTTP+JSON binding, a client asks a question:
POST /message:send HTTP/1.1
Host: research.example.com
Content-Type: application/a2a+json
X-Agent-Key: <your key>
{
"message": {
"role": "ROLE_USER",
"parts": [{ "text": "Summarise this week's announcements about A2A adoption, with sources." }],
"messageId": "9f1c2a7e-4b6d-4e2f-9a51-1d2c3b4a5e6f"
}
}And gets back a completed task with an artifact:
{
"task": {
"id": "c1a7f0d2-6b9e-4a8f-b3c2-5e4d6f7a8b9c",
"contextId": "7e2d9b1a-3c4f-4d5e-8f6a-9b0c1d2e3f4a",
"status": { "state": "TASK_STATE_COMPLETED" },
"artifacts": [
{
"artifactId": "a2b3c4d5-e6f7-4a8b-9c0d-1e2f3a4b5c6d",
"name": "Research brief",
"parts": [{ "text": "## A2A adoption this week\n\n1. ...\n\nSources: ..." }]
}
]
}
}With the JSON-RPC binding, the same call is a standard JSON-RPC 2.0 request with "method": "SendMessage" and the same object in params.
Streaming, polling and push notifications
A2A is built for work that takes time. The client has three ways to follow a task:
- Streaming: call
SendStreamingMessage(orPOST /message:stream) and read Server-Sent Events. You get the task, then status updates and artifact chunks as they arrive, ending with a terminal status. - Polling: call
GetTaskwith the task ID until it reaches a terminal state. - Push notifications: give the agent a webhook URL and a token in the request's
configuration. The agent posts status updates to your webhook, which suits jobs that run for minutes or hours, or clients that can't hold a connection open.
A streamed response looks like this:
data: {"task": {"id": "task-1", "contextId": "ctx-1", "status": {"state": "TASK_STATE_WORKING"}}}
data: {"artifactUpdate": {"taskId": "task-1", "contextId": "ctx-1", "artifact": {"parts": [{"text": "## Findings\n\n"}]}}}
data: {"statusUpdate": {"taskId": "task-1", "contextId": "ctx-1", "status": {"state": "TASK_STATE_COMPLETED"}}}Authentication and security
A2A doesn't invent its own auth. Security schemes in the Agent Card follow the OpenAPI model, and each scheme is exactly one of:
- API key (
apiKeySecurityScheme), sent in a header, query parameter or cookie - HTTP auth (
httpAuthSecurityScheme), such as Basic or Bearer - OAuth 2.0 (
oauth2SecurityScheme) - OpenID Connect (
openIdConnectSecurityScheme) - Mutual TLS (
mtlsSecurityScheme)
The flow is:
- Read
securitySchemesandsecurityRequirementsfrom the Agent Card. - Get credentials out of band, through whatever process the provider uses (a developer console, an OAuth flow, a signed contract).
- Send the credentials on every request using standard HTTP mechanisms.
The remote agent then decides what that caller is allowed to do. If a task hits something that needs more permission, it can move to TASK_STATE_AUTH_REQUIRED and wait.
Some practical habits:
- Verify Agent Card signatures before trusting a card from someone else's domain.
- Treat remote agents as untrusted input. Text in an artifact can contain prompt injection, the same as any web page. Don't let a remote agent's output trigger actions on your side without checks.
- Scope credentials per agent. Give each remote agent its own key so you can revoke one without breaking the rest.
- Log task IDs and context IDs. Agent-to-agent chains are hard to debug without a trail.
Remote agents vs local agents
A2A was designed for remote agents: agents that live behind someone else's HTTP endpoint, possibly in another company. That's where its features earn their keep, because you need discovery, auth, async tasks and opacity across an organisational boundary.
Inside one application, you often don't need A2A at all. If your planner, researcher and writer agents are functions in the same codebase, an agent framework's own handoff mechanism is simpler and faster. A useful rule of thumb:
- Same process, same team: use your framework's internal handoffs.
- Different service, different team or vendor: expose an A2A server and publish an Agent Card.
- A capability with fixed inputs and outputs: expose it as an MCP tool or a plain API, not as an agent.
The official SDKs make exposing an agent straightforward. They exist for Python (pip install a2a-sdk), JavaScript (npm install @a2a-js/sdk), Go, Java, .NET and Rust, and frameworks such as Google's ADK, LangGraph and BeeAI can expose their agents as A2A servers.
A2A vs MCP
The A2A project describes the difference with a nice image: MCP is vertical, A2A is horizontal.

- MCP deepens one agent. Every MCP server you connect hands the agent another tool, resource or prompt. A coding assistant with GitHub, a database and a browser connected over MCP can do more on its own.
- A2A connects agents across a boundary. The other agent might belong to another team or another company. Your agent asks it for help, and it works on the request with its own tools, which you never see.
| Question | Use MCP | Use A2A |
|---|---|---|
| Is the other side a tool with defined inputs and outputs? | Yes | No |
| Does the other side reason, plan and keep state across turns? | No | Yes |
| Do you control the other side's code and tools? | Often | Often not |
| Does the work take seconds or a single call? | Usually | Sometimes |
| Can the work take minutes or need follow-up questions? | Possible with the Tasks extension | Built in |
They are complementary, not competing. The A2A docs use an auto repair shop as the example: a customer's agent talks to the "shop manager" agent over A2A, the mechanic agent uses MCP to call its diagnostic scanner and repair manual, and the mechanic orders a part from a supplier's agent over A2A again. A2A connects the agents to each other, and MCP connects each agent to its own tools.
Both projects now sit under the Linux Foundation. Anthropic introduced MCP in November 2024 and donated it to the Agentic AI Foundation in December 2025, and the Linux Foundation's own A2A update describes the two as complementary layers.
A quick primer on MCP
If you're newer to the Model Context Protocol: an MCP host (an AI app like Claude Code or Cursor) creates an MCP client for each MCP server it connects to. Servers expose three core primitives:
- Tools: functions the model can call
- Resources: data the app can read for context
- Prompts: reusable templates
Messages use JSON-RPC 2.0, over stdio for local servers or Streamable HTTP for remote ones. The MCP architecture docs are the best starting point. For hands-on picks, see our guide to the best MCP servers for Claude Code, and our honest write-up of why our own CLI beat our MCP server for some coding agents.
MCP vs API
People also ask how MCP differs from a normal API, and the answer matters for where A2A fits.
An API is written for developers. You read the docs, write code against fixed endpoints, and handle auth, errors and formats yourself. An MCP server wraps capabilities so an AI application can find and use them at runtime: the model sees a list of tools with names, descriptions and JSON Schema inputs, and decides when to call them.
| Plain API | MCP | |
|---|---|---|
| Built for | Developers writing code | AI applications and their models |
| Discovery | Read the docs | tools/list at runtime |
| Integration effort | Custom code per API | One client works with any MCP server |
| Auth | Per API | Standard flows, OAuth recommended for remote servers |
| Best for | Deterministic code paths, high volume, tight control | Letting a model pick tools during a conversation |
MCP doesn't replace APIs. Most MCP servers call an API underneath. The same goes for A2A: a remote agent usually calls APIs and MCP tools to do its work. So the layers stack like this:
- A2A between agents
- MCP between an agent and its tools
- APIs underneath, doing the actual work
Worked example: delegating web research to a remote agent
Here's a pattern we see a lot. A company has an internal assistant that answers questions about competitors. It's good at the company's own data, but it can't reliably search the web, read current pages or operate sites with filters and forms. Instead of bolting all that onto the assistant, the team calls a separate web research agent over A2A.
The setup
- Orchestrator (A2A client): the internal assistant. It decides when a question needs fresh web information.
- Web research agent (A2A server): publishes the Agent Card shown earlier, with two skills,
live-web-researchandsite-task. - Web layer: inside the research agent, every search, page read and browser action goes through one web API. In this example that's TinyFish.
Step 1: discover and check the card
import httpx
card = httpx.get("https://research.example.com/.well-known/agent-card.json").json()
skills = {s["id"] for s in card["skills"]}
assert "live-web-research" in skills
interface = card["supportedInterfaces"][0] # first entry is preferredStep 2: delegate the task
The orchestrator sends a message, streams progress, and asks for structured output by including a data part:
{
"message": {
"role": "ROLE_USER",
"messageId": "3b8f1e2a-9c4d-4e5f-a6b7-c8d9e0f1a2b3",
"parts": [
{ "text": "Find the current published pricing for the three competitors below and note any change since last month." },
{ "data": { "competitors": ["acme.com", "globex.com", "initech.com"], "format": "table" } }
]
}
}Step 3: inside the remote agent, the web layer does the work
The research agent's planner turns the task into web steps. Each step maps to one capability behind a single TinyFish API key:
- TinySearch finds the right pages, such as each competitor's pricing page and recent announcements.
- TinyFetch reads those pages and returns clean Markdown the model can quote from.
- TinyAgent handles pages where the data sits behind interaction: a currency switcher, a plan toggle or a "show annual pricing" button.
- TinyBrowser gives the agent a remote browser over CDP when you want to drive it yourself with Playwright or Puppeteer.

With the Python SDK, the core of that web layer is short:
# pip install tinyfish | export TINYFISH_API_KEY=...
from tinyfish import TinyFish
web = TinyFish()
def research(competitor: str) -> dict:
# 1. Find the pricing page
hits = web.search.query(f"{competitor} pricing", location="US", language="en")
# 2. Read it as clean Markdown (top result here; a real agent would rank and check)
page = web.fetch.get_contents(urls=[hits.results[0].url])
# 3. If the page hides prices behind a toggle, let TinyAgent operate it
run = web.agent.run(
url=f"https://{competitor}/pricing",
goal="Switch to annual billing and return each plan name with its price as JSON.",
)
return {"page": page, "annual_prices": run.result}The remote agent's model writes the brief from those results, and the agent returns it to the orchestrator as an artifact: a Markdown table plus a data part with the raw numbers and source URLs. If a site needs a login the agent wasn't given, it moves the task to TASK_STATE_INPUT_REQUIRED or TASK_STATE_AUTH_REQUIRED instead of guessing.
Exact response fields vary by SDK version, so check the TinyFish docs when you wire this up. The shape of the workflow (search, then fetch, then act only when needed) stays the same.
Why split it this way
- The orchestrator stays simple. It knows how to delegate and read artifacts. It doesn't need browsers, proxies or anti-bot handling.
- The research agent owns web reliability. Search, fetch and browser sessions are one dependency with one key, instead of a scraper, a search API and a browser farm wired together by hand.
- Costs stay predictable. TinySearch and TinyFetch are free within their rate limits (TinySearch at 30 requests a minute and 500 an hour, TinyFetch at 150 URLs a minute and 1,000 a day). TinyAgent costs $0.016 per step and TinyBrowser $0.002 per browser-minute, so you only pay when the job needs a real browser. Current rates are on our pricing page.
- You can swap either side. Another team can call the same research agent over A2A from a different framework, and the orchestrator could switch to a different research agent by reading a different Agent Card.
If your agent runs inside an MCP host instead, the same web layer is available as an MCP server at https://agent.tinyfish.ai/mcp, with tools such as search, fetch_content, run_web_automation and create_browser_session. TinyFish doesn't ship an A2A server itself. It's the web infrastructure a remote agent calls. For more on choosing between the four, see which TinyFish API you should use.
Want to give your remote agent the live web? Start free with TinyFish: one API key for search, fetch, browser sessions and web agents.
When A2A is the wrong tool
A2A adds real moving parts: discovery, auth, task state and async delivery. Skip it when:
- All your agents live in one service. Use your framework's handoffs.
- The other side is really a function. A pricing lookup with fixed inputs is a tool. Expose it over MCP or a plain API.
- You need hard latency guarantees. An agent-to-agent chain adds a model's thinking time at every hop.
- You can't verify who the other agent is. Without signed cards or a trusted registry, you're sending work and data to an unknown party.
Common mistakes when starting with A2A
- Following pre-1.0 tutorials. Method names, part types and state names changed in 1.0. Check the spec version an SDK targets.
- Treating skills like function signatures. Skills describe what an agent is good at. Put strict input and output contracts in
dataparts and document them. - Holding a connection open for long jobs. Use push notifications or polling for anything that can take minutes.
- Trusting artifacts blindly. Output from another agent is untrusted input. Validate it before acting on it.
- One credential for every agent. Scope keys per remote agent so you can rotate or revoke them independently.
- Rebuilding web access in every agent. Put search, fetch and browser work behind one web layer and let agents share it.
Getting started
- Read the A2A specification sections on the data model and your chosen binding.
- Install an SDK, for example
pip install a2a-sdk, and run one of the official samples. - Expose one agent with one clear skill and publish its Agent Card at
/.well-known/agent-card.json. - Call it from a second agent using
SendMessage, then switch to streaming. - Give the remote agent the tools it needs over MCP or APIs. For live web work, a free TinyFish key covers search and fetch from day one.
The short DeepLearning.AI course on A2A, built with Google Cloud and IBM Research, is a good next step if you prefer video. And if you're new to agents in general, start with what an AI agent is and how to build one.
Building multi-agent systems on the live web? Get your free TinyFish API key: TinySearch and TinyFetch are free, with $8 to start on TinyAgent and TinyBrowser.
AI disclosure
Content on this website may be created or refined with the assistance of AI tools and is subject to human editorial review.



