A2A Quick Start Guide
Deploy a production-ready Agent-to-Agent protocol in 60 minutes.
What is A2A?
A lightweight, standardized JSON-RPC 2.0 interface that lets AI agents discover your site's capabilities and safely call them.
Two Core Components
- Agent Card at
/.well-known/agent.json
- Describes capabilities, rate limits, and endpoint - Service Endpoint (e.g.,
/api/a2a/service
) - Exposes callable methods
5-Step Implementation (~60 minutes)
Step 1: Create Agent Card
/.well-known/agent.json
:
{
"schema_version": "2025-08",
"service": {
"name": "Blog A2A",
"endpoint": "/api/a2a/service",
"rate_limit": {"burst": 10, "per_minute": 60}
},
"capabilities": [
{"method": "blog.get_metadata"},
{"method": "blog.list_posts", "params": {"limit": "number", "tag": "string?"}},
{"method": "blog.get_post", "params": {"slug": "string"}},
{"method": "blog.search_posts", "params": {"q": "string"}}
],
"legal": {
"license": "CC-BY-4.0",
"attribution_required": true
}
}
Step 2: Implement JSON-RPC Endpoint
src/pages/api/a2a/service.ts
(Astro SSR example):
import type { APIContext } from 'astro';
type RpcReq = {
jsonrpc: '2.0';
id?: string|number|null;
method: string;
params?: any
};
type RpcRes = {
jsonrpc: '2.0';
id: string|number|null;
result?: any;
error?: { code: number; message: string; data?: any }
};
const ok = (id: RpcReq['id'], result: any): RpcRes =>
({ jsonrpc: '2.0', id: id ?? null, result });
const err = (id: RpcReq['id'], code: number, message: string, data?: any): RpcRes =>
({ jsonrpc: '2.0', id: id ?? null, error: { code, message, data } });
const allowOrigin = (origin?: string) =>
origin && /^https?:\/\/(.*\.)?(openai|anthropic|perplexity|bing|chatgpt)\.com$/.test(origin)
? origin : '*';
export async function POST(ctx: APIContext) {
const req = (await ctx.request.json()) as RpcReq;
const origin = ctx.request.headers.get('origin') ?? undefined;
// Basic CORS & security headers
const baseHeaders = {
'Access-Control-Allow-Origin': allowOrigin(origin),
'Access-Control-Allow-Headers': 'content-type',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Cache-Control': 'no-store',
'Content-Type': 'application/json'
};
try {
if (req.jsonrpc !== '2.0' || !req.method) {
return new Response(
JSON.stringify(err(req?.id, -32600, 'Invalid Request')),
{ headers: baseHeaders, status: 400 }
);
}
// Method dispatch
switch (req.method) {
case 'blog.get_metadata': {
return new Response(
JSON.stringify(ok(req.id, {
name: 'My Blog',
version: 1,
description: 'Technical blog about AI and development'
})),
{ headers: baseHeaders }
);
}
case 'blog.list_posts': {
const { limit = 10, tag } = req.params ?? {};
// TODO: Fetch from Content Collections
const posts = [
{ slug: 'intro-to-a2a', title: 'Introduction to A2A', date: '2025-08-14' },
{ slug: 'deep-agents', title: 'Deep Agents Pattern', date: '2025-08-13' }
];
return new Response(
JSON.stringify(ok(req.id, { posts, limit, tag })),
{ headers: baseHeaders }
);
}
case 'blog.get_post': {
const { slug } = req.params ?? {};
if (!slug) {
return new Response(
JSON.stringify(err(req.id, -32602, 'Missing slug')),
{ headers: baseHeaders, status: 400 }
);
}
// TODO: Fetch actual post content
return new Response(
JSON.stringify(ok(req.id, {
slug,
title: 'Sample Post',
content: 'Post content here...',
date: '2025-08-14'
})),
{ headers: baseHeaders }
);
}
case 'blog.search_posts': {
const { q } = req.params ?? {};
if (!q) {
return new Response(
JSON.stringify(err(req.id, -32602, 'Missing query')),
{ headers: baseHeaders, status: 400 }
);
}
// TODO: Implement search
return new Response(
JSON.stringify(ok(req.id, { q, matches: [] })),
{ headers: baseHeaders }
);
}
default:
return new Response(
JSON.stringify(err(req.id, -32601, 'Method not found')),
{ headers: baseHeaders, status: 404 }
);
}
} catch (e: any) {
return new Response(
JSON.stringify(err(null, -32603, 'Internal error', { message: e?.message })),
{ headers: baseHeaders, status: 500 }
);
}
}
Step 3: Update robots.txt
User-agent: *
Allow: /.well-known/
Allow: /api/a2a/
Allow: /feed
# License and attribution requirements
# Content licensed under CC-BY-4.0
# Attribution required when using content
Step 4: Quick Test
# Test metadata endpoint
curl -sX POST https://your-domain.com/api/a2a/service \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","method":"blog.get_metadata","id":1}' | jq
# List posts
curl -sX POST https://your-domain.com/api/a2a/service \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","method":"blog.list_posts","params":{"limit":5},"id":2}' | jq
# Get specific post
curl -sX POST https://your-domain.com/api/a2a/service \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","method":"blog.get_post","params":{"slug":"intro-to-a2a"},"id":3}' | jq
Step 5: Production Hardening
Checklist
- ✅ JSON-RPC 2.0 conformance
- ✅ ETags / 304s on read methods
- ✅ Rate limiting implementation
- ✅ CORS allowlist for agent origins
- ✅ Performance target: p95 ≤ 300ms
Add Rate Limiting
// Simple in-memory rate limiter (use Redis in production)
const rateLimits = new Map<string, { tokens: number; lastRefill: number }>();
function checkRateLimit(ip: string): boolean {
const now = Date.now();
const limit = rateLimits.get(ip) ?? { tokens: 60, lastRefill: now };
// Refill tokens (1 per second)
const elapsed = (now - limit.lastRefill) / 1000;
limit.tokens = Math.min(60, limit.tokens + elapsed);
limit.lastRefill = now;
if (limit.tokens < 1) return false;
limit.tokens--;
rateLimits.set(ip, limit);
return true;
}
Add ETags for Caching
import { createHash } from 'crypto';
function generateETag(content: any): string {
return createHash('md5')
.update(JSON.stringify(content))
.digest('hex');
}
// In your endpoint:
const result = { /* your data */ };
const etag = generateETag(result);
if (ctx.request.headers.get('if-none-match') === etag) {
return new Response(null, { status: 304, headers: { 'ETag': etag } });
}
return new Response(JSON.stringify(ok(req.id, result)), {
headers: { ...baseHeaders, 'ETag': etag }
});
Why Implement A2A?
Real Results (Jul 17 → Aug 13, 2025)
- 5.2% of traffic from AI agents
- ChatGPT: 61.5% engagement rate
- Perplexity & Bing: Active discovery and referrals
Key Insight
"Agents are becoming the discovery layer. GEO (Generative Engine Optimization) is the new SEO."
Next Steps
- Implement the basic endpoint
- Connect to your content source (CMS, database, etc.)
- Add production features (caching, rate limiting)
- Monitor agent traffic in your analytics
- Share results with the community!
Resources
Ship your A2A implementation this week and join the agent-discoverable web!