Agent card
Served at /.well-known/agent-card.json and /.well-known/agent.json. Built by server.py::_build_agent_card.
Full shape
{
"name": "my-agent",
"description": "One-sentence statement of what this agent is for.",
"url": "http://my-agent:7870/a2a",
"version": "0.1.0",
"provider": {
"organization": "protoLabsAI",
"url": "https://github.com/protoLabsAI"
},
"capabilities": {
"streaming": true,
"pushNotifications": true,
"stateTransitionHistory": false,
"extensions": [
{"uri": "https://protolabs.ai/a2a/ext/cost-v1"}
]
},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/markdown"],
"skills": [
{
"id": "chat",
"name": "Chat",
"description": "General-purpose chat interface.",
"tags": ["template"],
"examples": ["hello", "what can you do?"]
}
],
"securitySchemes": {
"apiKey": {"type": "apiKey", "in": "header", "name": "X-API-Key"}
}
}Field reference
name
Short agent identifier. Same value you pass via AGENT_NAME.
description
One sentence. Used by planners and human consumers alike — write it for both audiences.
url
Must end with /a2a. The spec field points at the JSON-RPC endpoint, not the server root. Clients that use /a2a as-is are fine; clients that strip the path and POST to / get a 405 from FastAPI.
version
Your agent's version, not the A2A spec version. Semver is conventional.
capabilities
| Key | What it means |
|---|---|
streaming: true | message/stream works — consumers switch to the SSE path |
pushNotifications: true | tasks/pushNotificationConfig/* works — consumers can register webhooks |
stateTransitionHistory | Returns historical state per task. Template defaults to false |
extensions | See Extensions |
Lying about capabilities breaks consumers silently. If you disable streaming (for example), also strip the handler routes — otherwise clients see a mismatch.
skills
Each entry describes one dispatchable capability:
{
"id": "summarize_pr",
"name": "Summarize Pull Request",
"description": "Fetch a PR and return a three-bullet summary.",
"tags": ["github", "summarization"],
"examples": ["summarize https://github.com/..."],
"inputModes": ["text/plain"],
"outputModes": ["text/markdown"]
}id— sticky.cost-v1samples,effect-domain-v1declarations, and Workstacean's routing all key on it. Don't rename.tags— free-form. Workstacean's planner does substring matching against goals.examples— few-shot-ish prompts consumers can surface in their UI.inputModes/outputModes— overridedefaultInputModes/defaultOutputModesfor this specific skill.
defaultInputModes / defaultOutputModes
MIME types the agent accepts/produces. Template ships text/plain in, text/markdown out.
securitySchemes
Standard OpenAPI-style security scheme declaration. The template's default is an X-API-Key header.
Set the expected value via <AGENT_NAME>_API_KEY env var:
MY_AGENT_API_KEY=sk-abc123...If the env var is unset, the API key check is skipped entirely — useful for local dev, not appropriate for production.
Fork this file
The card lives in server.py::_build_agent_card. The template ships a placeholder with one chat skill and the cost-v1 extension declared. At a minimum, every fork should replace:
name+descriptionskills(deletechat, add your real ones)capabilities.extensions(addeffect-domain-v1if you mutate shared state)
Related
- Add a custom skill — walkthrough
- A2A endpoints — methods callers use to reach skills
- Extensions — the extensions the template handles