Server & Client SDK for Agent2Agent protocol with Express, Hono, and Edge Runtime support
npm install @drew-foxall/a2a-js-sdk


> 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
``bash`
npm install @drew-foxall/a2a-js-sdkor
pnpm add @drew-foxall/a2a-js-sdkor
yarn add @drew-foxall/a2a-js-sdk
Install the framework you want to use:
`bashFor Express (Node.js)
npm install express
---
Quick Start
The examples below show the same "Hello Agent" implemented for different environments.
$3
First, define your agent card and executor (shared across all implementations):
`typescript
// 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 {
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 => {};
}
`---
$3
The original Express implementation from upstream. Best for traditional Node.js servers.
`typescript
// 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:
`typescript
// 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');
`---
$3
Best for Cloudflare Workers, Vercel Edge Functions, Deno Deploy, and other edge environments.
`typescript
// 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:
`typescript
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:
`toml
wrangler.toml - No nodejs_compat needed!
name = "a2a-hello-agent"
main = "worker.ts"
compatibility_date = "2024-01-01"
``bash
wrangler deploy
`---
$3
Best for Bun-native applications with excellent TypeScript support.
`typescript
// 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 => {
approute.method;
});
app.listen(4000);
`---
$3
Best for minimal Cloudflare Workers with the smallest bundle size.
`typescript
// 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 => {
routerroute.method.toLowerCase();
});
export default { fetch: router.handle };
`---
$3
Best for Deno's web framework with file-based routing.
`typescript
// 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');
`---
$3
The client works with any A2A server implementation:
`typescript
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);
`---
ποΈ Architecture
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... β β β
βββββββββββββββββ βββββββββββββββββ βββββββββββββββββ
`$3
`typescript
// 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';
`$3
| 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 |---
β‘ Edge Runtime Support
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 |$3
| 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 |---
π Core Features
$3
`typescript
// 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);
}
`$3
All framework implementations support middleware:
`typescript
// Express
a2aApp.setupRoutes(app, '/a2a', [authMiddleware, loggingMiddleware]);// Hono
a2aApp.setupRoutes(app, '/a2a', [authMiddleware, loggingMiddleware]);
`$3
For long-running tasks, configure push notifications:
`typescript
const sendParams = {
message: { ... },
configuration: {
pushNotificationConfig: {
url: 'https://my-app.com/webhook',
token: 'auth-token',
},
},
};
`$3
`typescript
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();
`---
π Upstream Tracking
This fork tracks the official a2aproject/a2a-js repository:
| This Fork | Upstream |
|-----------|----------|
| v0.4.0 | v0.3.6 |
$3
`bash
git remote add upstream https://github.com/a2aproject/a2a-js.git
git fetch upstream
git merge upstream/main
``---
- A2A Protocol Specification
- Official A2A JS SDK
- A2A Samples
Contributions 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