initial: @a2a/mcp gateway for a2acloud agents

Local MCP server that exposes any number of deployed A2A agents to
Claude Code, Cursor, Windsurf, and other MCP clients. Reuses the
~/.a2a/credentials.json token written by the Python a2a CLI. Adding
or removing agents is a single ~/.a2a/mcp.json edit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-11 20:33:54 -03:00
commit 9c5d8172dc
13 changed files with 2738 additions and 0 deletions

62
src/config.ts Normal file
View File

@@ -0,0 +1,62 @@
/**
* Local config at ~/.a2a/mcp.json — which agents the gateway should expose.
*
* We store the agent URL alongside the name so the gateway never needs to hit
* the control plane during startup. Adding/removing an agent is a single
* file write; the MCP client picks up the change on the next list_tools.
*/
import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";
export interface EnabledAgent {
name: string;
url: string;
description?: string;
addedAt: string;
}
export interface GatewayConfig {
version: 1;
agents: EnabledAgent[];
}
const cfgDir = () => path.join(os.homedir(), ".a2a");
const cfgFile = () => path.join(cfgDir(), "mcp.json");
const EMPTY: GatewayConfig = { version: 1, agents: [] };
export async function loadConfig(): Promise<GatewayConfig> {
try {
const raw = await fs.readFile(cfgFile(), "utf8");
const data = JSON.parse(raw);
if (data?.version !== 1 || !Array.isArray(data.agents)) return { ...EMPTY };
return data as GatewayConfig;
} catch (err: any) {
if (err?.code === "ENOENT") return { ...EMPTY };
throw err;
}
}
export async function saveConfig(cfg: GatewayConfig): Promise<void> {
await fs.mkdir(cfgDir(), { recursive: true });
await fs.writeFile(cfgFile(), JSON.stringify(cfg, null, 2), { mode: 0o600 });
}
export async function addAgent(agent: EnabledAgent): Promise<GatewayConfig> {
const cfg = await loadConfig();
const existing = cfg.agents.findIndex((a) => a.name === agent.name);
if (existing >= 0) cfg.agents[existing] = agent;
else cfg.agents.push(agent);
await saveConfig(cfg);
return cfg;
}
export async function removeAgent(name: string): Promise<boolean> {
const cfg = await loadConfig();
const before = cfg.agents.length;
cfg.agents = cfg.agents.filter((a) => a.name !== name);
if (cfg.agents.length === before) return false;
await saveConfig(cfg);
return true;
}