Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 633ca5dad6 | |||
| fd71a528e9 | |||
| f0f6fe9672 |
28
README.md
28
README.md
@@ -1,4 +1,4 @@
|
||||
# @a2a/mcp
|
||||
# a2amcp
|
||||
|
||||
MCP gateway for a2acloud agents. It runs locally as a stdio MCP server and
|
||||
exposes deployed A2A agents as tools to Claude Code, Cursor, and other MCP
|
||||
@@ -7,9 +7,9 @@ clients.
|
||||
## Quickstart
|
||||
|
||||
```bash
|
||||
npx -y @a2a/mcp login
|
||||
npx -y @a2a/mcp add <agent-name>
|
||||
npx -y @a2a/mcp doctor
|
||||
npx -y a2amcp login
|
||||
npx -y a2amcp add <agent-name>
|
||||
npx -y a2amcp doctor
|
||||
```
|
||||
|
||||
Add it to your MCP client config:
|
||||
@@ -19,7 +19,7 @@ Add it to your MCP client config:
|
||||
"mcpServers": {
|
||||
"a2a": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@a2a/mcp"]
|
||||
"args": ["-y", "a2amcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,14 +30,14 @@ Restart the client after adding or removing agents.
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
a2a-mcp login # save control-plane credentials
|
||||
a2a-mcp whoami # show current account
|
||||
a2a-mcp agents # list agents visible to your account
|
||||
a2a-mcp add <name> # expose one agent through this gateway
|
||||
a2a-mcp list # list locally enabled agents
|
||||
a2a-mcp remove <name> # stop exposing an agent
|
||||
a2a-mcp doctor # probe enabled agents and count tools
|
||||
a2a-mcp logout # clear local credentials
|
||||
a2amcp login # save control-plane credentials
|
||||
a2amcp whoami # show current account
|
||||
a2amcp agents # list agents visible to your account
|
||||
a2amcp add <name> # expose one agent through this gateway
|
||||
a2amcp list # list locally enabled agents
|
||||
a2amcp remove <name> # stop exposing an agent
|
||||
a2amcp doctor # probe enabled agents and count tools
|
||||
a2amcp logout # clear local credentials
|
||||
```
|
||||
|
||||
Running `a2a-mcp` with no command starts the MCP gateway over stdio.
|
||||
Running `a2amcp` with no command starts the MCP gateway over stdio.
|
||||
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -1,19 +1,20 @@
|
||||
{
|
||||
"name": "@a2a/mcp",
|
||||
"version": "0.1.0",
|
||||
"name": "a2amcp",
|
||||
"version": "0.1.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@a2a/mcp",
|
||||
"version": "0.1.0",
|
||||
"name": "a2amcp",
|
||||
"version": "0.1.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.4",
|
||||
"commander": "^12.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"a2a-mcp": "dist/cli.js"
|
||||
"a2a-mcp": "dist/cli.js",
|
||||
"a2amcp": "dist/cli.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.12.0",
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "@a2a/mcp",
|
||||
"version": "0.1.0",
|
||||
"name": "a2amcp",
|
||||
"version": "0.1.3",
|
||||
"description": "MCP gateway for a2acloud agents. Run as a local MCP server; expose any number of deployed A2A agents to Claude Code, Cursor, and other MCP clients.",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"a2amcp": "dist/cli.js",
|
||||
"a2a-mcp": "dist/cli.js"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
|
||||
20
src/cli.ts
20
src/cli.ts
@@ -1,15 +1,19 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* `a2a-mcp` — install once, expose any number of a2acloud agents to your MCP
|
||||
* `a2amcp` — install once, expose any number of a2acloud agents to your MCP
|
||||
* client (Claude Code, Cursor, etc.).
|
||||
*
|
||||
* No-argument invocation runs the gateway over stdio. That's the form an MCP
|
||||
* client launches. Subcommands manage the local agent list, auth, etc.
|
||||
*/
|
||||
import { createRequire } from "node:module";
|
||||
import { Command } from "commander";
|
||||
import readline from "node:readline/promises";
|
||||
import { stdin as input, stdout as output, stderr } from "node:process";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const pkg = require("../package.json") as { version: string };
|
||||
|
||||
import { ApiError, ControlPlaneClient } from "./api.js";
|
||||
import { addAgent, loadConfig, removeAgent } from "./config.js";
|
||||
import {
|
||||
@@ -24,9 +28,9 @@ import { runStdio } from "./gateway.js";
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name("a2a-mcp")
|
||||
.name("a2amcp")
|
||||
.description("MCP gateway for a2acloud agents.")
|
||||
.version("0.1.0");
|
||||
.version(pkg.version);
|
||||
|
||||
async function client(apiOverride?: string): Promise<ControlPlaneClient> {
|
||||
const creds = await loadCredentials();
|
||||
@@ -69,7 +73,7 @@ program
|
||||
try {
|
||||
await runStdio();
|
||||
} catch (err) {
|
||||
stderr.write(`a2a-mcp: ${(err as Error).message}\n`);
|
||||
stderr.write(`a2amcp: ${(err as Error).message}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
@@ -108,7 +112,7 @@ program
|
||||
.description("Show the currently logged-in user.")
|
||||
.action(async () => {
|
||||
const creds = await loadCredentials();
|
||||
if (!creds) fail("not logged in (run `a2a-mcp login`)");
|
||||
if (!creds) fail("not logged in (run `a2amcp login`)");
|
||||
try {
|
||||
const me = await (await client()).me();
|
||||
output.write(`${me.email} (${creds.apiUrl})\n`);
|
||||
@@ -146,7 +150,7 @@ program
|
||||
.action(async () => {
|
||||
const cfg = await loadConfig();
|
||||
if (cfg.agents.length === 0) {
|
||||
output.write("(no agents added; try `a2a-mcp add <name>`)\n");
|
||||
output.write("(no agents added; try `a2amcp add <name>`)\n");
|
||||
return;
|
||||
}
|
||||
for (const a of cfg.agents) output.write(` ${a.name} ${a.url}\n`);
|
||||
@@ -229,7 +233,7 @@ program
|
||||
mcpServers: {
|
||||
a2a: {
|
||||
command: "npx",
|
||||
args: ["-y", "@a2a/mcp"],
|
||||
args: ["-y", "a2amcp"],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -237,6 +241,6 @@ program
|
||||
});
|
||||
|
||||
program.parseAsync(process.argv).catch((err) => {
|
||||
stderr.write(`a2a-mcp: ${(err as Error).message}\n`);
|
||||
stderr.write(`a2amcp: ${(err as Error).message}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* 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`)
|
||||
* We deliberately *only read* this file (and overwrite it on `a2amcp login`)
|
||||
* so a single login flow is shared between the Python and Node tooling.
|
||||
*/
|
||||
import { promises as fs } from "node:fs";
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* Naming: ``{agent}__{skill}``. Double underscore separator. Skill names
|
||||
* containing ``__`` are not supported.
|
||||
*/
|
||||
import { createRequire } from "node:module";
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
@@ -17,6 +18,9 @@ import { loadConfig, type EnabledAgent } from "./config.js";
|
||||
import { loadCredentials } from "./credentials.js";
|
||||
import { UpstreamAgent, UpstreamError, type UpstreamTool } from "./upstream.js";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const pkg = require("../package.json") as { version: string };
|
||||
|
||||
export const SEP = "__";
|
||||
|
||||
export interface GatewayOptions {
|
||||
@@ -48,7 +52,7 @@ export async function buildGateway(opts: GatewayOptions = {}): Promise<GatewayHa
|
||||
const server =
|
||||
opts.server ??
|
||||
new Server(
|
||||
{ name: "a2a-mcp", version: "0.1.0" },
|
||||
{ name: "a2amcp", version: pkg.version },
|
||||
{ capabilities: { tools: { listChanged: false } } },
|
||||
);
|
||||
|
||||
@@ -70,9 +74,9 @@ export async function buildGateway(opts: GatewayOptions = {}): Promise<GatewayHa
|
||||
} catch (err) {
|
||||
// Silently skip an upstream that's down — surfacing an error here
|
||||
// would break the client's whole tool list. The user can debug via
|
||||
// `a2a-mcp doctor`.
|
||||
// `a2amcp doctor`.
|
||||
process.stderr.write(
|
||||
`a2a-mcp: skipping ${u.name}: ${(err as Error).message}\n`,
|
||||
`a2amcp: skipping ${u.name}: ${(err as Error).message}\n`,
|
||||
);
|
||||
}
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user