What Is an Agent Card and How Do You Implement It in 2026?

An agent card is the JSON file an agent fetches first to learn what your service can do and how to call it. This page gives you the exact v1.0 field set, three working ways to publish it, and the mistakes that make a card unreadable.

CategoryAgent & AI protocols StatusStable Maintained byA2A / Linux Foundation Glippy checkAgent Interactivity (category 10)

Agent Card (/.well-known/agent-card.json) is the JSON manifest an A2A server publishes so that other agents can discover its identity, skills, protocol endpoints and authentication requirements before sending a single request. The A2A specification requires every A2A server to make one available, and agent-card.json is registered with IANA as a permanent well-known URI suffix under the Linux Foundation. If you are exposing an agent or an API for other agents to call, this file is the thing that makes you callable without a bilateral integration.

Why Agent Card matters for AI visibility

Agents do not crawl your site to work out whether you are worth calling. They request one fixed path, parse the JSON, and either add you to their routing table or move on. Because the path is a registered well-known URI rather than a convention, a client written against the specification knows exactly where to look on any domain, and a missing or malformed file is a silent disqualification rather than a soft ranking penalty. This is a harder gate than most GEO surfaces: there is no partial credit for a card that fails to parse.

The card is also your contract surface. The skills array, with its tags and examples, is what a routing agent matches an incoming user request against, so a card listing one vague skill loses to a card listing four specific ones with realistic example prompts. Everything else on the card, the transports in supportedInterfaces, the schemes in securitySchemes, and the optional JWS signatures, decides whether the calling agent can connect at all and whether it should trust what it just read.

Where the spec lives

The card is defined by protobuf in the A2A repository and rendered into prose on the specification site, so the proto file is the authority when the two disagree.

Three ways to implement Agent Card

Start with a static file if you have one agent and one endpoint: it is a text file and it is done in ten minutes. Move to serving the card from the application when the endpoint URL differs per environment or the skill list is generated from code, because that is where hand-maintained cards drift out of date. Add a JWS signature when other organisations call your agent and need to prove the card they fetched is the one you published.

01

Static file: the minimum valid card

Every field below is REQUIRED in A2A v1.0 except provider. Arrays marked required must carry at least one element, so an empty skills array is not a valid card. Drop this at the root of the origin your agent is reachable on.

json/.well-known/agent-card.json
{
  "name": "Northwind Support Agent",
  "description": "Answers order and returns questions for Northwind customers, and files support tickets on their behalf.",
  "version": "1.0.0",
  "supportedInterfaces": [
    {
      "url": "https://api.example.com/a2a/v1",
      "protocolBinding": "JSONRPC",
      "protocolVersion": "1.0"
    }
  ],
  "provider": {
    "organization": "Northwind Traders",
    "url": "https://www.example.com"
  },
  "capabilities": {
    "streaming": false,
    "pushNotifications": false
  },
  "defaultInputModes": ["text/plain", "application/json"],
  "defaultOutputModes": ["text/plain", "application/json"],
  "skills": [
    {
      "id": "order-status",
      "name": "Order status lookup",
      "description": "Returns the current shipping status for an order given its reference number.",
      "tags": ["orders", "shipping", "support"],
      "examples": ["Where is order NW-10422?", "Has NW-10422 shipped yet?"]
    }
  ]
}

What this does: A client fetches this file, reads the first entry of supportedInterfaces as your preferred transport, and can immediately open a JSON-RPC session against that URL. The examples strings are what a routing agent compares an incoming user request against, so write them as real prompts rather than as documentation.

02

Serve it from the app so it cannot drift

Use this when the endpoint URL changes per environment, or when you want the card to carry cache validators. The specification asks servers to send a Cache-Control max-age and an ETag derived from the card version or a hash of its content, so clients can revalidate with If-None-Match instead of re-downloading.

javascriptserver.js
// server.js - npm i express
const express = require('express');
const { createHash } = require('node:crypto');
const { readFileSync } = require('node:fs');

const app = express();
const ORIGIN = process.env.PUBLIC_ORIGIN || 'https://api.example.com';

