@ai11/openclaw-a2a
v0.1.0
Published
OpenClaw A2A Plugin — exposes OpenClaw agents via the Agent2Agent protocol
Maintainers
Readme
OpenClaw A2A Plugin
Expose OpenClaw agents via Google's Agent-to-Agent (A2A) protocol, enabling seamless interoperability with any A2A-compliant client or agent network.
Built on the official @a2a-js/sdk as the protocol engine.
Features
- Agent Card Discovery —
GET /.well-known/agent-card.jsonserves a fully compliant A2A Agent Card - JSON-RPC
message/send— synchronous task execution via standard A2A JSON-RPC - Streaming — SSE-based streaming responses with incremental artifact updates
- Skill → Agent Routing — map A2A skills to OpenClaw agents via a configurable routing table
- Authentication — static bearer tokens, JWT validation (HS256/RS256), and mTLS identity extraction
- Per-Skill ACLs — fine-grained access control by subject or role per skill
- SQLite Persistent TaskStore — survive Gateway restarts with WAL-mode SQLite (or use in-memory)
- Rate Limiting — per-caller token bucket rate limiter with configurable RPM and burst
- Health Checks — upstream
/v1/responsesreachability probes - Outbound Federation —
a2a-call-remoteanda2a-discovertools for calling external A2A agents
Quick Start
1. Install
npm install @ai11/openclaw-a2a2. Minimal Configuration
Add the plugin to your openclaw.json with just the essentials:
{
"plugins": {
"@ai11/openclaw-a2a": {
"basePath": "/a2a",
"gatewayBaseUrl": "http://127.0.0.1:3000",
"gatewayToken": "<your OpenClaw Gateway token>",
"routing": {
"defaultAgentId": "default-agent"
},
"auth": {
"allowedBearerTokens": ["<generate-a-strong-token>"],
"agentCardPublic": true
},
"agentCard": {
"name": "My OpenClaw Agent",
"description": "An AI agent powered by OpenClaw",
"version": "1.0.0",
"skills": [
{
"id": "general",
"name": "General",
"description": "General-purpose assistant",
"tags": ["general"]
}
]
}
}
}
}Where do I get
gatewayToken? This is the token configured in your OpenClaw Gateway'sopenclaw.jsonunder the top-level"token"field. It authenticates this plugin's loopback calls to the Gateway's/v1/responsesendpoint. If you haven't set one, add"token": "some-secret"to your Gateway config and use the same value here.
3. Start the Gateway
openclaw gateway startYou're up! The agent card is at http://localhost:3000/.well-known/agent-card.json and A2A requests go to POST http://localhost:3000/a2a.
Advanced Configuration
The full config adds JWT auth, per-skill ACLs, rate limiting, persistent task storage, and multi-agent routing:
{
"plugins": {
"@ai11/openclaw-a2a": {
"basePath": "/a2a",
"gatewayBaseUrl": "http://127.0.0.1:3000",
"gatewayToken": "<your OpenClaw Gateway token>",
"routing": {
"defaultAgentId": "default-agent",
"skillToAgentId": {
"code-review": "code-reviewer-agent",
"summarize": "summarizer-agent"
}
},
"auth": {
"allowedBearerTokens": ["token-abc-123"],
"agentCardPublic": true,
"jwt": {
"hsSecret": "your-hs256-secret",
"audience": "https://your-gateway.example.com",
"issuer": "https://auth.example.com",
"algorithms": ["HS256", "RS256"]
},
"acls": {
"code-review": {
"allowedSubjects": ["service-a"],
"allowedRoles": ["developer"]
}
}
},
"rateLimit": {
"requestsPerMinute": 60,
"burst": 10
},
"taskStore": {
"type": "sqlite",
"dbPath": "./data/tasks.db",
"ttlMs": 604800000,
"cleanupIntervalMs": 3600000
},
"agentCard": {
"name": "My OpenClaw Agent",
"description": "An AI agent powered by OpenClaw",
"version": "1.0.0",
"capabilities": {
"streaming": true,
"pushNotifications": false,
"stateTransitionHistory": false
},
"skills": [
{
"id": "code-review",
"name": "Code Review",
"description": "Reviews pull requests and provides feedback",
"tags": ["code", "review"]
},
{
"id": "summarize",
"name": "Summarize",
"description": "Summarizes documents and conversations",
"tags": ["text", "summary"]
}
]
}
}
}
}Usage Examples
Discover the Agent Card
curl http://localhost:3000/.well-known/agent-card.jsonSend a message (JSON-RPC message/send)
curl -X POST http://localhost:3000/a2a \
-H "Content-Type: application/json" \
-H "Authorization: Bearer token-abc-123" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "message/send",
"params": {
"message": {
"kind": "message",
"messageId": "msg-001",
"role": "user",
"parts": [{ "kind": "text", "text": "Review this code" }],
"metadata": { "ai11.skillId": "code-review" }
}
}
}'Stream a response (SSE)
curl -X POST http://localhost:3000/a2a \
-H "Content-Type: application/json" \
-H "Authorization: Bearer token-abc-123" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "message/stream",
"params": {
"message": {
"kind": "message",
"messageId": "msg-002",
"role": "user",
"parts": [{ "kind": "text", "text": "Summarize this document" }],
"metadata": { "ai11.skillId": "summarize" }
}
}
}'Security Considerations
Authentication
- Bearer tokens are the simplest auth method. Generate strong, random tokens (e.g.,
openssl rand -hex 32). Never use placeholder values in production. - JWT validation supports HS256 and RS256. Always set
audienceandissuerto prevent token reuse across services. - mTLS extracts client identity from TLS certificates for high-trust environments.
Token Management
gatewayTokenauthenticates loopback calls from this plugin to the OpenClaw Gateway. Treat it like any internal secret — do not commit it to version control. Use environment variables or a secrets manager.allowedBearerTokensshould be rotated periodically. When rotating, temporarily allow both old and new tokens, then remove the old one.- JWT secrets (
hsSecret) must be strong and unique. For RS256, use proper key management and rotate signing keys on a schedule.
Access Control (ACLs)
- By default, all authenticated callers can access all skills. Use
auth.aclsto restrict sensitive skills to specific subjects or roles. - ACL subjects come from the authenticated identity (bearer token lookup, JWT
subclaim, or mTLS CN). - Always apply the principle of least privilege — only grant access to the skills each caller needs.
Network Exposure
- Do not expose the Gateway directly to the public internet without a reverse proxy (e.g., nginx, Caddy) handling TLS termination.
- The Agent Card endpoint (
/.well-known/agent-card.json) is public by default whenagentCardPublic: true. Set it tofalseif your agent should not be discoverable. - Rate limiting (
rateLimit) is per-caller and in-memory. For production deployments behind a load balancer, consider additional rate limiting at the proxy layer. - The loopback URL (
gatewayBaseUrl) should always point to127.0.0.1orlocalhost— never expose it externally.
Task Store
- SQLite task store files (
data/tasks.db) may contain conversation content. Protect them with appropriate file permissions. - Set
ttlMsto automatically purge old tasks and limit data retention.
Architecture
The plugin registers two HTTP routes on the OpenClaw Gateway:
GET /.well-known/agent-card.json— serves the Agent Card (optionally public)POST {basePath}(prefix match, default/a2a) — JSON-RPC handler for A2A methods
Request flow:
Client → Auth middleware → Rate limiter → ACL check → a2a-js JsonRpcTransportHandler
→ OpenClawAgentExecutor → Router.resolve(skillId) → POST /v1/responses (loopback)
→ SSE stream parsed → EventBus artifact/status events → A2A response/streamThe OpenClawAgentExecutor implements the AgentExecutor interface from @a2a-js/sdk. It extracts text from incoming A2A messages, resolves the target OpenClaw agent via the skill router, calls the Gateway's loopback /v1/responses endpoint, and streams incremental results back through the A2A protocol.
For a deep dive, see:
- docs/prd.md — Product Requirements Document
- docs/architecture.md — Architecture & Design
Project Structure
src/
├── index.ts # Plugin entrypoint — route registration & wiring
├── config.ts # Config types, parsing, validation, AgentCard builder
├── transport.ts # HTTP bridge — adapts a2a-js handlers to raw Node.js req/res
├── router.ts # Skill → AgentId routing with default fallback
├── openclaw-agent-executor.ts # AgentExecutor — bridges A2A to OpenClaw /v1/responses
├── auth.ts # Bearer token + JWT + mTLS auth, ACL authorization
├── rate-limit.ts # Per-caller token bucket rate limiter
├── sqlite-task-store.ts # SQLite-backed TaskStore with TTL cleanup
├── health.ts # Upstream health check probe
├── *.test.ts # Co-located unit tests (Vitest)
docs/
├── prd.md # Product Requirements Document
├── architecture.md # Architecture & design decisionsDevelopment
# Install dependencies
npm install
# Build
npm run build
# Run tests
npm test
# Watch mode
npm run test:watch
# Clean build artifacts
npm run cleanRequirements: Node.js ≥ 18
