n4nAI

A2A agent cards explained: capability discovery

A2A agent cards are machine-readable manifests that let AI agents discover each other's capabilities over HTTP. Learn the spec, examples, and pitfalls.

n4n Team5 min read1,106 words

Audio narration

Coming soon — every post will get a voice note here.

A2A agent cards are JSON manifests that an autonomous agent publishes at a predictable URL to advertise its identity, supported interaction protocols, and concrete capabilities to other agents. The a2a agent cards spec gives multi-agent systems a standardized, machine-readable contract for discovery, replacing ad-hoc hardcoded endpoints and tribal knowledge with a single source of truth that any compliant client can parse.

What an A2A Agent Card Actually Contains

The card is not a full OpenAPI schema. It is a capability summary. A minimal card answers four questions: who are you, how do I talk to you, what can you do, and how do I prove I’m allowed.

Core identity fields

name, version, and description are plain metadata. protocols lists the transport and message formats the agent accepts, such as a2a/0.1 over HTTP or grpc. auth declares required schemes: none, bearer, oauth2, or mtls.

Capability blocks

The capabilities array is the heart of the document. Each entry describes a single callable skill:

{
  "id": "summarize_text",
  "description": "Compress a document to N bullet points",
  "input_schema": {"type": "object", "properties": {"text": {"type": "string"}, "max_points": {"type": "integer"}}},
  "output_schema": {"type": "array", "items": {"type": "string"}},
  "cost_hint": "0.001 USD per 1k tokens"
}

Schemas are standard JSON Schema drafts. They let a client validate a request before sending it, which matters when the caller is an LLM that may drift from the expected shape.

Full example card

{
  "name": "ResearchAggregator",
  "version": "1.4.0",
  "description": "Fetches and synthesizes web sources into structured briefs",
  "protocols": ["a2a/0.1"],
  "auth": {"scheme": "bearer", "token_url": "https://auth.example.com/token"},
  "capabilities": [
    {
      "id": "fetch_and_summarize",
      "description": "Given a query, return a cited summary",
      "input_schema": {"type": "object", "properties": {"query": {"type": "string"}}},
      "output_schema": {"type": "object"},
      "latency_p99_ms": 4200
    }
  ],
  "well_known": "https://research.example.com/.well-known/agent.json"
}

How Capability Discovery Works in Practice

An agent that needs a service does a GET on a peer’s card URL. The convention is /.well-known/agent.json, but cards can be referenced via a registry or returned from a handshake endpoint.

curl -s https://research.example.com/.well-known/agent.json | jq '.capabilities[].id'

The client parses the card, matches the required capability against its own task planner, and constructs a request conforming to the advertised input_schema. No human reads the card; the LLM orchestrator or a static router consumes it programmatically.

Discovery is pull-based. There is no mandatory central broker. That keeps deployments decentralized but shifts the burden of card freshness to the publisher. Use cache headers: a card with Cache-Control: max-age=300 is fine for most dynamic agents, but pair it with ETag to support conditional requests.

Why Agent Cards Matter for Multi-Agent Systems

Hardcoding agent URLs and payload shapes breaks the moment you scale past three agents. a2a agent cards let you compose pipelines at runtime. A planner can scan a registry of cards, pick the cheapest summarize_text capability, and retry on another if the first returns 503.

Loose coupling also enables safe canary deployments. You ship a new agent version with a new card advertising version: 2.0.0 and a superset of capabilities. Old clients keep calling the old card until they explicitly opt in by matching on semver constraints.

Without cards, teams build internal wikis that rot. With cards, the running agent is the documentation. When a capability is removed, the card reflects it immediately, and a disciplined client treats a missing capability as a hard error rather than a silent timeout.

A Concrete Example: Routing a Research Task

Suppose a coordinator agent receives “Summarize the latest quantum computing papers.” It holds a list of candidate cards. The code below filters for a capability matching the task and invokes it.

import json, requests

def find_capable_agent(cards, capability_id):
    for card in cards:
        if any(cap["id"] == capability_id for cap in card.get("capabilities", [])):
            return card
    raise ValueError("No agent provides " + capability_id)

