claude-a2a-cli
v0.1.4
Published
A2A-compatible server wrapping the Claude CLI for agent-to-agent communication
Readme
claude-a2a
An A2A-compatible server that wraps the Claude Code CLI, enabling any A2A client to send messages to Claude agents on a machine and receive responses. Supports multimodal input (images, PDFs), session continuity, multi-agent configurations, and cost tracking. Also includes an MCP client so that interactive Claude Code sessions can natively call remote agents.
What problem does this solve?
Claude Code agents running on different machines have no built-in way to communicate with each other. There is no push mechanism in MCP, no way to inject messages into an active Claude Code session, and no standard protocol for bridging separate Claude instances.
claude-a2a solves this by exposing the local claude CLI as a network service using the A2A protocol. Any machine that can reach the server over HTTP can send messages to Claude and get responses back, complete with session continuity, cost tracking, and access controls.
Protocols
claude-a2a implements two protocols:
A2A (Agent-to-Agent)
A2A is Google's open protocol for communication between AI agents. It defines how agents discover each other (via Agent Cards), exchange messages, and track tasks. claude-a2a implements A2A v0.3.0 using the official @a2a-js/sdk.
The server exposes both JSON-RPC and REST transports:
POST /a2a/jsonrpc— A2A JSON-RPC 2.0 endpoint/a2a/rest/v1/...— A2A REST endpointsGET /.well-known/agent-card.json— Agent Card for discovery
MCP (Model Context Protocol)
MCP is Anthropic's protocol for giving AI models access to tools and data. The included MCP client runs as a stdio server that Claude Code can connect to, giving an interactive Claude session tools to call remote claude-a2a servers.
How it works
Remote Agent / A2A Client
|
| A2A Protocol (HTTP)
v
+------------------+
| claude-a2a | Express + @a2a-js/sdk
| |
| Agent Card | /.well-known/agent-card.json
| Auth layer | Master key / JWT tokens
| Rate limiter |
| Budget tracker |
| Claude Session | Long-lived claude CLI process (stream-json NDJSON I/O)
+------------------+
|
v
Claude CLI (local)The server spawns a long-lived Claude CLI process per session using --input-format stream-json --output-format stream-json. Messages are sent as NDJSON on stdin and results are read from stdout. Session continuity is maintained by keeping the process alive across messages, with --resume for recovery after restarts.
| A2A Concept | claude-a2a Implementation | |---|---| | Agent Card | Describes each configured Claude agent | | Message (user) | Written to Claude process stdin as NDJSON | | Message (agent) | Parsed from Claude process stdout result | | Task context | Maps to a long-lived Claude CLI session | | Skills | Agent configurations (tools, model, description) |
Each response includes Claude-specific metadata (session ID, token usage, cost, model used) in the message's metadata.claude field.
Multimodal input
claude-a2a supports sending images, PDFs, and structured data to Claude via A2A's FilePart and DataPart message types:
| A2A Part Type | Claude Content Block |
|---|---|
| TextPart | Plain text (or { type: "text" } block) |
| FilePart (image MIME + base64) | { type: "image", source: { type: "base64" } } |
| FilePart (PDF/other MIME + base64) | { type: "document", source: { type: "base64" } } |
| FilePart (URI) | Text description (URI download not supported) |
| DataPart | JSON stringified as text |
Text-only messages are sent as plain strings for backward compatibility. When non-text parts are present, the message is sent as a ContentBlock[] array.
Supported input MIME types are advertised in the Agent Card's defaultInputModes: text, image/png, image/jpeg, image/gif, image/webp, application/pdf.
Installation
# Use directly with npx (no install needed)
CLAUDE_A2A_MASTER_KEY=my-secret-key npx claude-a2a-cli serve --agent my-agent
# Or install globally
npm install -g claude-a2a-cli
CLAUDE_A2A_MASTER_KEY=my-secret-key claude-a2a serve --agent my-agentRequirements
- Node.js >= 18 (tested with 24)
- Claude Code CLI installed and authenticated (
claudecommand available in PATH)
Quick start
Option A: npx (no clone required)
# Scaffold a config in the current directory
npx claude-a2a-cli init --yes
# Start the server
CLAUDE_A2A_MASTER_KEY=my-secret-key npx claude-a2a-cli serveOption B: Single-agent mode (no config file at all)
# Start a single agent directly from CLI flags
CLAUDE_A2A_MASTER_KEY=my-secret-key npx claude-a2a-cli serve \
--agent my-agent \
--work-dir ./my-project \
--model claude-sonnet-4-6 \
--max-budget 5.0Option C: Clone and build
cd claude-a2a
npm install
npm run build
# Start with a master key for auth
CLAUDE_A2A_MASTER_KEY=my-secret-key ./dist/cli.js serveVerify it works
# Check health
curl http://localhost:8462/health
# View the agent card
curl http://localhost:8462/.well-known/agent-card.json
# Send a message
curl -X POST http://localhost:8462/a2a/jsonrpc \
-H "Authorization: Bearer my-secret-key" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "message/send",
"params": {
"message": {
"kind": "message",
"messageId": "msg-1",
"role": "user",
"parts": [{"kind": "text", "text": "What is 2+2?"}]
},
"configuration": {"blocking": true}
}
}'Note: If running from within a Claude Code session, the
CLAUDECODEenvironment variable will cause spawned Claude processes to exit immediately. Start the server in a separate terminal or useenv -u CLAUDECODE -u CLAUDE_CODE_ENTRYPOINTto unset it.
Configuration
There are three ways to configure the server:
1. init command (recommended for new projects)
claude-a2a init # interactive prompts
claude-a2a init --yes # non-interactive with defaults
claude-a2a init --yes -d ./my-project # specify target directoryThis scaffolds a config.yaml and a .claude/settings.json with tool permissions.
2. Single-agent CLI flags (no config file)
claude-a2a serve --agent <name> [options]| Flag | Description |
|---|---|
| --agent <name> | Agent name (enables single-agent mode) |
| --work-dir <path> | Agent working directory |
| --model <model> | Claude model (e.g. claude-sonnet-4-6) |
| --permission-mode <mode> | Permission mode (default: default) |
| --system-prompt <text> | System prompt |
| --max-budget <usd> | Max budget per invocation in USD |
When --agent is set, the server skips config file lookup entirely. Data is stored in ./data by default.
3. YAML config file
The server looks for a config file in this order:
- Path passed via
--config/-cflag ./config.yamlin the current directory/etc/claude-a2a/config.yaml
If no file is found, sensible defaults are used. Copy config/example.yaml to get started:
cp config/example.yaml config.yamlEnvironment variables
Secrets should be set via environment variables rather than in the config file:
| Variable | Purpose |
|---|---|
| CLAUDE_A2A_MASTER_KEY | Master authentication key (full access) |
| CLAUDE_A2A_JWT_SECRET | Secret for signing/verifying JWT tokens |
| CLAUDE_A2A_PORT | Override server port (default: 8462) |
| CLAUDE_A2A_DATA_DIR | Override data directory (default: /var/lib/claude-a2a or ./data in single-agent mode) |
| CLAUDE_A2A_CONFIG | Override config file path |
| LOG_LEVEL | Logging level: debug, info, warn, error (default: info) |
Key config sections
server — Host, port, TLS, max concurrent Claude processes, request timeout, max body size (default 10mb to support base64-encoded files).
auth — Master key and JWT settings. If neither is configured, the server allows unauthenticated access.
rate_limiting — Token-bucket rate limiter. Per-client, configurable requests per minute and burst.
budgets — Daily spending limits (global and per-client) to prevent runaway costs.
sessions — Max session lifetime, idle timeout, and per-client session limits.
claude — Path to the claude binary, default model, default permission mode, and working directory.
agents — The most important section. This is where you define your agents.
Agents
In the A2A protocol, an Agent Card is a JSON document that describes what an agent can do. It is served at a well-known URL (/.well-known/agent-card.json) so that other agents and tools can discover and understand the agent's capabilities before sending it messages.
claude-a2a automatically generates an Agent Card from the agents section of your config. Each entry in agents becomes a skill on the card.
Defining agents
Each agent is a named configuration that controls how Claude behaves when it receives messages for that agent. Here is a full example:
agents:
general:
description: "General-purpose Claude assistant"
enabled: true
model: null # null = use Claude's default model
append_system_prompt: "You are responding via the claude-a2a A2A API. Be concise."
settings_file: null # path to a Claude Code settings.json file
permission_mode: "default" # Claude's permission mode
allowed_tools: [] # passed as --allowedTools to the CLI
max_budget_usd: 1.0 # max spend per invocation
required_scopes: # JWT scopes needed to use this agent
- "agent:general"
work_dir: null # working directory (null = use global default)Agent config fields
| Field | Description |
|---|---|
| description | Human-readable description. Appears in the Agent Card. |
| enabled | Set to false to disable an agent without removing its config. |
| model | Claude model to use (e.g. claude-sonnet-4-6). null uses the CLI default. |
| append_system_prompt | Extra instructions appended to Claude's system prompt for this agent. |
| settings_file | Path to a Claude Code settings JSON file. Required for granting tool permissions in headless mode. See Permissions. |
| permission_mode | Claude's permission mode. See Permissions. |
| allowed_tools | List of tools passed as --allowedTools to the CLI (e.g. ["Bash(git:*)"]). |
| max_budget_usd | Maximum spend (in USD) per single invocation. |
| required_scopes | JWT scopes required to call this agent. Ignored for master key auth. |
| work_dir | Working directory for Claude. Determines what files Claude can see. |
Example: multiple agents
agents:
general:
description: "General-purpose assistant for questions and research"
enabled: true
max_budget_usd: 0.50
required_scopes: ["agent:general"]
code:
description: "Code assistant that can read, write, and run code"
enabled: true
model: "claude-sonnet-4-6"
append_system_prompt: "You are a code assistant. Focus on writing clean, correct code."
settings_file: "/home/projects/my-app/.claude/settings.json"
permission_mode: "default"
max_budget_usd: 5.0
required_scopes: ["agent:code"]
work_dir: "/home/projects/my-app"
reviewer:
description: "Code reviewer that reads code and provides feedback"
enabled: true
max_budget_usd: 1.0
required_scopes: ["agent:reviewer"]
work_dir: "/home/projects/my-app"Clients target a specific agent by including "metadata": {"agent": "code"} in their message. If no agent is specified, the first enabled agent is used.
Viewing the Agent Card
Once the server is running, view the generated Agent Card:
curl http://localhost:8462/.well-known/agent-card.jsonThis returns the full A2A Agent Card JSON, including all enabled agents as skills, supported authentication schemes, and capability declarations.
Permissions
Claude CLI's permission system controls which tools the agent can use. This is critical for headless (non-interactive) operation because there is no human to approve permission prompts.
Permission modes
The permission_mode field maps to --permission-mode on the CLI:
| Mode | Behavior |
|---|---|
| default | Auto-approves reads. Writes and Bash require explicit allow rules in a settings file. Recommended for headless use. |
| acceptEdits | Auto-approves file reads and writes within the project directory. Blocks Bash. |
| plan | Enters planning workflow — not suitable for headless use. |
| dontAsk | Denies all tool use that would normally prompt. |
| bypassPermissions | Allows everything. Cannot be used when running as root. |
Settings file (recommended approach)
The most reliable way to grant permissions for headless operation is via a Claude Code settings file. Set settings_file in the agent config to point at it, and use permission_mode: "default":
{
"permissions": {
"allow": [
"Read",
"Edit",
"Glob",
"Grep",
"Write"
]
}
}Place this file at <work_dir>/.claude/settings.json and reference it from the agent config:
agents:
myagent:
work_dir: "/home/projects/my-app"
settings_file: "/home/projects/my-app/.claude/settings.json"
permission_mode: "default"The settings file allow rules grant tool access that would otherwise require interactive approval. Without a settings file, most tools will be denied in headless mode.
Path patterns: In settings files,
/pathis relative to the settings file. Use//pathfor absolute filesystem paths (e.g.Write(//tmp/**)). Note that some system paths like/tmpmay require blanket tool allow ("Write") rather than path-scoped rules.
Running as root
bypassPermissions mode is blocked when running as root for security. Use default mode with a settings file instead.
Authentication
claude-a2a supports three tiers of authentication:
1. Master key
A shared secret that grants full access. Set via the CLAUDE_A2A_MASTER_KEY environment variable. Pass it as a Bearer token:
curl -H "Authorization: Bearer my-secret-key" http://localhost:8462/admin/statsBest for: trusted agents on the same network or VPN.
2. JWT tokens
Scoped tokens with per-client controls. Generate them using the CLI:
# Generate a token for "my-agent" with access to the "general" agent
CLAUDE_A2A_JWT_SECRET=your-jwt-secret \
npx claude-a2a-cli token my-agent agent:general
# Generate a token with access to all agents
CLAUDE_A2A_JWT_SECRET=your-jwt-secret \
npx claude-a2a-cli token my-agent '*'JWT claims can include:
scopes— which agents the token can access (e.g.agent:general,agent:code, or*)budget_daily_usd— per-client daily spending limitrate_limit_rpm— per-client rate limit override
3. No authentication
If neither CLAUDE_A2A_MASTER_KEY nor CLAUDE_A2A_JWT_SECRET is set, the server allows unauthenticated access. Only appropriate for local development.
Session continuity
claude-a2a maintains session continuity across messages using A2A's contextId. When you send a first message, the response includes a contextId. Include that same contextId in subsequent messages to continue the conversation in the same Claude session:
# First message — note the contextId in the response
RESPONSE=$(curl -s -X POST http://localhost:8462/a2a/jsonrpc \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0", "id": "1",
"method": "message/send",
"params": {
"message": {
"kind": "message", "messageId": "m1", "role": "user",
"parts": [{"kind": "text", "text": "Remember the number 42."}]
},
"configuration": {"blocking": true}
}
}')
# Extract contextId from response
CONTEXT_ID=$(echo "$RESPONSE" | jq -r '.result.contextId')
# Second message — same contextId, Claude remembers the conversation
curl -s -X POST http://localhost:8462/a2a/jsonrpc \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"jsonrpc\": \"2.0\", \"id\": \"2\",
\"method\": \"message/send\",
\"params\": {
\"message\": {
\"kind\": \"message\", \"messageId\": \"m2\", \"role\": \"user\",
\"contextId\": \"$CONTEXT_ID\",
\"parts\": [{\"kind\": \"text\", \"text\": \"What number did I say?\"}]
},
\"configuration\": {\"blocking\": true}
}
}"Under the hood, claude-a2a keeps a long-lived Claude CLI process per session. If the process dies (e.g. server restart), it respawns with --resume <session-id> to recover the conversation.
MCP client (for interactive Claude Code sessions)
The included MCP client lets an interactive Claude Code session call remote claude-a2a servers as tools. This means a Claude agent on your laptop can ask a Claude agent on your server to do work.
Setup
- Create a client config file at
~/.claude-a2a/client.json:
{
"servers": {
"my-server": {
"url": "http://192.168.1.80:8462",
"token": "my-secret-key"
}
}
}- Add the MCP server to your project's
.mcp.json:
{
"mcpServers": {
"claude-a2a": {
"type": "stdio",
"command": "node",
"args": ["/path/to/claude-a2a/dist/client.js"]
}
}
}Restart Claude Code after adding the MCP config.
Available MCP tools
Once connected, Claude Code gains these tools:
| Tool | Description |
|---|---|
| list_remote_servers | List configured remote servers |
| list_remote_agents | Fetch the Agent Card from a remote server |
| send_message | Send a message to a remote agent and get a response |
| get_server_health | Check server health and status |
| list_sessions | List active sessions (admin) |
| delete_session | Clean up a remote session (admin) |
Admin API
Admin endpoints require master key authentication.
# Server stats
curl -H "Authorization: Bearer $MASTER_KEY" http://localhost:8462/admin/stats
# List active sessions
curl -H "Authorization: Bearer $MASTER_KEY" http://localhost:8462/admin/sessions
# Delete a session
curl -X DELETE -H "Authorization: Bearer $MASTER_KEY" \
http://localhost:8462/admin/sessions/<session-id>
# Create a JWT token
curl -X POST -H "Authorization: Bearer $MASTER_KEY" \
-H "Content-Type: application/json" \
http://localhost:8462/admin/tokens \
-d '{"sub": "my-client", "scopes": ["agent:general"]}'
# Revoke a token
curl -X DELETE -H "Authorization: Bearer $MASTER_KEY" \
http://localhost:8462/admin/tokens/<token-jti>Production deployment
A systemd service file is included for production deployment:
# Build
npm run build
# Run the install script (creates user, directories, copies files)
sudo bash scripts/install.sh
# Configure
sudo vim /etc/claude-a2a/config.yaml
sudo vim /etc/claude-a2a/env # set CLAUDE_A2A_MASTER_KEY, CLAUDE_A2A_JWT_SECRET
# Start
sudo systemctl enable --now claude-a2a
# Check status
sudo systemctl status claude-a2a
curl http://localhost:8462/healthThe systemd service runs with security hardening (read-only filesystem, no new privileges, restricted syscalls).
Development
# Run in dev mode (uses tsx, no build step)
CLAUDE_A2A_MASTER_KEY=dev-key npm run dev
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Lint
npm run lint
# Build
npm run buildProject structure
claude-a2a/
src/
cli.ts # CLI entry point (serve, init, client, token)
init.ts # Interactive config scaffolding
version.ts # Version constant
server/
index.ts # Express server setup, wires everything together
config.ts # YAML config loading + Zod validation
agent-card.ts # Generates A2A Agent Card from config
claude-session.ts # Long-lived Claude CLI process wrapper (NDJSON I/O)
claude-runner.ts # Session pool manager
agent-executor.ts # A2A executor: bridges A2A protocol to Claude Runner
auth/
middleware.ts # Express auth middleware (master key + JWT)
tokens.ts # JWT creation, verification, revocation
user.ts # User context type
services/
database.ts # SQLite connection with migrations
session-store.ts # Tracks Claude sessions by context ID
task-store.ts # A2A task persistence with tenant isolation
rate-limiter.ts # Token-bucket rate limiter
budget-tracker.ts # Daily per-client and global cost tracking
routes/
admin.ts # Token CRUD, session management, stats
health.ts # Health check endpoint
client/
index.ts # MCP client entry point
mcp-server.ts # MCP tool definitions
a2a-client.ts # HTTP client for remote A2A servers
config.ts # Client config loading
tests/ # Vitest unit tests
config/
example.yaml # Example server configuration
systemd/
claude-a2a.service # systemd unit file
scripts/
install.sh # Production install scriptResponse metadata
Every response from claude-a2a includes Claude-specific metadata in result.metadata.claude:
{
"claude": {
"agent": "general",
"session_id": "7fcfc468-2111-4fc2-97ad-bcb4d91ce0c8",
"context": null,
"cost_usd": 0.014664,
"duration_ms": 1932,
"duration_api_ms": 1859,
"permission_denials": [],
"model_used": "claude-sonnet-4-6",
"num_turns": 1,
"usage": {
"input_tokens": 3,
"output_tokens": 5,
"cache_creation_input_tokens": 816,
"cache_read_input_tokens": 18848
}
}
}This lets clients track costs, monitor token usage, detect permission issues, and manage session state.
Built with
- @a2a-js/sdk — Official A2A TypeScript SDK (Google)
- @modelcontextprotocol/sdk — Official MCP TypeScript SDK (Anthropic)
- Express 5 — HTTP framework
- Zod — Config validation
- pino — Structured logging
- better-sqlite3 — SQLite for persistence
- Vitest — Testing
- esbuild — Build tool
