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

61
src/credentials.ts Normal file
View File

@@ -0,0 +1,61 @@
/**
* Reader for ~/.a2a/credentials.json — same file the Python `a2a` CLI writes.
*
* We deliberately *only read* this file (and overwrite it on `a2a-mcp login`)
* so a single login flow is shared between the Python and Node tooling.
*/
import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";
export const DEFAULT_API_URL = "https://api.a2acloud.io";
export interface Credentials {
apiUrl: string;
token: string;
email: string;
}
const credsDir = () => path.join(os.homedir(), ".a2a");
const credsFile = () => path.join(credsDir(), "credentials.json");
export async function loadCredentials(): Promise<Credentials | null> {
try {
const raw = await fs.readFile(credsFile(), "utf8");
const data = JSON.parse(raw);
if (!data?.token) return null;
return {
apiUrl: data.api_url ?? DEFAULT_API_URL,
token: data.token,
email: data.email ?? "",
};
} catch (err: any) {
if (err?.code === "ENOENT") return null;
throw err;
}
}
export async function saveCredentials(c: Credentials): Promise<void> {
await fs.mkdir(credsDir(), { recursive: true });
await fs.writeFile(
credsFile(),
JSON.stringify({ api_url: c.apiUrl, token: c.token, email: c.email }),
{ mode: 0o600 },
);
}
export async function clearCredentials(): Promise<boolean> {
try {
await fs.unlink(credsFile());
return true;
} catch (err: any) {
if (err?.code === "ENOENT") return false;
throw err;
}
}
export function resolveApiUrl(override?: string): string {
if (override) return override;
if (process.env.A2A_API_URL) return process.env.A2A_API_URL;
return DEFAULT_API_URL;
}