A JavaScript SDK for building Agent2Agent (A2A) Protocol servers with multi-framework and edge runtime support.
Fork of a2aproject/a2a-js β Tracks upstream v0.3.6 with added multi-framework and edge runtime support.
- π― Multi-Framework: Express, Hono, Elysia, itty-router, Fresh, and Web Standard
- β‘ Edge Runtime Native: Cloudflare Workers, Deno, Bun β no compatibility layers needed
- π Universal JavaScript: Built on web-standard APIs (
EventTarget,Request/Response) - π SSE Streaming: Full Server-Sent Events support across all frameworks
- π Pluggable Logger: Console, JSON, or custom logging implementations
- π¦ Modular Architecture: Import only what you need from
server/core - π Full A2A Protocol: Complete implementation of the Agent2Agent specification
npm install @drew-foxall/a2a-js-sdk
# or
pnpm add @drew-foxall/a2a-js-sdk
# or
yarn add @drew-foxall/a2a-js-sdkInstall the framework you want to use:
# For Express (Node.js)
npm install express
# For Hono (Edge/Serverless)
npm install hono
# For Hono on Node.js (development)
npm install hono @hono/node-serverThe examples below show the same "Hello Agent" implemented for different environments.
First, define your agent card and executor (shared across all implementations):
// shared/agent.ts
import { v4 as uuidv4 } from 'uuid';
import type { AgentCard, Message } from '@drew-foxall/a2a-js-sdk';
import {
AgentExecutor,
RequestContext,
ExecutionEventBus,
} from '@drew-foxall/a2a-js-sdk/server';
export const helloAgentCard: AgentCard = {
name: 'Hello Agent',
description: 'A simple agent that says hello.',
protocolVersion: '0.3.0',
version: '0.1.0',
url: 'http://localhost:4000/',
skills: [{ id: 'chat', name: 'Chat', description: 'Say hello', tags: ['chat'] }],
capabilities: { pushNotifications: false },
defaultInputModes: ['text'],
defaultOutputModes: ['text'],
};
export class HelloExecutor implements AgentExecutor {
async execute(ctx: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
const response: Message = {
kind: 'message',
messageId: uuidv4(),
role: 'agent',
parts: [{ kind: 'text', text: 'Hello, world!' }],
contextId: ctx.contextId,
};
eventBus.publish(response);
eventBus.finished();
}
cancelTask = async (): Promise<void> => {};
}The original Express implementation from upstream. Best for traditional Node.js servers.
// server-express.ts
import express from 'express';
import {
DefaultRequestHandler,
InMemoryTaskStore,
} from '@drew-foxall/a2a-js-sdk/server';
import { A2AExpressApp } from '@drew-foxall/a2a-js-sdk/server/express';
import { helloAgentCard, HelloExecutor } from './shared/agent';
const requestHandler = new DefaultRequestHandler(
helloAgentCard,
new InMemoryTaskStore(),
new HelloExecutor()
);
const app = express();
const a2aApp = new A2AExpressApp(requestHandler);
// Setup routes with optional base path and middleware
a2aApp.setupRoutes(app, '/a2a', [/* middlewares */]);
app.listen(4000, () => {
console.log('π Express A2A server running on http://localhost:4000');
});Options:
// With custom user extractor for authentication
const a2aApp = new A2AExpressApp(requestHandler, async (req) => {
// Extract user from request (e.g., from JWT token)
return req.user ?? new UnauthenticatedUser();
});
// Setup with REST API enabled (in addition to JSON-RPC)
a2aApp.setupRoutes(app, '/a2a', [], '.well-known/agent-card.json');Best for Cloudflare Workers, Vercel Edge Functions, Deno Deploy, and other edge environments.
// worker.ts - Cloudflare Workers / Edge Runtime
import { Hono } from 'hono';
import {
DefaultRequestHandler,
InMemoryTaskStore,
} from '@drew-foxall/a2a-js-sdk/server';
import { A2AHonoApp } from '@drew-foxall/a2a-js-sdk/server/hono';
import { helloAgentCard, HelloExecutor } from './shared/agent';
const requestHandler = new DefaultRequestHandler(
helloAgentCard,
new InMemoryTaskStore(),
new HelloExecutor()
);
const app = new Hono();
const a2aApp = new A2AHonoApp(requestHandler, {
enableRest: true, // Enable REST API endpoints
logger: ConsoleLogger.create(),
});
a2aApp.setupRoutes(app);
export default app;With Authentication:
import { A2AHonoApp, UserBuilder } from '@drew-foxall/a2a-js-sdk/server/hono';
const userBuilder: UserBuilder = async (request) => {
const token = request.headers.get('Authorization')?.replace('Bearer ', '');
if (token) {
const user = await validateToken(token);
return user;
}
return new UnauthenticatedUser();
};
const a2aApp = new A2AHonoApp(requestHandler, { userBuilder });Deploy to Cloudflare Workers:
# wrangler.toml - No nodejs_compat needed!
name = "a2a-hello-agent"
main = "worker.ts"
compatibility_date = "2024-01-01"wrangler deployBest for Bun-native applications with excellent TypeScript support.
// server-elysia.ts
import { Elysia } from 'elysia';
import { DefaultRequestHandler, InMemoryTaskStore } from '@drew-foxall/a2a-js-sdk/server';
import { A2AElysiaApp } from '@drew-foxall/a2a-js-sdk/server/elysia';
import { helloAgentCard, HelloExecutor } from './shared/agent';
const requestHandler = new DefaultRequestHandler(
helloAgentCard,
new InMemoryTaskStore(),
new HelloExecutor()
);
const a2aApp = new A2AElysiaApp(requestHandler, { enableRest: true });
const routes = a2aApp.getRoutes('/a2a');
const app = new Elysia();
routes.forEach(route => {
app[route.method](route.path, route.handler);
});
app.listen(4000);Best for minimal Cloudflare Workers with the smallest bundle size.
// worker.ts
import { Router } from 'itty-router';
import { DefaultRequestHandler, InMemoryTaskStore } from '@drew-foxall/a2a-js-sdk/server';
import { A2AIttyRouterApp } from '@drew-foxall/a2a-js-sdk/server/itty-router';
import { helloAgentCard, HelloExecutor } from './shared/agent';
const requestHandler = new DefaultRequestHandler(
helloAgentCard,
new InMemoryTaskStore(),
new HelloExecutor()
);
const a2aApp = new A2AIttyRouterApp(requestHandler, { enableRest: true });
const routes = a2aApp.getRoutes('/a2a');
const router = Router();
routes.forEach(route => {
router[route.method.toLowerCase()](route.pattern, route.handler);
});
export default { fetch: router.handle };Best for Deno's web framework with file-based routing.
// routes/a2a/[...path].ts
import { DefaultRequestHandler, InMemoryTaskStore } from '@drew-foxall/a2a-js-sdk/server';
import { A2AFreshApp } from '@drew-foxall/a2a-js-sdk/server/fresh';
import { helloAgentCard, HelloExecutor } from '../../shared/agent.ts';
const requestHandler = new DefaultRequestHandler(
helloAgentCard,
new InMemoryTaskStore(),
new HelloExecutor()
);
const a2aApp = new A2AFreshApp(requestHandler, { enableRest: true });
export const handler = a2aApp.createHandlers('/a2a');The client works with any A2A server implementation:
import { A2AClient } from '@drew-foxall/a2a-js-sdk/client';
import { v4 as uuidv4 } from 'uuid';
const client = await A2AClient.fromCardUrl('http://localhost:4000/.well-known/agent-card.json');
const response = await client.sendMessage({
message: {
messageId: uuidv4(),
role: 'user',
parts: [{ kind: 'text', text: 'Hi there!' }],
kind: 'message',
},
});
console.log('Response:', response);This SDK uses a layered architecture separating framework-agnostic logic from framework-specific implementations:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Transport Layer β
β ββββββββββββββββββββββββββ ββββββββββββββββββββββββ β
β β JsonRpcTransportHandlerβ β RestTransportHandler β β
β ββββββββββββ¬ββββββββββββββ ββββββββββββ¬ββββββββββββ β
β βββββββββββββββββ¬ββββββββββββββ β
β βΌ β
β ββββββββββββββββββββββββββββββββββ β
β β A2ARequestHandler β β
β β (Framework-Agnostic Logic) β β
β ββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββΌβββββββββββββββββββ
βΌ βΌ βΌ
βββββββββββββββββ βββββββββββββββββ βββββββββββββββββ
β server/core β β server/hono β βserver/express β
β(Web Standard) β β server/elysia β β (Original) β
β β β etc... β β β
βββββββββββββββββ βββββββββββββββββ βββββββββββββββββ
// Core utilities (logging, routes, streaming)
import {
ConsoleLogger, JsonLogger, NoopLogger,
HTTP_STATUS, REST_ROUTES, AGENT_CARD_ROUTE,
processStream, createSSEEvent,
} from '@drew-foxall/a2a-js-sdk/server/core';
// Framework implementations
import { A2AExpressApp } from '@drew-foxall/a2a-js-sdk/server/express'; // Original Express
import { A2AExpressApp } from '@drew-foxall/a2a-js-sdk/server/express-adapter'; // Core-based Express
import { A2AHonoApp } from '@drew-foxall/a2a-js-sdk/server/hono';
import { A2AElysiaApp } from '@drew-foxall/a2a-js-sdk/server/elysia';
import { A2AIttyRouterApp } from '@drew-foxall/a2a-js-sdk/server/itty-router';
import { A2AFreshApp } from '@drew-foxall/a2a-js-sdk/server/fresh';
import { A2AWebStandardApp } from '@drew-foxall/a2a-js-sdk/server/web-standard';| Framework | Import Path | Best For |
|---|---|---|
| Express (Original) | server/express |
Node.js servers (reference implementation) |
| Express (Core-based) | server/express-adapter |
Parity testing with edge implementations |
| Hono | server/hono |
Cloudflare Workers, Deno, Bun |
| Elysia | server/elysia |
Bun-native with excellent TypeScript |
| itty-router | server/itty-router |
Lightweight Cloudflare Workers |
| Fresh | server/fresh |
Deno's web framework |
| Web Standard | server/web-standard |
Any runtime with Request/Response |
This SDK uses web-standard APIs, making it compatible with all modern JavaScript runtimes:
| Runtime | Status | Notes |
|---|---|---|
| Cloudflare Workers | β Native | No nodejs_compat needed |
| Vercel Edge Functions | β Native | Full support |
| Deno Deploy | β Native | No npm shims required |
| Bun | β Native | Full web API support |
| Node.js 15+ | β Native | EventTarget built-in |
| Browsers | β Native | Universal JavaScript |
| Traditional (Express) | Edge (Hono) |
|---|---|
| Runs on dedicated servers | Runs at the edge, close to users |
| Cold starts in seconds | Cold starts in milliseconds |
Requires nodejs_compat on CF Workers |
Native edge runtime support |
| Full Node.js API access | Web-standard APIs only |
| Best for complex backends | Best for low-latency agents |
// Server: Publish events
eventBus.publish({ kind: 'status-update', taskId, status: { state: 'working' }, final: false });
eventBus.publish({ kind: 'artifact-update', taskId, artifact: { ... } });
eventBus.publish({ kind: 'status-update', taskId, status: { state: 'completed' }, final: true });
eventBus.finished();
// Client: Consume stream
const stream = client.sendMessageStream(params);
for await (const event of stream) {
console.log(event.kind, event);
}All framework implementations support middleware:
// Express
a2aApp.setupRoutes(app, '/a2a', [authMiddleware, loggingMiddleware]);
// Hono
a2aApp.setupRoutes(app, '/a2a', [authMiddleware, loggingMiddleware]);For long-running tasks, configure push notifications:
const sendParams = {
message: { ... },
configuration: {
pushNotificationConfig: {
url: 'https://my-app.com/webhook',
token: 'auth-token',
},
},
};import { ConsoleLogger, JsonLogger, NoopLogger } from '@drew-foxall/a2a-js-sdk/server/core';
// Human-readable for development
const devLogger = ConsoleLogger.create('debug');
// Structured JSON for production
const prodLogger = JsonLogger.create();
// Silent for testing
const testLogger = NoopLogger.create();This fork tracks the official a2aproject/a2a-js repository:
| This Fork | Upstream |
|---|---|
| v0.4.0 | v0.3.6 |
git remote add upstream https://github.com/a2aproject/a2a-js.git
git fetch upstream
git merge upstream/mainContributions are welcome! Please open an issue or pull request.
- Edge/Framework improvements: Submit PRs to this repository
- A2A Protocol issues: Report to the official repository