registry = [{"url": "https://research.example.com/.well-known/agent.json"}]
cards = [requests.get(c["url"]).json() for c in registry]
target = find_capable_agent(cards, "fetch_and_summarize")
resp = requests.post(
    target["well_known"].replace("agent.json", "invoke"),
    headers={"Authorization": f"Bearer {token}"},
    json={"capability": "fetch_and_summarize", "input": {"query": "quantum computing"}}
)
print(resp.json())

This is deliberately naive. Production code adds schema validation with jsonschema, timeout budgets, and fallback to the next card on connection errors. The point stands: the card is the only interface contract the coordinator needs to bind to a remote skill.

Common Misconceptions

“Agent cards are just API docs”

Wrong. OpenAPI describes REST resources for human developers. a2a agent cards describe discrete skills for autonomous clients. The card omits HTTP verb trivia and focuses on capability semantics. A client does not care if the backend is REST or gRPC; it cares that fetch_and_summarize takes a query and returns an object.

“They replace authentication”

No. The auth block is a declaration, not enforcement. You still issue tokens, rotate them, and audit. The card tells the caller which scheme to use; it does not grant access. Never embed secrets in a card.

“Cards must be static files”

They can be static, but a dynamic endpoint that reflects current load or feature flags is better. Return a card generated per request with latency_p99_ms updated from your metrics pipeline. Just honor caching to avoid stampedes from dozens of polling agents.

“LLM agents don’t need schemas”

An LLM will hallucinate fields. The input_schema is a guardrail. Validate with jsonschema before sending. If the model emits max_points: "five", reject it before the remote agent wastes a token budget.

Implementing a Minimal Card Server

You can serve a card from any static host. For a Python service using FastAPI:

from fastapi import FastAPI
app = FastAPI()

@app.get("/.well-known/agent.json")
def agent_card():
    return {
        "name": "EchoAgent",
        "version": "0.1.0",
        "protocols": ["a2a/0.1"],
        "auth": {"scheme": "none"},
        "capabilities": [{"id": "echo", "input_schema": {"type": "string"}, "output_schema": {"type": "string"}}]
    }

Deploy this behind TLS. That’s a discoverable agent. Add a CI check that fails if the card does not match your actual handler signatures.

Versioning and Deprecation

Treat the card version as semver. When you break an input_schema, bump major. Mark old capabilities with "deprecated": true and a sunset date. Clients should log warnings when calling deprecated IDs.

{
  "id": "old_summarize",
  "deprecated": true,
  "sunset": "2025-12-31"
}

A coordinator should prefer the highest non-deprecated version that satisfies its constraint. If you run a registry, index cards by name and version to support queries like “give me the latest ResearchAggregator with capability fetch_and_summarize”.

Security Considerations

Assume the card is public. Anything published at /.well-known/ is reachable by anyone who guesses the URL. Put only the token_url or public key ID in auth, never the token itself. If your agent exposes sensitive capabilities, gate them behind an auth scheme that requires a signed request, and return a 401 on the invoke endpoint rather than advertising the capability to unauthenticated card fetches.

Use TLS mutual authentication for mtls schemes and document the expected certificate authority in the card. Clients should verify the server certificate against the CA pinned in their own trust store, not blindly trust the card’s self-description.

Testing Your Card

Validate the card against a JSON Schema of the a2a agent cards meta-schema before deploy. In Node:

npx ajv validate -s a2a-card-schema.json -d .well-known/agent.json

Write a contract test that boots the agent, fetches its own card, and asserts every capabilities[].id maps to a reachable invoke route. This catches drift when someone renames a handler but forgets the card.

Where the Spec Falls Short

The a2a agent cards format has no standard registry protocol yet. You must build your own discovery index or use DNS-SD. Also, there is no mandated capability taxonomy, so summarize_text from vendor A may not match vendor B’s text_summarize. Define internal naming conventions early and enforce them in code review.

Another gap is no standardized health signal inside the card. You can bolt on health_url, but clients should not assume it exists. Until the spec absorbs this, pair cards with a separate uptime probe.

Closing Thoughts

If you run more than two agents that call each other, publish a card. It is cheap, static or dynamically generated, and prevents the “where do I send this payload” Slack messages at 2am. The a2a agent cards pattern is boring infrastructure that earns its keep the first time you swap a provider without a code change.

Tagsa2aagent-cardscapability-discoverydefinition

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All agent-to-agent (a2a) communication protocols posts →