diff --git a/src/api.ts b/src/api.ts index 0582afa..8700741 100644 --- a/src/api.ts +++ b/src/api.ts @@ -15,6 +15,7 @@ export interface AgentRow { status: string; url?: string; description?: string; + card?: Record; } export class ControlPlaneClient { diff --git a/src/cli.ts b/src/cli.ts index 4d6a6f7..2e2ac65 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -43,6 +43,30 @@ function fail(msg: string, code = 1): never { process.exit(code); } +function normalizeMcpPath(path: unknown): string { + if (typeof path !== "string" || !path.trim()) return "/mcp"; + const trimmed = path.trim(); + return trimmed.startsWith("/") ? trimmed : `/${trimmed}`; +} + +function mcpTargetForAgent( + row: { name: string; url?: string; card?: Record }, + apiUrl: string, +): { url?: string; mcpPath: string } { + const imported = row.card?.capabilities?.a2a_cloud_import; + const mcp = imported?.mcp; + if (mcp?.mode === "generated") { + return { + url: `${apiUrl.replace(/\/+$/, "")}/v1/agents/${encodeURIComponent(row.name)}`, + mcpPath: normalizeMcpPath(mcp.path), + }; + } + return { + url: mcp?.base_url || row.url, + mcpPath: normalizeMcpPath(mcp?.path || row.card?.mcp_endpoint), + }; +} + async function prompt(label: string, opts: { hidden?: boolean } = {}): Promise { const rl = readline.createInterface({ input, output, terminal: true }); try { @@ -160,7 +184,9 @@ program output.write("(no agents added; try `a2amcp add `)\n"); return; } - for (const a of cfg.agents) output.write(` ${a.name} ${a.url}\n`); + for (const a of cfg.agents) { + output.write(` ${a.name} ${a.url}${a.mcpPath ?? "/mcp"}\n`); + } }); program @@ -171,11 +197,16 @@ program .action( async (name: string, opts: { url?: string; api?: string }) => { let url = opts.url; + let mcpPath = "/mcp"; let description: string | undefined; if (!url) { try { - const row = await (await client(opts.api)).getAgent(name); - url = row.url; + const creds = await loadCredentials(); + const apiUrl = resolveApiUrl(opts.api ?? creds?.apiUrl); + const row = await new ControlPlaneClient(apiUrl, creds?.token ?? null).getAgent(name); + const target = mcpTargetForAgent(row, apiUrl); + url = target.url; + mcpPath = target.mcpPath; description = row.description; } catch (err) { fail(err instanceof ApiError ? err.message : (err as Error).message); @@ -185,10 +216,11 @@ program await addAgent({ name, url, + mcpPath, description, addedAt: new Date().toISOString(), }); - output.write(`added ${name} -> ${url}\n`); + output.write(`added ${name} -> ${url.replace(/\/+$/, "")}${mcpPath}\n`); output.write("restart your MCP client to pick up the new tools.\n"); }, ); @@ -219,6 +251,7 @@ program const u = new UpstreamAgent({ name: a.name, url: a.url, + mcpPath: a.mcpPath, token: creds?.token ?? null, }); try { diff --git a/src/config.ts b/src/config.ts index 85526ad..f31cdc0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -12,6 +12,7 @@ import path from "node:path"; export interface EnabledAgent { name: string; url: string; + mcpPath?: string; description?: string; addedAt: string; } diff --git a/src/gateway.ts b/src/gateway.ts index 6ff7b8b..e515644 100644 --- a/src/gateway.ts +++ b/src/gateway.ts @@ -60,7 +60,7 @@ export async function buildGateway(opts: GatewayOptions = {}): Promise(method: string, params: unknown): Promise { const id = _nextId++; const timeoutMs = this.cfg.requestTimeoutMs ?? DEFAULT_RPC_TIMEOUT_MS; @@ -87,7 +100,7 @@ export class UpstreamAgent { if (this.cfg.token) headers["authorization"] = `Bearer ${this.cfg.token}`; try { - const resp = await fetch(`${this.cfg.url}/mcp`, { + const resp = await fetch(this.mcpUrl(), { method: "POST", headers, body: JSON.stringify({ jsonrpc: JSON_RPC, id, method, params }), @@ -193,7 +206,7 @@ export class UpstreamAgent { let resp: Response; try { - resp = await fetch(`${this.cfg.url}/mcp`, { + resp = await fetch(this.mcpUrl(), { method: "POST", headers, body: JSON.stringify({ @@ -356,7 +369,7 @@ export class UpstreamAgent { "mcp-session-id": upstreamSessionId, }; if (this.cfg.token) headers.authorization = `Bearer ${this.cfg.token}`; - const resp = await fetch(`${this.cfg.url}/mcp`, { + const resp = await fetch(this.mcpUrl(), { method: "POST", headers, body: JSON.stringify(envelope),