Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 024f14000e |
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "a2amcp",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "a2amcp",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.0.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "a2amcp",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"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": {
|
||||
|
||||
20
src/api.ts
20
src/api.ts
@@ -52,23 +52,21 @@ export class ControlPlaneClient {
|
||||
}
|
||||
|
||||
login(email: string, password: string) {
|
||||
return this.request<{ access_token: string; user: { email: string } }>(
|
||||
"POST",
|
||||
"/v1/auth/login",
|
||||
{ email, password },
|
||||
);
|
||||
return this.request<{
|
||||
access_token: string;
|
||||
user: { id: number; email: string };
|
||||
}>("POST", "/v1/auth/login", { email, password });
|
||||
}
|
||||
|
||||
signup(email: string, password: string) {
|
||||
return this.request<{ access_token: string; user: { email: string } }>(
|
||||
"POST",
|
||||
"/v1/auth/signup",
|
||||
{ email, password },
|
||||
);
|
||||
return this.request<{
|
||||
access_token: string;
|
||||
user: { id: number; email: string };
|
||||
}>("POST", "/v1/auth/signup", { email, password });
|
||||
}
|
||||
|
||||
me() {
|
||||
return this.request<{ email: string }>("GET", "/v1/me");
|
||||
return this.request<{ id: number; email: string }>("GET", "/v1/me");
|
||||
}
|
||||
|
||||
listAgents() {
|
||||
|
||||
@@ -92,7 +92,14 @@ program
|
||||
const api = resolveApiUrl(opts.api);
|
||||
try {
|
||||
const out = await new ControlPlaneClient(api, null).login(email, password);
|
||||
await saveCredentials({ apiUrl: api, token: out.access_token, email: out.user.email });
|
||||
const bucket = `user-${out.user.id}-files`;
|
||||
await saveCredentials({
|
||||
apiUrl: api,
|
||||
token: out.access_token,
|
||||
email: out.user.email,
|
||||
userId: out.user.id,
|
||||
bucket,
|
||||
});
|
||||
output.write(`logged in as ${out.user.email} @ ${api}\n`);
|
||||
} catch (err) {
|
||||
fail(err instanceof ApiError ? err.message : (err as Error).message);
|
||||
|
||||
@@ -14,6 +14,8 @@ export interface Credentials {
|
||||
apiUrl: string;
|
||||
token: string;
|
||||
email: string;
|
||||
userId?: number;
|
||||
bucket?: string;
|
||||
}
|
||||
|
||||
const credsDir = () => path.join(os.homedir(), ".a2a");
|
||||
@@ -28,6 +30,8 @@ export async function loadCredentials(): Promise<Credentials | null> {
|
||||
apiUrl: data.api_url ?? DEFAULT_API_URL,
|
||||
token: data.token,
|
||||
email: data.email ?? "",
|
||||
userId: typeof data.user_id === "number" ? data.user_id : undefined,
|
||||
bucket: typeof data.bucket === "string" ? data.bucket : undefined,
|
||||
};
|
||||
} catch (err: any) {
|
||||
if (err?.code === "ENOENT") return null;
|
||||
@@ -37,11 +41,14 @@ export async function loadCredentials(): Promise<Credentials | null> {
|
||||
|
||||
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 },
|
||||
);
|
||||
const out: Record<string, unknown> = {
|
||||
api_url: c.apiUrl,
|
||||
token: c.token,
|
||||
email: c.email,
|
||||
};
|
||||
if (c.userId !== undefined) out.user_id = c.userId;
|
||||
if (c.bucket !== undefined) out.bucket = c.bucket;
|
||||
await fs.writeFile(credsFile(), JSON.stringify(out), { mode: 0o600 });
|
||||
}
|
||||
|
||||
export async function clearCredentials(): Promise<boolean> {
|
||||
|
||||
@@ -39,14 +39,25 @@ export interface GatewayHandle {
|
||||
|
||||
export async function buildGateway(opts: GatewayOptions = {}): Promise<GatewayHandle> {
|
||||
const cfg = opts.agents ?? (await loadConfig()).agents;
|
||||
const token =
|
||||
opts.token !== undefined
|
||||
? opts.token
|
||||
: (await loadCredentials())?.token ?? null;
|
||||
const creds = opts.token !== undefined ? null : await loadCredentials();
|
||||
const token = opts.token !== undefined ? opts.token : creds?.token ?? null;
|
||||
// Forward CP credentials + bucket as params._meta so upstream agents
|
||||
// can act on behalf of the caller (mirrors the platform-orchestrator
|
||||
// call shape). When the user isn't logged in, _meta is omitted and
|
||||
// upstream sees the same unauthenticated context as before.
|
||||
const cpJwt = creds?.token ?? null;
|
||||
const cpUrl = creds?.apiUrl ?? null;
|
||||
const bucket = creds?.bucket ?? null;
|
||||
|
||||
const upstreams = new Map<string, UpstreamAgent>();
|
||||
for (const a of cfg) {
|
||||
upstreams.set(a.name, new UpstreamAgent({ name: a.name, url: a.url, token }));
|
||||
upstreams.set(
|
||||
a.name,
|
||||
new UpstreamAgent({
|
||||
name: a.name, url: a.url, token,
|
||||
cpJwt, cpUrl, bucket,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
const server =
|
||||
|
||||
@@ -12,6 +12,18 @@ export interface UpstreamConfig {
|
||||
name: string;
|
||||
url: string;
|
||||
token: string | null;
|
||||
/** Forwarded to the upstream as ``params._meta.cp_jwt`` on tools/call.
|
||||
* Lets the upstream agent act on the caller's behalf (file/agent CRUD
|
||||
* on /v1/me/*) — same path the platform orchestrator uses. */
|
||||
cpJwt?: string | null;
|
||||
/** Paired with ``cpJwt``. The upstream agent uses this as the base URL
|
||||
* when calling back into /v1/me/*. */
|
||||
cpUrl?: string | null;
|
||||
/** User's MinIO bucket (``user-<id>-files``). Forwarded as
|
||||
* ``params._meta.bucket`` so agents that touch the workspace
|
||||
* (agent-builder, file tools) get a context whose
|
||||
* ``ctx.workspace.bucket`` resolves to the right bucket. */
|
||||
bucket?: string | null;
|
||||
}
|
||||
|
||||
export interface UpstreamTool {
|
||||
@@ -88,9 +100,12 @@ export class UpstreamAgent {
|
||||
name: string,
|
||||
arguments_: Record<string, unknown>,
|
||||
): Promise<UpstreamCallResult> {
|
||||
return this.rpc<UpstreamCallResult>("tools/call", {
|
||||
name,
|
||||
arguments: arguments_,
|
||||
});
|
||||
const meta: Record<string, unknown> = {};
|
||||
if (this.cfg.cpJwt) meta.cp_jwt = this.cfg.cpJwt;
|
||||
if (this.cfg.cpUrl) meta.cp_url = this.cfg.cpUrl;
|
||||
if (this.cfg.bucket) meta.bucket = this.cfg.bucket;
|
||||
const params: Record<string, unknown> = { name, arguments: arguments_ };
|
||||
if (Object.keys(meta).length > 0) params._meta = meta;
|
||||
return this.rpc<UpstreamCallResult>("tools/call", params);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user