// Build the body once at boot so the ETag is stable across requests.
const card = JSON.parse(readFileSync('./agent-card.json', 'utf8'));
card.supportedInterfaces = [
  { url: `${ORIGIN}/a2a/v1`, protocolBinding: 'JSONRPC', protocolVersion: '1.0' },
];
const body = JSON.stringify(card);
const etag = `"${createHash('sha256').update(body).digest('base64url').slice(0, 27)}"`;

function sendCard(req, res) {
  res.set({
    'Content-Type': 'application/json; charset=utf-8',
    'Cache-Control': 'public, max-age=3600',
    'Access-Control-Allow-Origin': '*',
    ETag: etag,
  });
  if (req.headers['if-none-match'] === etag) return res.status(304).end();
  res.send(body);
}

app.get('/.well-known/agent-card.json', sendCard);
// Pre-1.0 clients still probe the old path. Keep it while they are in your logs.
app.get('/.well-known/agent.json', sendCard);

app.listen(8080);

What this does: The body and its ETag are computed once at boot, so repeat fetches from the same client cost a 304 rather than a full transfer. The second route keeps the pre-0.3 path /.well-known/agent.json answering for older clients, which is a local compatibility decision: the v1.0 specification does not mention that path at all.

03

Sign the card with a detached JWS

Signing is optional in the specification, but clients SHOULD verify at least one signature before trusting a card, so signing is what lets a cautious caller accept yours. The payload has to be canonicalised with RFC 8785 and the signatures field itself excluded, otherwise the verifier rebuilds different bytes and the check fails.

javascriptsign-agent-card.mjs
// sign-agent-card.mjs - npm i jose canonicalize
import { readFileSync, writeFileSync } from 'node:fs';
import canonicalize from 'canonicalize';
import { FlattenedSign, importPKCS8 } from 'jose';

const ALG = 'ES256';
const KID = 'agent-card-2026-01';
const JKU = 'https://www.example.com/.well-known/jwks.json';

const card = JSON.parse(readFileSync('agent-card.json', 'utf8'));
delete card.signatures;                       // never sign the signatures field
const payload = new TextEncoder().encode(canonicalize(card)); // RFC 8785

const key = await importPKCS8(readFileSync('signing-key.pem', 'utf8'), ALG);
const jws = await new FlattenedSign(payload)
  .setProtectedHeader({ alg: ALG, typ: 'JOSE', kid: KID, jku: JKU })
  .sign(key);

card.signatures = [{ protected: jws.protected, signature: jws.signature }];
writeFileSync('agent-card.json', JSON.stringify(card, null, 2));
console.log('signed with', KID);

What this does: It writes a signatures array back into the card holding the base64url protected header and the signature, with the payload left out because the verifier reconstructs it from the card. Publish the matching public key at the jku URL as a JWKS so clients can resolve the kid without contacting you.

Implementation guidelines

These are the failures that show up once a card is live and other people's clients are parsing it.

  1. Get the path exactly right. It is /.well-known/agent-card.json at the root of the origin, served over HTTPS with a 200 and a JSON content type. A redirect chain, an HTML error page returned with status 200, or the file parked one directory deeper all read as no card at all.
  2. Treat the required list as required. name, description, version, supportedInterfaces, capabilities, defaultInputModes, defaultOutputModes and skills are all marked REQUIRED in the proto. provider, documentationUrl, iconUrl, securitySchemes, securityRequirements and signatures are optional.
  3. Order supportedInterfaces by preference. The first entry is the preferred interface and clients select the first one they support. Each entry needs its own url, protocolBinding (JSONRPC, GRPC or HTTP+JSON, or a URI for a custom binding) and protocolVersion. If you set tenant on an entry, clients must echo that exact value on every request to that interface.
  4. Version the card and the validator together. Bump version whenever the skills or interfaces change, and derive the ETag from that value or from a hash of the body. Clients cache cards aggressively, so a skill you added without a version bump can take a day to be noticed.
  5. Keep the public card public. Internal hostnames, rate limits, unreleased skills and anything customer-specific belong in the extended card, which is fetched only after authentication. Declare it with capabilities.extendedAgentCard: true and serve it at GET /extendedAgentCard on the REST binding.
  6. Migrate off the 0.3 shape deliberately. Version 1.0 removed the top-level url, protocolVersion and supportsAuthenticatedExtendedCard fields and dropped the kind discriminator on polymorphic objects. The published JSON Schema sets additionalProperties: false, so leftovers from an older card are hard validation errors, not harmless extras.
  7. Validate on every deploy. Point Ajv at the generated schema bundle and assert your card against #/$defs/AgentCard in CI. It is a five-line test and it catches the whole class of typos that make you silently invisible to callers.

