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)
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.
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 |
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.
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:
- Validates entries at startup (URL format, auth type, protocol safety)
- Discovers agent cards from standard well-known paths
- Normalizes capabilities across different agent card formats
- Invokes through a multi-protocol fallback chain
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.
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 usehttp://localhost:9000during 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 IPsKeep both
false(default) in production.
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 |
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/mcpOnce 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 agentsget_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)
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# Bun (fast, recommended)
bun run --watch src/index.ts
# Node.js
npm ci && npx tsc
node --experimental-specifier-resolution=node dist/index.js# 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"}}}'| 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 |
| 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 |
| 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 |
[
{
"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"]
}
]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.
| 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 |
- 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
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.
Apache 2.0 — see LICENSE for details.