Lightweight, fast Agent2Agent (A2A) protocol proxy in Python.
- Overview
- Key Features
- Out of Scope
- Quick Start
- Configuration Reference
- Security Considerations
- Performance Tuning
- End-to-End Example
- Compliance Mapping to A2A
- Error Handling
- License
This project provides a lightweight and fast proxy for the Agent2Agent (A2A) protocol, configurable via YAML.
It forwards JSON-RPC 2.0 requests to an upstream server and can translate them into REST (HTTP+JSON) or WebSocket frames as needed.
The proxy supports streaming responses via Server-Sent Events (SSE) and can expose an Agent Card endpoint.
- Transparent A2A JSON-RPC Proxy – forwards JSON-RPC 2.0 requests.
- Upstream Transport Translation
- JSONRPC – pass JSON-RPC 2.0 through to a single endpoint.
- HTTP+JSON – translate JSON-RPC to REST endpoints per the A2A spec.
- WEBSOCKET – translate JSON-RPC HTTP/SSE to WebSocket JSON frames upstream.
- Streaming Passthrough – SSE for
message/stream&tasks/resubscribe. - YAML-based Configuration – upstream URL, headers, TLS, concurrency limits, logging, etc.
- Agent Card Endpoint – serve a static card, proxy the upstream card, or merge the two.
- Minimal Overhead – async-first; Starlette + Uvicorn + httpx, optional HTTP/2.
- Concurrency Limiting – middleware back-pressure.
- Pluggable upstreams – currently supports LangGraph servers; additional LLM frameworks planned.
The A2A Proxy sits between an A2A client and an upstream LangGraph server. It forwards JSON-RPC requests and translates streaming responses as needed. An MCP server can be co-located with the LangGraph runtime to expose tools/capabilities used by the agent.
- Current supported upstream: LangGraph-based servers
- Planned: broader support for additional LLM/agent frameworks
Note: Today the proxy targets LangGraph-based backends. Support for additional LLM and agent frameworks is planned.
- gRPC Transport (client or upstream)
- Deep Validation beyond basic JSON-RPC checks
- Push Notification Webhooks (handled upstream <-> client)
# From source
pip install -e .
# OR in a virtualenv
python -m venv .venv
source .venv/bin/activate
pip install -e .See config.example.yaml for a minimal example.
Via console script:
a2a-proxy -c config.yamlOr directly with uvicorn:
uvicorn a2a_proxy.server:create_app_from_file \
--factory --host 0.0.0.0 --port 8000 \
--log-level info --reload --env-file .env# Health
curl -s http://localhost:8000/healthz
# Agent Card
curl -s http://localhost:8000/.well-known/agent-card.json | jq .
# JSON-RPC (message/send)
curl -s http://localhost:8000/a2a/v1 \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"message/send","params":{"message":{"role":"user","parts":[{"kind":"text","text":"tell me a joke"}],"messageId":"abcd-1234"}}}'
# Streaming (message/stream)
curl -N http://localhost:8000/a2a/v1 \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"message/stream","params":{"message":{"role":"user","parts":[{"kind":"text","text":"stream me a story"}],"messageId":"abcd-1234"}}}'server:
host: 0.0.0.0
port: 8000
jsonrpc_path: /a2a/v1
health_path: /healthz
agent_card_path: /.well-known/agent-card.json
concurrency_limit: 100
log_level: info
access_log: true
connect_timeout_seconds: 10
request_timeout_seconds: 60
sse_client_ping_interval_seconds: null
workers: 1
upstream:
transport: JSONRPC
url: https://upstream.example.com/a2a/v1
agent_card_url: null # If not set, the proxy derives it from upstream.url as https://{host}/.well-known/agent-card.json (or http for ws)
headers:
X-Proxy: a2a-proxy
pass_through_authorization: true
verify_tls: true
ca_bundle: null
http2: false
max_connections: 100
connect_timeout_seconds: null
request_timeout_seconds: null
http_proxy: null
agent_card:
mode: proxy # proxy | static | merge
content: {}
cache_ttl_seconds: 300- proxy – GET
/.well-known/agent-card.jsonreturns the upstream Agent Card (cached). - static – serve
agent_card.contentverbatim. - merge – deep-merge
agent_card.contentover upstream card, cache result.
- Deploy behind HTTPS; keep
verify_tlstrue in production. pass_through_authorizationforwards client credentials upstream.- Configure
concurrency_limitand consider multiple workers. - Protect at the edge with rate-limiting / WAF if required.
- Install
uvicorn[standard](uvloop) for faster I/O. - Scale
workerswith CPU & workload. - Adjust
max_connectionsto suit upstream. - Enable HTTP/2 upstream (
http2: true) where supported. - Run behind a production reverse proxy (nginx, haproxy, envoy).
This repo ships a minimal WebSocket upstream server (examples/server.py) and a Python client (examples/client.py). The proxy sits in the middle.
pip install -e .
pip install "langchain-core>=0.2" "langgraph>=0.2"
pip install "langchain-google-genai>=2.0.0" "langchain-openai>=0.2.0"
pip install "python-dotenv>=1.0.0" "a2a-sdk>=0.2"cp .env.example .env
# edit .env to set credentials & model_source
python examples/server.pyServer listens on ws://127.0.0.1:9001.
# Start the REST SSE server on http://127.0.0.1:9002
EXAMPLE_TRANSPORT=rest python examples/server.pyCreate config.yaml:
server:
host: 127.0.0.1
port: 8000
jsonrpc_path: /a2a/v1
health_path: /healthz
concurrency_limit: 50
log_level: debug
upstream:
transport: WEBSOCKET
url: ws://127.0.0.1:9001
pass_through_authorization: true
headers: {}
agent_card:
mode: static
cache_ttl_seconds: 60
content:
protocolVersion: "0.3.0"
name: "Example Proxy"
description: "Local proxy to example WS server"
url: "http://127.0.0.1:8000/a2a/v1"
preferredTransport: "JSONRPC"
version: "1.0.0"
capabilities: { streaming: true, pushNotifications: true }
defaultInputModes: ["application/json", "text/plain"]
defaultOutputModes: ["application/json", "text/plain"]
skills: []Or configure the proxy for REST upstream:
server:
host: 127.0.0.1
port: 8000
jsonrpc_path: /a2a/v1
health_path: /healthz
concurrency_limit: 50
log_level: debug
upstream:
transport: HTTP+JSON
url: http://127.0.0.1:9002/a2a/v1
pass_through_authorization: true
headers: {}
agent_card:
mode: static
cache_ttl_seconds: 60
content:
protocolVersion: "0.3.0"
name: "Example Proxy (REST upstream)"
description: "Local proxy to example REST SSE server"
url: "http://127.0.0.1:8000/a2a/v1"
preferredTransport: "JSONRPC"
version: "1.0.0"
capabilities: { streaming: true, pushNotifications: true }
defaultInputModes: ["application/json", "text/plain"]
defaultOutputModes: ["application/json", "text/plain"]
skills: []Run the proxy:
a2a-proxy -c config.yamlSanity check:
curl -s http://127.0.0.1:8000/healthz
curl -s http://127.0.0.1:8000/.well-known/agent-card.json | jq .python examples/client.py- WS upstream:
ws://127.0.0.1:9001 - REST upstream:
http://127.0.0.1:9002/a2a/v1 - Proxy Agent Card:
http://127.0.0.1:8000/.well-known/agent-card.json - Health:
http://127.0.0.1:8000/healthz
- 3.2.1 JSON-RPC 2.0 downstream
- 3.3 Streaming (SSE)
- 3.5 Method naming & mapping
- 4 Authentication (pass-through / static)
- 5 Agent Card endpoint
- 7 RPC Methods (
message/send,message/stream,tasks/*,pushNotificationConfig/*)
- Invalid content-type / malformed JSON → JSON-RPC errors -32600 / ‑32700.
- Upstream JSON-RPC errors forwarded unchanged.
- REST / WS upstream errors mapped to JSON-RPC envelopes (HTTP 200).
MIT
