Skip to main content
Anvil supports Google’s Agent2Agent (A2A) protocol, letting your agents communicate with other A2A-compatible systems using a standardized interface. The same agent definition that serves a REST endpoint and an MCP server can also serve an A2A endpoint — you choose which protocols to enable. A2A, MCP, and REST share the same routing, tool registry, and observability machinery, so there is no duplication of logic.
The A2A specification is still evolving. Anvil isolates all protocol logic behind an adapter layer (A2AServer and a2aHttpHandler), so spec updates require changes only to the adapter — not to your agent definitions.

What is A2A?

Agent2Agent (A2A) is an open protocol proposed by Google for standardized communication between AI agents. It allows one agent to call another agent’s capabilities using a JSON-RPC 2.0 transport. Agents advertise their skills via an Agent Card — a JSON document served at a well-known URL — and callers discover and invoke those skills via the message/send method. Anvil’s implementation covers:
  • Agent Card — served at /.well-known/agent-card.json (and the alias /agent.json) so external agents can discover your skills
  • message/send — synchronous task execution; calls the target skill and returns a completed Task artifact
  • tasks/get — retrieve a previously created task by ID

Setting up an A2A server

1

Create an AgentRegistry with your agents

A2AServer dispatches incoming A2A requests to agents by skill ID. Each skill ID must match a registered agent name.
import { AgentRegistry } from 'anvil/agent';
import { LlmClient } from 'anvil/llm';

const client = new LlmClient({ /* your driver config */ });
const registry = new AgentRegistry();

registry.register('support', {
  client,
  model: 'gpt-4o-mini',
  system: 'You are a helpful customer support agent.',
});

registry.register('summarizer', {
  client,
  model: 'gpt-4o-mini',
  system: 'Summarize the provided text concisely.',
});
2

Create the A2AServer

import { A2AServer } from 'anvil/a2a';

const a2a = new A2AServer({
  registry,
  name: 'Acme AI Gateway',
  description: 'Customer-facing AI agents for Acme Corp.',
  url: 'https://api.acme.com/a2a',
  version: '1.0.0',
  // Optional: explicitly declare skills. Defaults to all registered agents.
  skills: [
    {
      id: 'support',
      name: 'Customer Support',
      description: 'Answers product and account questions.',
      tags: ['support', 'faq'],
    },
    {
      id: 'summarizer',
      name: 'Text Summarizer',
      description: 'Summarizes long text into key points.',
      tags: ['summarization', 'nlp'],
    },
  ],
  defaultSkill: 'support', // used when a request names no skill
});
3

Mount the HTTP handler

a2aHttpHandler returns a standard fetch-compatible handler you can mount directly in your Anvil app.
import { a2aHttpHandler } from 'anvil/a2a';
import { createApp } from 'anvil';

const app = createApp();

const handleA2A = a2aHttpHandler(a2a, {
  path: '/a2a',                              // JSON-RPC endpoint (default)
  cardPath: '/.well-known/agent-card.json',  // Agent Card URL (default)
});

// Mount the A2A handler alongside your existing routes
app.all('/a2a', handleA2A);
app.get('/.well-known/agent-card.json', handleA2A);
app.get('/agent.json', handleA2A); // alias, served automatically

export default app;

A2AServer API

Constructor options

OptionTypeRequiredDescription
registryAgentRegistryThe agent registry that backs skill execution
namestringHuman-readable name for this A2A server, advertised in the Agent Card
descriptionstringHuman-readable description advertised in the Agent Card
urlstringThe public URL of the A2A endpoint, included in the Agent Card
versionstringServer version string. Defaults to '0.0.1'
skillsA2ASkill[]Skills to advertise. Each id must be a registered agent name. Defaults to all agents in the registry
defaultSkillstringSkill to invoke when message/send includes no skillId. Defaults to the first skill

server.agentCard()

Returns the AgentCard object that describes this server’s capabilities. The HTTP handler serves this automatically at the card path.
const card = a2a.agentCard();
// {
//   protocolVersion: '0.3.0',
//   name: 'Acme AI Gateway',
//   capabilities: { streaming: false },
//   skills: [...],
//   ...
// }

server.handle(message)

Process a raw JSON-RPC 2.0 request object. Returns a JSON-RPC response or null for notifications (requests with no id). You can call this directly if you are integrating with a custom transport.
const response = await a2a.handle({
  jsonrpc: '2.0',
  id: 1,
  method: 'message/send',
  params: {
    message: {
      role: 'user',
      parts: [{ kind: 'text', text: 'What is your return policy?' }],
    },
    metadata: { skillId: 'support' },
  },
});

Supported JSON-RPC methods

MethodDescription
message/sendSend a message to a skill and receive a completed Task artifact synchronously
tasks/getRetrieve a previously created task by its id
Any other method returns a -32601 Unknown method error.

AgentCard and skill discovery

External agents discover your capabilities by fetching the Agent Card from /.well-known/agent-card.json. Anvil serves it automatically:
{
  "protocolVersion": "0.3.0",
  "name": "Acme AI Gateway",
  "description": "Customer-facing AI agents for Acme Corp.",
  "url": "https://api.acme.com/a2a",
  "version": "1.0.0",
  "capabilities": { "streaming": false },
  "defaultInputModes": ["text/plain"],
  "defaultOutputModes": ["text/plain"],
  "skills": [
    {
      "id": "support",
      "name": "Customer Support",
      "description": "Answers product and account questions.",
      "tags": ["support", "faq"]
    }
  ]
}

A2ASkill fields

FieldTypeRequiredDescription
idstringMust match a registered agent name in the AgentRegistry
namestringHuman-readable skill name
descriptionstringWhat the skill does — shown to calling agents during discovery
tagsstring[]Optional categorization tags

a2aHttpHandler options

OptionTypeDefaultDescription
pathstring'/a2a'The path for the JSON-RPC endpoint
cardPathstring'/.well-known/agent-card.json'The path to serve the Agent Card. The alias /.well-known/agent.json is always served at this path too

One definition, three protocols

Because A2AServer wraps the same AgentRegistry your application already uses, enabling A2A requires only a few extra lines — your agent logic and tools are defined once and reused:
import { createApp } from 'anvil';
import { a2aHttpHandler, A2AServer } from 'anvil/a2a';
import { registry } from './agents.js';  // shared AgentRegistry

const app = createApp();

// REST — agents are already mounted as HTTP route handlers
// A2A — same registry, A2A transport
const a2aServer = new A2AServer({ registry, name: 'My App', description: 'My Anvil app', url: 'https://example.com/a2a' });
app.all('/a2a', a2aHttpHandler(a2aServer));
app.get('/.well-known/agent-card.json', a2aHttpHandler(a2aServer));

export default app;
Enable only the transports your architecture requires. Each transport adapter adds a single route — you incur no overhead for protocols you do not mount.