You have two agents, built by two teams, and one has to hand work to the other. Function calling doesn't fit, because the second agent isn't a function: it takes minutes, asks follow-up questions, and might need a human to approve something. A2A, the Agent2Agent protocol, is an open spec for exactly that hand-off. MCP connects an agent to tools. A2A connects agents to each other.
The specification itself was read on 2026-10-08 and the agent card and security sections re-checked on 2026-10-09 (a2a-protocol.org, latest release listed as 1.0.0), and this post sticks to what it says. Where I did not read a part, I say so.
What is the A2A protocol, in one paragraph?
A2A defines how a client agent sends work to a remote agent, how the remote agent reports progress, and how results come back, without either side seeing the other's internals. The spec lists three transport bindings plus custom ones: JSON-RPC, gRPC and HTTP+JSON/REST. A client picks up the remote agent's capabilities from a published Agent Card, sends a message, and gets back either a direct reply or a Task it can follow until it finishes.
The "opaque" part matters. The remote agent could be a LangGraph app, a vendor's hosted service or a script. The client never sees its prompts, tools or memory. It only sees cards, messages, tasks and artifacts.
How is A2A different from MCP?
The A2A documentation frames it as vertical versus horizontal. MCP deepens a single agent by giving it tools and resources. A2A links agents as peers, covering discovery, negotiation and shared tasks. In the docs' words, tools are typically stateless primitives with structured inputs and outputs, while agents are more autonomous, keep state over longer interactions and hold multi-turn dialogues.
| Question | MCP | A2A |
|---|---|---|
| What sits on the other end? | A server exposing tools, resources, prompts | Another agent that decides for itself how to do the work |
| Typical interaction | One call, one structured result | Multi-turn, long-running, may pause for input |
| Who owns the reasoning? | The calling agent | Each agent owns its own |
| Discovery | You configure the server | The Agent Card at a well-known path |
| Output shape | Tool result | Task with Artifacts |
If this is new, start with what MCP is and MCP vs function calling vs tool use. The short version of the split, using the A2A docs' own example: a customer talks to a shop manager agent over A2A, the manager talks to a mechanic agent over A2A, and the mechanic uses MCP to drive a diagnostic scanner. Then it asks a parts supplier agent about stock, over A2A again.
The docs also say an A2A server can expose some well-defined, stateless skills as MCP-compatible resources. They add that this suits simple tool-like calls, and that A2A's strength is stateful collaboration beyond that.
What does an Agent Card contain?
The card is how an agent introduces itself. The spec puts discovery at https://{server_domain}/.well-known/agent-card.json. These top-level fields are marked required in the spec's table:
| Field | Purpose |
|---|---|
name, description | Who the agent is and what it does |
supportedInterfaces | Which bindings and endpoints it speaks |
version | The agent's own version |
capabilities | Flags such as streaming, pushNotifications, extendedAgentCard |
defaultInputModes, defaultOutputModes | Media types it accepts and produces |
skills | What it can do |
Optional fields are provider, documentationUrl, securitySchemes, securityRequirements, signatures and iconUrl. The nested objects have required fields of their own, which is where hand-written cards usually break:
| Object | Required fields |
|---|---|
AgentInterface (each entry of supportedInterfaces) | url, protocolBinding (JSONRPC, GRPC or HTTP+JSON), protocolVersion (for example "1.0"); tenant is optional |
AgentSkill (each entry of skills) | id, name, description, tags; examples, inputModes, outputModes and securityRequirements are optional |
The spec says the first entry in supportedInterfaces is the preferred one, and that clients pick the first interface they support. Production URLs for HTTP-based transports must be absolute HTTPS.
Cards may be signed. Per section 8.4, the card is canonicalized with the JSON Canonicalization Scheme (RFC 8785) and signed as a JSON Web Signature (RFC 7515); each entry in signatures needs a base64url protected header and a signature. Signing is optional (the spec says "MAY"), so an unsigned card is valid.
Here is a small checker I wrote from those tables. It checks that required fields exist, including inside supportedInterfaces and skills, but not their types or values, so treat it as a smoke test and not a validator.
import json
import sys
REQUIRED = [
"name", "description", "supportedInterfaces", "version",
"capabilities", "defaultInputModes", "defaultOutputModes", "skills",
]
INTERFACE_REQUIRED = ["url", "protocolBinding", "protocolVersion"]
SKILL_REQUIRED = ["id", "name", "description", "tags"]
def check(card: dict) -> list[str]:
problems = [f"missing required field: {f}" for f in REQUIRED if f not in card]
for i, iface in enumerate(card.get("supportedInterfaces", [])):
problems += [f"supportedInterfaces[{i}] missing: {f}" for f in INTERFACE_REQUIRED if f not in iface]
for i, skill in enumerate(card.get("skills", [])):
problems += [f"skills[{i}] missing: {f}" for f in SKILL_REQUIRED if f not in skill]
caps = card.get("capabilities", {})
if caps.get("streaming") is True:
print("note: card advertises streaming, so SendStreamingMessage is allowed")
if caps.get("pushNotifications") is not True:
print("note: pushNotifications is not true, so webhooks will be refused")
if "securitySchemes" not in card:
print("note: no securitySchemes declared, so clients cannot discover how to authenticate")
return problems
if __name__ == "__main__":
card = json.load(open(sys.argv[1]))
issues = check(card)
print("OK" if not issues else "\n".join(issues))
I ran it on 2026-10-09 against two hand-made cards. A complete minimal card (one JSON-RPC interface, one skill) printed:
note: card advertises streaming, so SendStreamingMessage is allowed
note: pushNotifications is not true, so webhooks will be refused
note: no securitySchemes declared, so clients cannot discover how to authenticate
OK
A card with defaultOutputModes removed, protocolVersion missing from its interface and tags missing from its skill printed:
note: card advertises streaming, so SendStreamingMessage is allowed
note: pushNotifications is not true, so webhooks will be refused
note: no securitySchemes declared, so clients cannot discover how to authenticate
missing required field: defaultOutputModes
supportedInterfaces[0] missing: protocolVersion
skills[0] missing: tags
The first two "note" lines come from spec statements: streaming needs capabilities.streaming to be true, and without capabilities.pushNotifications the server returns PushNotificationNotSupportedError. The third reflects section 7.3: clients learn which authentication schemes a server wants from securitySchemes.
How does an A2A task work?
Three objects do most of the work.
Message. One turn of conversation. Fields include messageId, contextId, taskId, role, parts, metadata, extensions and referenceTaskIds. Role is ROLE_USER or ROLE_AGENT. Each Part carries exactly one of text, raw (bytes, base64 in JSON), url or data (any JSON value), plus optional filename, mediaType and metadata.
Task. The unit of work the remote agent tracks. It has an id (generated by the server), an optional contextId, a required status, and optional artifacts, history and metadata. The status holds a state, an optional message and an optional timestamp.
Artifact. The deliverable. It has a required artifactId (unique within a task), a list of parts (at least one), and optional name, description, metadata and extensions.
The states, per the spec:
| State | Kind |
|---|---|
TASK_STATE_SUBMITTED, TASK_STATE_WORKING | In progress |
TASK_STATE_INPUT_REQUIRED, TASK_STATE_AUTH_REQUIRED | Interrupted: the agent is waiting on the client |
TASK_STATE_COMPLETED, TASK_STATE_FAILED, TASK_STATE_CANCELED, TASK_STATE_REJECTED | Terminal |
The contextId groups related tasks and messages into one logical conversation. The spec says clients should treat server-generated values as opaque and that agents must reject a request whose contextId and taskId don't match.
Two rules are easy to miss. First, the spec says Messages should not be used to deliver task outputs; return them as Artifacts. Second, SendMessage has a returnImmediately option that defaults to false. With false, the call waits until the task reaches a terminal or interrupted state. With true, it returns once the task is created and you follow progress another way.
How do you follow a long-running task?
The JSON-RPC method names the spec lists are SendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask, SubscribeToTask and GetExtendedAgentCard. Push notification configs have their own create, get, list and delete operations (the spec's gRPC names are CreateTaskPushNotificationConfig and friends).
Pick by how long the work runs:
- Seconds:
SendMessageand wait. - Minutes, client stays connected:
SendStreamingMessageorSubscribeToTask. Needscapabilities.streaming. Each streamed item is aStreamResponseholding exactly one oftask,message,statusUpdateorartifactUpdate. - Hours, client may disconnect: a push notification config. The agent POSTs updates to your webhook. Needs
capabilities.pushNotifications. - Anything:
GetTaskas a fallback poll.
The JSON-RPC binding wraps these in a standard JSON-RPC 2.0 envelope (jsonrpc, id, method set to the operation name such as SendMessage, and params). The spec's examples of the params payloads are skeletons, so I'm not printing a request body here that I haven't sent to a real agent. Copy a real one from the spec rather than from a blog.
What about security?
I read sections 7 and 13 of the spec on 2026-10-09. What they require, in plain terms:
- Transport. Production deployments must use encrypted transport (HTTPS, or TLS for gRPC). TLS 1.3 or later is recommended. Clients should verify the server's certificate against trusted CAs.
- Authentication. The card's
securitySchemestells the client what the server wants: API key, HTTP auth, OAuth 2.0, OpenID Connect or mutual TLS. The client gets credentials out of band, then sends them on every request. The server must authenticate every incoming request. A2A deliberately leaves credential acquisition outside the protocol. - Authorization. What an authenticated caller may do is "implementation-specific". But servers must check authorization on every operation, scope
ListTasksandGetTaskresults to what the caller is allowed to see, and do those checks before any lookup that could reveal whether a resource exists. - Mid-task authorization. An agent that needs an OAuth token or a human's approval moves the task to
TASK_STATE_AUTH_REQUIRED. The spec is explicit that this state, by itself, is not authorization for anything, and that a credential obtained for one step must not be assumed to cover later messages. It recommends receiving credentials out of band; passing them in-band through a chain of agents exposes them to every agent in the chain. - Webhooks. Agents sending push notifications must include the authentication from the config and should reject private, localhost and link-local webhook URLs to prevent SSRF. Clients must validate that a webhook call is authentic and should check the task ID matches a task they created.
- Extended cards.
GetExtendedAgentCardrequires authentication, and extended cards should not contain internal URLs or credentials.
One wrinkle in the spec text: the card field table names the field securityRequirements, but a few operation descriptions (for example GetExtendedAgentCard) still say AgentCard.security. Use the field table, and expect SDKs to lag the prose.
None of this tells you how to defend against a hostile or compromised peer agent. The spec secures the connection and the caller's identity, not the content. Treat another agent's output the way you'd treat a tool result: as untrusted input. The reasoning in prompt injection in tool-using agents applies unchanged, and the permission habits in the MCP security checklist carry over.
Do you actually need A2A?
Use this as a decision rule, not a law.
| Situation | Use |
|---|---|
| Agents in one codebase, one team | Plain function calls or your framework's handoffs |
| Agent needs a database or API | MCP or plain function calling |
| Agent hands a long job to a service another team runs | A2A is a good fit |
| You must swap the remote agent's vendor without code changes | A2A is a good fit |
| You only want to split a big prompt | You don't need multiple agents; see when to use multiple agents |
My opinion: most small projects don't need A2A yet. Cross-team, cross-vendor and long-running hand-offs are where it earns its keep. If you can't name the second team, you probably want a function. For background on multi-agent design, the multi-agent systems lesson covers patterns that work without a protocol.
What I could not verify
- The binding sections (9 to 11) beyond the method names and base request shape, and the extension mechanism.
- How many production systems use A2A, and which vendors ship it. The page I read doesn't say, and I'm not guessing.
- Library and SDK status. I didn't test any A2A SDK, so no code here sends a real A2A request.
Re-check version numbers before building: this was 1.0.0 on 2026-10-08, and the spec says clients send an A2A-Version header, with unsupported versions returning VersionNotSupportedError.