Do this, not that

Do

  • Give every skill a stable id, at least one tags entry, and examples written as prompts a user would actually type.
  • Send Cache-Control: public, max-age=3600 and an ETag, and answer If-None-Match with a 304.
  • Describe how to authenticate in securitySchemes, using the wrapper key that matches the scheme, such as httpAuthSecurityScheme or openIdConnectSecurityScheme.
  • Keep /.well-known/agent.json answering as an alias for as long as pre-1.0 clients still appear in your access logs.

Do not

  • Do not leave a top-level url or protocolVersion on the card. Both moved inside supportedInterfaces in v1.0 and the schema rejects unknown properties.
  • Do not declare supportsAuthenticatedExtendedCard. It is now capabilities.extendedAgentCard.
  • Do not sign the pretty-printed file. Canonicalise with RFC 8785 and remove signatures first, or no client will be able to verify it.
  • Do not advertise skills you cannot route to. A skill that returns an error on its own examples prompt costs you more trust than omitting it would.

How Glippy checks this

Glippy requests /.well-known/agent-card.json at the root of the domain once per crawl, with a five second timeout, and scores the result under Agent Interactivity, category 10. A response that parses as JSON and carries name, description and a version field scores the full four points, and Glippy reports how many entries your skills array holds. A card that responds but is missing those fields scores two and is raised as a warning. No card at all is reported as information rather than a penalty, because most pages are not agent hosts, but if a UCP profile on the same domain advertises a2a while the card returns 404, that contradiction is flagged as a warning. The same discovery sweep also looks for an MCP server card, an agent skills index and a schemamap, and any one of them is enough to mark the domain as having an agent discovery surface. You can see the whole category on the AI agent accessibility checker.

Check your Agent Card setup

Glippy runs 240+ checks across 16 categories on any page, including Agent Interactivity (category 10). No sign-up required.

Frequently asked questions

/.well-known/agent-card.json is the current and only path named in the A2A specification, and it is the suffix registered with IANA. The older /.well-known/agent.json was the pre-0.3 location; the v1.0 specification does not mention it, and the A2A Python SDK carried a legacy constant for it at version 0.3.0 but removed it by 1.0.0. Publish the new path as the real card and keep the old path as an alias only if you can still see clients requesting it.

Eight fields are marked REQUIRED in the A2A proto: name, description, version, supportedInterfaces, capabilities, defaultInputModes, defaultOutputModes and skills. Arrays marked required must contain at least one element, so an empty skills or supportedInterfaces array is invalid. Each AgentSkill in turn requires id, name, description and tags, with examples, inputModes and outputModes optional.

No. The A2A specification says cards MAY be signed using JWS as defined in RFC 7515, and that clients SHOULD verify at least one signature when signatures are present. If nobody outside your organisation calls your agent, an unsigned card over HTTPS is fine. If they do, signing is what lets a careful client distinguish your card from one served by a compromised CDN or an impostor domain, and multiple signatures are allowed so you can rotate keys without a gap.

They describe different things and they are at different levels of maturity. An agent card describes an agent as a peer you delegate a task to, and its path is a permanent registered well-known URI, so it is safe to build against. MCP describes tools and resources a model calls directly, and its equivalent discovery document is still a proposal: the server card work under SEP-1649 and SEP-2127 is being developed as an experimental extension, with the endpoint convention still moving. Publish an agent card today; treat an MCP server card as something to revisit when the proposal settles.

Reviewed against the primary sources on . These standards move quickly, so check the linked specs before you ship.

Check your site for AI search readiness

Start free with the Glippy Chrome extension for instant page checks. Scaling up? Automate audits across many URLs and your whole sitemap with the Glippy MCP server.

Add Glippy to Chrome – free Automate with the MCP server →