Skip to content

Repository files navigation

A2A Proxy (Python)

Lightweight, fast Agent2Agent (A2A) protocol proxy in Python.


Table of Contents


Overview

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.

Key Features

  • 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.

Architecture

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

diagram

Note: Today the proxy targets LangGraph-based backends. Support for additional LLM and agent frameworks is planned.

Out of Scope

  • gRPC Transport (client or upstream)
  • Deep Validation beyond basic JSON-RPC checks
  • Push Notification Webhooks (handled upstream <-> client)

Quick Start

Install

# From source
pip install -e .

# OR in a virtualenv
python -m venv .venv
source .venv/bin/activate
pip install -e .

Create config.yaml

See config.example.yaml for a minimal example.

Run

Via console script:

a2a-proxy -c config.yaml

Or 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

Test

# 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"}}}'

Configuration Reference (YAML)

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

Agent Card Modes

  • proxy – GET /.well-known/agent-card.json returns the upstream Agent Card (cached).
  • static – serve agent_card.content verbatim.
  • merge – deep-merge agent_card.content over upstream card, cache result.

Security Considerations

  • Deploy behind HTTPS; keep verify_tls true in production.
  • pass_through_authorization forwards client credentials upstream.
  • Configure concurrency_limit and consider multiple workers.
  • Protect at the edge with rate-limiting / WAF if required.

Performance Tuning

  • Install uvicorn[standard] (uvloop) for faster I/O.
  • Scale workers with CPU & workload.
  • Adjust max_connections to suit upstream.
  • Enable HTTP/2 upstream (http2: true) where supported.
  • Run behind a production reverse proxy (nginx, haproxy, envoy).

End-to-End Example: Example Server + Proxy + Client

This repo ships a minimal WebSocket upstream server (examples/server.py) and a Python client (examples/client.py). The proxy sits in the middle.

Prerequisites

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"

Run the Example Upstream Server (WebSocket)

cp .env.example .env
# edit .env to set credentials & model_source
python examples/server.py

Server listens on ws://127.0.0.1:9001.

Run the Example Upstream Server (REST)

# Start the REST SSE server on http://127.0.0.1:9002
EXAMPLE_TRANSPORT=rest python examples/server.py

Run the Proxy Pointing to the WS Server

Create 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.yaml

Sanity check:

curl -s http://127.0.0.1:8000/healthz
curl -s http://127.0.0.1:8000/.well-known/agent-card.json | jq .

Run the Client Pointing at the Proxy

python examples/client.py

End-to-End Ports and URLs

  • 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

Compliance Mapping to A2A

  • 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/*)

Error Handling

  • 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).

License

MIT

About

Proxy for A2A protocol

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages