Skip to content

jinyitao123/a2a-gateway

Repository files navigation

A2A Gateway

A protocol-bridging gateway that connects any AI agent platform to the A2A and MCP ecosystems.

A2A Gateway sits between your internal agents and the outside world. It exposes your agents as standard A2A services, lets them discover and invoke external agents, and provides a unified MCP tool interface for orchestration — all without modifying your agent platform.

┌─────────────────────────────────────────────────────────┐
│                     MCP Clients                         │
│           (Claude, Cursor, custom agents)               │
└──────────────────────┬──────────────────────────────────┘
                       │ MCP Streamable HTTP
                       ▼
┌─────────────────────────────────────────────────────────┐
│                   A2A Gateway                           │
│                                                         │
│  ┌──────────┐  ┌──────────────┐  ┌───────────────────┐ │
│  │ MCP      │  │ A2A JSON-RPC │  │ Agent Card        │ │
│  │ Bridge   │  │ Router       │  │ Discovery         │ │
│  │ /mcp     │  │ /a2a         │  │ /.well-known/     │ │
│  └────┬─────┘  └──────┬───────┘  └───────────────────┘ │
│       │               │                                 │
│  ┌────┴───────────────┴─────────────────────────────┐   │
│  │          InternalAgentProvider (interface)        │   │
│  │  ┌─────────────┐  ┌─────────┐  ┌─────────────┐  │   │
│  │  │MemohProvider│  │ Custom  │  │   ...       │  │   │
│  │  └─────────────┘  └─────────┘  └─────────────┘  │   │
│  └──────────────────────────────────────────────────┘   │
│                                                         │
│  ┌──────────────────────────────────────────────────┐   │
│  │          External Agent Layer                     │   │
│  │  Registry → Card Discovery → Multi-Protocol      │   │
│  │                               Invoker            │   │
│  └──────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────┘
         │                                    │
         ▼                                    ▼
  Your Agent Platform               External A2A Agents
  (Memoh, custom, ...)             (any A2A-compatible)

Why A2A Gateway?

Problem

You have agents on your platform. You want them to:

  • Call each other
  • Be callable by external agents
  • Discover and invoke agents on other platforms
  • Work with MCP-based tools and orchestrators

But wiring all of this directly means coupling your platform to multiple protocols, handling auth, discovery, security, retries — for each integration.

Solution

A2A Gateway provides one bridge that handles all of it:

Capability How
Internal agent interop Agents call each other through a platform-agnostic InternalAgentProvider interface
External agent discovery Registry-curated agents with automatic agent card fetching and normalization
Multi-protocol invocation Tries HTTP+JSON, JSON-RPC SendMessage, and JSON-RPC tasks/send — first success wins
MCP tool surface All agents (internal + external) exposed as MCP tools, usable by Claude, Cursor, or any MCP client
A2A compliance Full JSON-RPC 2.0 endpoint with tasks/send, tasks/sendSubscribe (SSE streaming), tasks/get, tasks/cancel
Security by default SSRF protection, HTTPS enforcement, response size limits, concurrency control, registry allowlisting

Key Design Decisions

1. Provider Pattern — Swap Your Platform, Keep Everything Else

The gateway never talks to your agent platform directly. It talks to an InternalAgentProvider interface:

interface InternalAgentProvider {
  listAgents(): Promise<AgentInfo[]>;
  invoke(agentId: string, query: string, context?: Message[]): Promise<string>;
}

The included MemohAgentProvider implements this for Memoh. To connect a different platform, implement this interface — the MCP bridge, external agent handling, A2A router, and all tests remain untouched.

2. Registry-Based External Agents — No Arbitrary URL Access

External agents are not invoked by free-form URL. They come from a curated JSON registry with per-agent auth, timeouts, and enablement controls. The gateway:

  1. Validates entries at startup (URL format, auth type, protocol safety)
  2. Discovers agent cards from standard well-known paths
  3. Normalizes capabilities across different agent card formats
  4. Invokes through a multi-protocol fallback chain

3. Protocol Bridge — A2A ↔ MCP in Both Directions

The gateway is bidirectional:

  • Inbound A2A: External agents call your agents via /a2a (standard A2A JSON-RPC)
  • Outbound via MCP: Your agents call external agents through MCP tools (ask_external_agent)
  • Internal via MCP: Your agents call each other through MCP tools (ask_agent)

One gateway handles all three interaction patterns.

4. Security-First External Calls

Every outbound request to external agents passes through hardened checks:

  • Private IP blocking — 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.1, IPv6 link-local/ULA
  • HTTPS enforcement — HTTP rejected unless explicitly opted in for dev
  • Origin validation — target URL origin must match registry base_url
  • Response limits — configurable max bytes (default 1MB) and timeouts (default 20s)
  • Concurrency gate — configurable parallel request limit (default 8)
  • No redirects — 3xx responses rejected to prevent SSRF redirect chains

Note: These checks only apply to outbound calls to external agents. Inbound connections to the gateway (/mcp, /a2a, /health) are not affected — you can freely use http://localhost:9000 during local development. For local development with external agents on HTTP or private networks, set:

ALLOW_INSECURE_DEV=true              # Allow HTTP targets
ALLOW_PRIVATE_DEV_TARGETS=true       # Allow localhost / private IPs

Keep both false (default) in production.

MCP Tools

Once connected, MCP clients see these tools:

Tool Description
list_internal_agents List available agents on your platform
ask_agent Invoke an internal agent by ID with optional conversation context
list_agents List curated external A2A agents
get_agent_capabilities Fetch and normalize an external agent's capabilities
ask_external_agent Invoke an external agent with multi-turn support
health Bridge health and connectivity summary

Integration with Goose

Goose natively supports MCP StreamableHttp extensions. Register A2A Gateway as a Goose extension — zero code required:

# In your Goose config (e.g. ~/.config/goose/profiles.yaml)
extensions:
  a2a-gateway:
    enabled: true
    type: streamable_http
    name: a2a-gateway
    uri: http://localhost:9000/mcp

Once registered, Goose immediately gains access to all gateway tools:

  • list_internal_agents / ask_agent — call internal agents (e.g. Memoh bots)
  • list_agents / ask_external_agent — discover and invoke external A2A agents
  • get_agent_capabilities / health — inspect agent metadata and bridge status

This turns Goose into a multi-agent orchestrator — it can delegate subtasks to specialized agents on any connected platform, all through its natural tool-calling flow.

Goose ──MCP──▶ A2A Gateway ──▶ Internal Agents (Memoh, custom, ...)
                            ──▶ External A2A Agents (any platform)

Quick Start

Docker (recommended)

docker build -t a2a-gateway .
docker run -d -p 9000:9000 \
  -e A2A_PORT=9000 \
  -e MEMOH_SERVER_URL=http://your-server:8080 \
  -e MEMOH_AGENT_URL=http://your-agent:8081 \
  -e MEMOH_USERNAME=admin \
  -e MEMOH_PASSWORD=secret \
  -v "$(pwd)/data:/data" \
  a2a-gateway

Local development

# Bun (fast, recommended)
bun run --watch src/index.ts

# Node.js
npm ci && npx tsc
node --experimental-specifier-resolution=node dist/index.js

Verify

# Health check
curl http://localhost:9000/health

# Agent card discovery
curl http://localhost:9000/.well-known/agent.json

# MCP initialize
curl -X POST http://localhost:9000/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"cli","version":"1.0.0"}}}'

Configuration

Core

Variable Default Description
A2A_PORT 9000 Listen port
A2A_HOST 0.0.0.0 Listen address
A2A_PUBLIC_URL http://a2a-gateway:9000 Public-facing URL for agent card
DB_PATH /data/a2a-tasks.db SQLite task store path

Agent Platform (Memoh)

Variable Default Description
MEMOH_SERVER_URL http://server:8080 Platform server URL
MEMOH_AGENT_URL http://agent:8081 Agent gateway URL
MEMOH_USERNAME admin Auth username
MEMOH_PASSWORD Auth password

External Agent Registry

Variable Default Description
EXTERNAL_AGENTS_REGISTRY_PATH data/external-agents.json Registry file path
ALLOW_INSECURE_DEV false Allow HTTP targets (dev only)
ALLOW_PRIVATE_DEV_TARGETS false Allow private/local hostnames (dev only)
EXTERNAL_AGENTS_TIMEOUT_MS 20000 Request timeout
EXTERNAL_AGENTS_MAX_RESPONSE_BYTES 1048576 Max response size
EXTERNAL_AGENTS_MAX_CONCURRENCY 8 Parallel request limit

External Agent Registry Format

[
  {
    "id": "weather-agent",
    "name": "Weather Agent",
    "base_url": "https://weather-agent.example.com",
    "auth_type": "bearer",
    "auth_token": "sk-...",
    "enabled": true,
    "description": "Provides weather forecasts",
    "agent_card_path": "/.well-known/agent.json",
    "tags": ["weather", "forecast"]
  }
]

Adapting to Your Platform

To replace the Memoh provider with your own:

import { InternalAgentProvider, AgentInfo } from './types/internal-agent-provider';

export class MyPlatformProvider implements InternalAgentProvider {
  async listAgents(): Promise<AgentInfo[]> {
    // Fetch agents from your platform
    return [{ id: '1', name: 'My Agent', description: '...' }];
  }

  async invoke(agentId: string, query: string): Promise<string> {
    // Call your platform's agent API
    return 'Agent response';
  }
}

Then wire it in index.ts:

const provider = new MyPlatformProvider();
app.route('/mcp', createMcpBridgeRouter({ internalProvider: provider, registry, cardService, invoker }));

Everything else — MCP tools, external agent handling, A2A endpoints, security — works unchanged.

Endpoints

Method Path Protocol Description
GET /health HTTP Health check
GET /.well-known/agent.json A2A Agent card discovery
POST /a2a A2A JSON-RPC Agent-to-agent communication
POST /mcp MCP Streamable HTTP MCP tool interface

Tech Stack

  • Runtime: Bun or Node.js (auto-detected)
  • Framework: Hono (lightweight, multi-runtime)
  • Protocols: A2A JSON-RPC 2.0, MCP Streamable HTTP, SSE
  • Storage: SQLite (via better-sqlite3) with WAL mode
  • Language: TypeScript

Ecosystem

Complementary projects in the A2A/MCP space:

Project Focus Language
codex-a2a Runtime adapter — wraps Codex with A2A serving & outbound peer calls Python
A2A Agent-to-Agent protocol specification
MCP Model Context Protocol specification

a2a-gateway is a protocol-bridging gateway (sits between platforms and the ecosystem); codex-a2a is a runtime adapter (wraps a specific runtime with A2A semantics). They can be used together.

License

Apache 2.0 — see LICENSE for details.

About

A2A + MCP bridge : internal bot-to-bot calls, external agent discovery, and secure Streamable HTTP tools.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors