@agentrq/acp-gateway 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -1
- package/dist/__tests__/acpClient.test.js +220 -106
- package/dist/__tests__/acpClient.test.js.map +1 -1
- package/dist/__tests__/agentInfo.test.js +79 -0
- package/dist/__tests__/agentInfo.test.js.map +1 -0
- package/dist/__tests__/agentInstall.test.js +342 -0
- package/dist/__tests__/agentInstall.test.js.map +1 -0
- package/dist/__tests__/auth.test.js +283 -0
- package/dist/__tests__/auth.test.js.map +1 -0
- package/dist/__tests__/config.test.js +45 -0
- package/dist/__tests__/config.test.js.map +1 -1
- package/dist/__tests__/index.test.js +544 -12
- package/dist/__tests__/index.test.js.map +1 -1
- package/dist/__tests__/mcpClient.test.js +12 -0
- package/dist/__tests__/mcpClient.test.js.map +1 -1
- package/dist/__tests__/registry.test.js +175 -0
- package/dist/__tests__/registry.test.js.map +1 -0
- package/dist/acpClient.js +215 -59
- package/dist/acpClient.js.map +1 -1
- package/dist/agentInfo.js +69 -0
- package/dist/agentInfo.js.map +1 -0
- package/dist/agentInstall.js +241 -0
- package/dist/agentInstall.js.map +1 -0
- package/dist/auth.js +189 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.js +50 -16
- package/dist/config.js.map +1 -1
- package/dist/index.js +508 -46
- package/dist/index.js.map +1 -1
- package/dist/mcpClient.js +12 -1
- package/dist/mcpClient.js.map +1 -1
- package/dist/registry.js +118 -0
- package/dist/registry.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agentInstall.ts
|
|
3
|
+
*
|
|
4
|
+
* Downloads, verifies and unpacks the binary distribution of a registry agent,
|
|
5
|
+
* caching it so the download happens once.
|
|
6
|
+
*
|
|
7
|
+
* This is the one place in the gateway that fetches a third-party executable
|
|
8
|
+
* and runs it, so the rules here are deliberately strict: an archive is only
|
|
9
|
+
* installed when the registry publishes a `sha256` that the download matches,
|
|
10
|
+
* unless the user has explicitly said otherwise.
|
|
11
|
+
*/
|
|
12
|
+
import { createHash } from "node:crypto";
|
|
13
|
+
import { spawn } from "node:child_process";
|
|
14
|
+
import { chmod, cp, mkdir, mkdtemp, rename, rm, writeFile } from "node:fs/promises";
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { homedir, tmpdir } from "node:os";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
import { availableKinds, findAgent, packageLaunchSpec, selectBinaryTarget, } from "./registry.js";
|
|
19
|
+
/**
|
|
20
|
+
* Where downloaded agents live, unless overridden.
|
|
21
|
+
*
|
|
22
|
+
* Follows each platform's own convention: `%LOCALAPPDATA%` on Windows,
|
|
23
|
+
* `XDG_CACHE_HOME` where it is set, and `~/.cache` otherwise.
|
|
24
|
+
*/
|
|
25
|
+
export function defaultCacheDir(platform = process.platform, env = process.env) {
|
|
26
|
+
const base = platform === "win32"
|
|
27
|
+
? env.LOCALAPPDATA || path.join(homedir(), "AppData", "Local")
|
|
28
|
+
: env.XDG_CACHE_HOME || path.join(homedir(), ".cache");
|
|
29
|
+
return path.join(base, "acp-gateway", "agents");
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Classifies an archive by its URL.
|
|
33
|
+
*
|
|
34
|
+
* The registry documents exactly which formats may appear; anything else is a
|
|
35
|
+
* raw executable to be saved as-is.
|
|
36
|
+
*/
|
|
37
|
+
export function archiveKind(url) {
|
|
38
|
+
const pathname = url.split("?")[0].split("#")[0].toLowerCase();
|
|
39
|
+
if (pathname.endsWith(".zip"))
|
|
40
|
+
return "zip";
|
|
41
|
+
if (pathname.endsWith(".tar.gz") || pathname.endsWith(".tgz"))
|
|
42
|
+
return "tar.gz";
|
|
43
|
+
if (pathname.endsWith(".tar.bz2") || pathname.endsWith(".tbz2"))
|
|
44
|
+
return "tar.bz2";
|
|
45
|
+
return "raw";
|
|
46
|
+
}
|
|
47
|
+
/** The directory one agent build is unpacked into. */
|
|
48
|
+
export function installDir(cacheDir, agentId, target, version) {
|
|
49
|
+
// The version is part of the path so a registry bump installs alongside the
|
|
50
|
+
// old build rather than half-overwriting it.
|
|
51
|
+
return path.join(cacheDir, `${agentId}@${version}`, target);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the registry's `cmd` inside the install directory.
|
|
55
|
+
*
|
|
56
|
+
* `cmd` is third-party text, so a path that climbs out of the directory — and
|
|
57
|
+
* would have us run something else entirely — is rejected rather than resolved.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveExecutable(dir, cmd) {
|
|
60
|
+
const executable = path.resolve(dir, cmd);
|
|
61
|
+
const root = path.resolve(dir);
|
|
62
|
+
if (executable !== root && !executable.startsWith(root + path.sep)) {
|
|
63
|
+
throw new Error(`Agent command "${cmd}" points outside its install directory; refusing to run it.`);
|
|
64
|
+
}
|
|
65
|
+
return executable;
|
|
66
|
+
}
|
|
67
|
+
export function sha256(data) {
|
|
68
|
+
return createHash("sha256").update(data).digest("hex");
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Refuses an archive we cannot vouch for.
|
|
72
|
+
*
|
|
73
|
+
* Roughly half the registry's binary targets publish no checksum. Running one
|
|
74
|
+
* means trusting whatever the vendor's host served, so it takes an explicit
|
|
75
|
+
* opt-in rather than happening quietly.
|
|
76
|
+
*/
|
|
77
|
+
export function assertVerifiable(agent, target, allowUnverified) {
|
|
78
|
+
if (target.sha256 || allowUnverified)
|
|
79
|
+
return;
|
|
80
|
+
throw new Error(`The ACP registry publishes no sha256 for "${agent.id}" on this platform, so the ` +
|
|
81
|
+
`download cannot be verified. Re-run with --allow-unverified-agent to install it anyway, ` +
|
|
82
|
+
`or install the agent yourself and pass it after --.`);
|
|
83
|
+
}
|
|
84
|
+
/** Checks a download against the registry's checksum. */
|
|
85
|
+
export function assertChecksum(agent, target, data) {
|
|
86
|
+
if (!target.sha256)
|
|
87
|
+
return;
|
|
88
|
+
const actual = sha256(data);
|
|
89
|
+
if (actual.toLowerCase() !== target.sha256.toLowerCase()) {
|
|
90
|
+
throw new Error(`Checksum mismatch for "${agent.id}": the registry expects ${target.sha256.toLowerCase()} ` +
|
|
91
|
+
`but ${target.archive} produced ${actual}. Refusing to run it.`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Downloads an archive into memory so it can be checksummed before it touches disk. */
|
|
95
|
+
export async function downloadArchive(url, fetchImpl = fetch) {
|
|
96
|
+
const response = await fetchImpl(url);
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
|
|
99
|
+
}
|
|
100
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
101
|
+
}
|
|
102
|
+
/** Runs an extraction tool, failing with its own diagnostics attached. */
|
|
103
|
+
export async function runExtractionTool(command, args, cwd) {
|
|
104
|
+
const child = spawn(command, args, { cwd, stdio: ["ignore", "ignore", "pipe"] });
|
|
105
|
+
let stderr = "";
|
|
106
|
+
child.stderr?.on("data", (chunk) => {
|
|
107
|
+
stderr += chunk.toString();
|
|
108
|
+
});
|
|
109
|
+
await new Promise((resolve, reject) => {
|
|
110
|
+
child.on("error", (err) => reject(new Error(`Could not run "${command}" to unpack the agent archive: ${err.message}. ` +
|
|
111
|
+
`Install it, or install the agent yourself and pass it after --.`)));
|
|
112
|
+
child.on("exit", (code) => {
|
|
113
|
+
if (code === 0)
|
|
114
|
+
return resolve();
|
|
115
|
+
reject(new Error(`"${command}" failed to unpack the agent archive (code=${code}): ${stderr.trim()}`));
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Unpacks an archive into `dir`.
|
|
121
|
+
*
|
|
122
|
+
* Uses the system's own tools rather than adding archive libraries as
|
|
123
|
+
* dependencies. `tar` handles the tarballs everywhere and zips on macOS and
|
|
124
|
+
* Windows, where it is bsdtar; GNU tar cannot read zips, so Linux falls back
|
|
125
|
+
* to `unzip`.
|
|
126
|
+
*/
|
|
127
|
+
export async function extractArchive(data, kind, dir, cmd, platform = process.platform) {
|
|
128
|
+
await mkdir(dir, { recursive: true });
|
|
129
|
+
if (kind === "raw") {
|
|
130
|
+
// No archive to unpack: the download is the executable itself.
|
|
131
|
+
await writeFile(path.join(dir, path.basename(cmd)), data);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const archivePath = path.join(dir, `archive.${kind}`);
|
|
135
|
+
await writeFile(archivePath, data);
|
|
136
|
+
try {
|
|
137
|
+
if (kind === "zip" && platform === "linux") {
|
|
138
|
+
await runExtractionTool("unzip", ["-q", "-o", archivePath], dir);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
await runExtractionTool("tar", ["-xf", archivePath], dir);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
await rm(archivePath, { force: true });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Makes a registry agent runnable, returning the command that launches it.
|
|
150
|
+
*
|
|
151
|
+
* A cached install is reused as-is; the version in the cache path means a
|
|
152
|
+
* registry bump installs a fresh copy rather than reusing a stale one.
|
|
153
|
+
*/
|
|
154
|
+
export async function installBinaryAgent({ agent, target, platformTarget, cacheDir, allowUnverified = false, fetchImpl = fetch, platform = process.platform, }) {
|
|
155
|
+
cacheDir ??= defaultCacheDir(platform);
|
|
156
|
+
const dir = installDir(cacheDir, agent.id, platformTarget, agent.version);
|
|
157
|
+
const executable = resolveExecutable(dir, target.cmd);
|
|
158
|
+
const spec = {
|
|
159
|
+
command: executable,
|
|
160
|
+
args: target.args ?? [],
|
|
161
|
+
env: target.env,
|
|
162
|
+
kind: "binary",
|
|
163
|
+
};
|
|
164
|
+
if (existsSync(executable)) {
|
|
165
|
+
console.error(`[registry] Using cached ${agent.id} ${agent.version} from ${dir}`);
|
|
166
|
+
return spec;
|
|
167
|
+
}
|
|
168
|
+
assertVerifiable(agent, target, allowUnverified);
|
|
169
|
+
console.error(`[registry] Downloading ${agent.id} ${agent.version} from ${target.archive}`);
|
|
170
|
+
const data = await downloadArchive(target.archive, fetchImpl);
|
|
171
|
+
assertChecksum(agent, target, data);
|
|
172
|
+
if (!target.sha256) {
|
|
173
|
+
console.error(`[registry] ⚠️ ${agent.id} publishes no checksum; installing it unverified at your request.`);
|
|
174
|
+
}
|
|
175
|
+
// Unpack somewhere temporary and move into place only once it succeeded, so
|
|
176
|
+
// a failed extraction never leaves a half-installed agent to be cached.
|
|
177
|
+
const staging = await mkdtemp(path.join(tmpdir(), `acp-gateway-${agent.id}-`));
|
|
178
|
+
try {
|
|
179
|
+
await extractArchive(data, archiveKind(target.archive), staging, target.cmd, platform);
|
|
180
|
+
const staged = resolveExecutable(staging, target.cmd);
|
|
181
|
+
if (!existsSync(staged)) {
|
|
182
|
+
throw new Error(`The archive for "${agent.id}" does not contain "${target.cmd}" where the registry says it should.`);
|
|
183
|
+
}
|
|
184
|
+
await rm(dir, { recursive: true, force: true });
|
|
185
|
+
await mkdir(path.dirname(dir), { recursive: true });
|
|
186
|
+
await moveInto(staging, dir);
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
await rm(staging, { recursive: true, force: true });
|
|
190
|
+
}
|
|
191
|
+
// Archive formats do not always carry the executable bit. Windows has no
|
|
192
|
+
// such bit — chmod there only toggles the read-only flag — so skip it.
|
|
193
|
+
if (platform !== "win32") {
|
|
194
|
+
await chmod(executable, 0o755);
|
|
195
|
+
}
|
|
196
|
+
console.error(`[registry] Installed ${agent.id} ${agent.version} to ${dir}`);
|
|
197
|
+
return spec;
|
|
198
|
+
}
|
|
199
|
+
/** Moves a staged install into the cache, copying across filesystems if needed. */
|
|
200
|
+
export async function moveInto(from, to) {
|
|
201
|
+
try {
|
|
202
|
+
await rename(from, to);
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// rename() fails across devices — the temp dir is often a different mount.
|
|
206
|
+
await cp(from, to, { recursive: true });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Turns a registry id into the command that runs that agent, installing it
|
|
211
|
+
* first where the only distribution is a binary.
|
|
212
|
+
*
|
|
213
|
+
* Package distributions are preferred over binaries: npm and PyPI verify what
|
|
214
|
+
* they serve, and nothing has to be downloaded, unpacked or cached here.
|
|
215
|
+
*/
|
|
216
|
+
export async function resolveAgentLaunch({ id, registry, platformTarget, cacheDir, allowUnverified = false, fetchImpl = fetch, platform = process.platform, }) {
|
|
217
|
+
const agent = findAgent(registry, id);
|
|
218
|
+
if (!agent) {
|
|
219
|
+
throw new Error(`No agent "${id}" in the ACP registry. Run --list-agents to see what it publishes.`);
|
|
220
|
+
}
|
|
221
|
+
const kinds = availableKinds(agent, platformTarget);
|
|
222
|
+
if (!kinds.length) {
|
|
223
|
+
const published = Object.keys(agent.distribution).join(", ") || "nothing";
|
|
224
|
+
throw new Error(`The ACP registry publishes no build of "${id}" for ${platformTarget ?? `${process.platform}/${process.arch}`} ` +
|
|
225
|
+
`(it publishes: ${published}).`);
|
|
226
|
+
}
|
|
227
|
+
if (kinds.includes("npx"))
|
|
228
|
+
return packageLaunchSpec("npx", agent.distribution.npx, platform);
|
|
229
|
+
if (kinds.includes("uvx"))
|
|
230
|
+
return packageLaunchSpec("uvx", agent.distribution.uvx, platform);
|
|
231
|
+
return installBinaryAgent({
|
|
232
|
+
agent,
|
|
233
|
+
target: selectBinaryTarget(agent, platformTarget),
|
|
234
|
+
platformTarget: platformTarget,
|
|
235
|
+
cacheDir,
|
|
236
|
+
allowUnverified,
|
|
237
|
+
fetchImpl,
|
|
238
|
+
platform,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
//# sourceMappingURL=agentInstall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentInstall.js","sourceRoot":"","sources":["../src/agentInstall.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auth.ts
|
|
3
|
+
*
|
|
4
|
+
* ACP authentication support: reading the login methods an agent advertises
|
|
5
|
+
* during `initialize`, recognising the protocol's `auth_required` failure, and
|
|
6
|
+
* running either kind of login on the user's behalf.
|
|
7
|
+
*
|
|
8
|
+
* See https://agentclientprotocol.com/protocol/v1/authentication
|
|
9
|
+
*/
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import { createInterface } from "node:readline/promises";
|
|
12
|
+
/** JSON-RPC code ACP reserves for "the user must authenticate first". */
|
|
13
|
+
export const AUTH_REQUIRED_CODE = -32000;
|
|
14
|
+
/**
|
|
15
|
+
* `type` discriminates the two kinds of auth method on the wire, and the
|
|
16
|
+
* protocol treats a missing `type` as `agent`.
|
|
17
|
+
*/
|
|
18
|
+
export function authMethodType(method) {
|
|
19
|
+
return method.type === "terminal" ? "terminal" : "agent";
|
|
20
|
+
}
|
|
21
|
+
/** Renders the agent's login options as a numbered list for the terminal. */
|
|
22
|
+
export function describeAuthMethods(methods) {
|
|
23
|
+
if (!methods?.length) {
|
|
24
|
+
return "The agent advertises no authentication methods — no login is needed.";
|
|
25
|
+
}
|
|
26
|
+
return methods
|
|
27
|
+
.map((m, i) => {
|
|
28
|
+
const kind = authMethodType(m) === "terminal" ? " [terminal login]" : "";
|
|
29
|
+
const description = m.description ? ` — ${m.description}` : "";
|
|
30
|
+
return ` ${i + 1}. ${m.name} (${m.id})${kind}${description}`;
|
|
31
|
+
})
|
|
32
|
+
.join("\n");
|
|
33
|
+
}
|
|
34
|
+
/** Pulls `code`/`message` out of a JSON-RPC failure in either shape it arrives in. */
|
|
35
|
+
function errorParts(err) {
|
|
36
|
+
if (!err || typeof err !== "object")
|
|
37
|
+
return { message: "" };
|
|
38
|
+
const candidate = err;
|
|
39
|
+
const source = candidate.error && typeof candidate.error === "object"
|
|
40
|
+
? candidate.error
|
|
41
|
+
: candidate;
|
|
42
|
+
return {
|
|
43
|
+
code: typeof source.code === "number" ? source.code : undefined,
|
|
44
|
+
message: typeof source.message === "string" ? source.message : "",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Tells an "authenticate first" refusal apart from any other failure.
|
|
49
|
+
*
|
|
50
|
+
* ACP carries `auth_required` on the reserved code -32000, which agents also
|
|
51
|
+
* use for unrelated errors (a denied permission, for one), so the message has
|
|
52
|
+
* to agree before we send the user through a login.
|
|
53
|
+
*/
|
|
54
|
+
export function isAuthRequiredError(err) {
|
|
55
|
+
const { code, message } = errorParts(err);
|
|
56
|
+
return code === AUTH_REQUIRED_CODE && /auth/i.test(message);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Chooses the login method to run without asking anyone.
|
|
60
|
+
*
|
|
61
|
+
* An explicitly named id always wins. Otherwise `agent` methods come first:
|
|
62
|
+
* the agent drives those itself, so they work in an unattended gateway, while
|
|
63
|
+
* a `terminal` method needs a human at a TTY.
|
|
64
|
+
*/
|
|
65
|
+
export function pickAuthMethod(methods, { preferredId, allowTerminal = false } = {}) {
|
|
66
|
+
if (!methods?.length)
|
|
67
|
+
return undefined;
|
|
68
|
+
if (preferredId)
|
|
69
|
+
return methods.find((m) => m.id === preferredId);
|
|
70
|
+
return (methods.find((m) => authMethodType(m) === "agent") ??
|
|
71
|
+
(allowTerminal ? methods.find((m) => authMethodType(m) === "terminal") : undefined));
|
|
72
|
+
}
|
|
73
|
+
/** Asks on stderr so the gateway's stdout stays free for its own output. */
|
|
74
|
+
async function askOnTerminal(question) {
|
|
75
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
76
|
+
try {
|
|
77
|
+
return await rl.question(question);
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
rl.close();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Asks the user which login to use, the way an editor would on first run.
|
|
85
|
+
*
|
|
86
|
+
* A single method needs no question. An empty answer takes the first method,
|
|
87
|
+
* and an unrecognised one re-asks rather than logging in with something the
|
|
88
|
+
* user did not choose.
|
|
89
|
+
*/
|
|
90
|
+
export async function promptForAuthMethod(methods, ask = askOnTerminal) {
|
|
91
|
+
if (!methods.length)
|
|
92
|
+
return undefined;
|
|
93
|
+
if (methods.length === 1)
|
|
94
|
+
return methods[0];
|
|
95
|
+
console.error(`\n[auth] The agent requires a login. Available methods:\n${describeAuthMethods(methods)}`);
|
|
96
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
97
|
+
const answer = (await ask(`[auth] Choose a method [1-${methods.length}, default 1]: `)).trim();
|
|
98
|
+
if (!answer)
|
|
99
|
+
return methods[0];
|
|
100
|
+
const byIndex = Number(answer);
|
|
101
|
+
if (Number.isInteger(byIndex) && byIndex >= 1 && byIndex <= methods.length) {
|
|
102
|
+
return methods[byIndex - 1];
|
|
103
|
+
}
|
|
104
|
+
const byId = methods.find((m) => m.id === answer);
|
|
105
|
+
if (byId)
|
|
106
|
+
return byId;
|
|
107
|
+
console.error(`[auth] "${answer}" is not one of the listed methods.`);
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Runs a `terminal` login by re-launching the configured agent interactively.
|
|
113
|
+
*
|
|
114
|
+
* The protocol has the client reproduce its own agent invocation with the
|
|
115
|
+
* method's extra args and env, hand the process the user's terminal, and read
|
|
116
|
+
* success off the exit status.
|
|
117
|
+
*/
|
|
118
|
+
export async function runTerminalAuth(method, launch) {
|
|
119
|
+
const extra = method.args ?? [];
|
|
120
|
+
const extraEnv = method.env ?? {};
|
|
121
|
+
const args = [...launch.args, ...extra];
|
|
122
|
+
console.error(`[auth] Running terminal login: ${launch.command} ${args.join(" ")}\n` +
|
|
123
|
+
`[auth] Complete the login in your terminal; the gateway resumes when it exits.`);
|
|
124
|
+
const child = spawn(launch.command, args, {
|
|
125
|
+
stdio: "inherit",
|
|
126
|
+
env: { ...process.env, ...launch.env, ...extraEnv },
|
|
127
|
+
});
|
|
128
|
+
await new Promise((resolve, reject) => {
|
|
129
|
+
child.on("error", (err) => reject(new Error(`Terminal login "${method.id}" failed to start: ${err.message}`)));
|
|
130
|
+
child.on("exit", (code, signal) => {
|
|
131
|
+
if (code === 0)
|
|
132
|
+
return resolve();
|
|
133
|
+
reject(new Error(`Terminal login "${method.id}" failed (code=${code}, signal=${signal}).`));
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/** Runs whichever kind of login the chosen method calls for. */
|
|
138
|
+
export async function runAuthMethod(connection, method, launch) {
|
|
139
|
+
if (authMethodType(method) === "terminal") {
|
|
140
|
+
await runTerminalAuth(method, launch);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// A `terminal` method must never reach `authenticate` — the agent does not
|
|
144
|
+
// implement one for it.
|
|
145
|
+
await connection.authenticate({ methodId: method.id });
|
|
146
|
+
}
|
|
147
|
+
console.error(`[auth] Logged in with "${method.name}" (${method.id}).`);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Logs in: picks a method — asking the user when one is there to ask — and
|
|
151
|
+
* runs it. Returns the method used, or undefined when the agent advertises no
|
|
152
|
+
* login at all.
|
|
153
|
+
*/
|
|
154
|
+
export async function login({ connection, methods, launch, preferredId, interactive = false, ask, }) {
|
|
155
|
+
if (!methods?.length) {
|
|
156
|
+
console.error("[auth] The agent advertises no authentication methods; nothing to log in to.");
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
let method = pickAuthMethod(methods, { preferredId, allowTerminal: interactive });
|
|
160
|
+
if (preferredId && !method) {
|
|
161
|
+
throw new Error(`Unknown authentication method "${preferredId}". Available:\n${describeAuthMethods(methods)}`);
|
|
162
|
+
}
|
|
163
|
+
if (!preferredId && interactive) {
|
|
164
|
+
method = await promptForAuthMethod(methods, ask);
|
|
165
|
+
}
|
|
166
|
+
if (!method) {
|
|
167
|
+
throw new Error(`No usable authentication method. Available:\n${describeAuthMethods(methods)}\n` +
|
|
168
|
+
`Terminal logins need an interactive terminal; re-run acp-gateway from one, ` +
|
|
169
|
+
`or pass --auth-method <id>.`);
|
|
170
|
+
}
|
|
171
|
+
await runAuthMethod(connection, method, launch);
|
|
172
|
+
return method;
|
|
173
|
+
}
|
|
174
|
+
/** Whether the agent said it implements `logout` during `initialize`. */
|
|
175
|
+
export function supportsLogout(agentCapabilities) {
|
|
176
|
+
const auth = agentCapabilities?.auth;
|
|
177
|
+
return auth?.logout !== undefined && auth?.logout !== null;
|
|
178
|
+
}
|
|
179
|
+
/** Ends the agent's authenticated state, if it implements logout. */
|
|
180
|
+
export async function logout(connection, agentCapabilities) {
|
|
181
|
+
if (!supportsLogout(agentCapabilities)) {
|
|
182
|
+
console.error("[auth] The agent does not support logout.");
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
await connection.logout({});
|
|
186
|
+
console.error("[auth] Logged out.");
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { readFileSync } from "node:fs";
|
|
8
8
|
import { resolve } from "node:path";
|
|
9
|
+
/** The MCP transports ACP defines that this gateway knows how to hand over. */
|
|
10
|
+
export const MCP_TRANSPORTS = ["http", "sse", "stdio"];
|
|
9
11
|
/**
|
|
10
12
|
* Find and parse the .mcp.json config file.
|
|
11
13
|
* Searches CWD and then up to 3 parent directories.
|
|
@@ -18,29 +20,61 @@ export function loadMcpConfig(startDir = process.cwd()) {
|
|
|
18
20
|
resolve(startDir, "..", "..", "..", ".mcp.json"),
|
|
19
21
|
];
|
|
20
22
|
for (const candidate of candidates) {
|
|
23
|
+
// Only reading and parsing may fall through to the next candidate. What the
|
|
24
|
+
// file *says* is a separate matter: once a .mcp.json has been found, a
|
|
25
|
+
// mistake inside it is reported rather than hidden behind "no config found".
|
|
26
|
+
let parsed;
|
|
21
27
|
try {
|
|
22
|
-
|
|
23
|
-
const parsed = JSON.parse(raw);
|
|
24
|
-
const servers = Object.entries(parsed.mcpServers ?? {}).map(([name, cfg]) => ({
|
|
25
|
-
name,
|
|
26
|
-
type: cfg.type ?? (cfg.url ? "http" : "stdio"),
|
|
27
|
-
url: cfg.url,
|
|
28
|
-
command: cfg.command,
|
|
29
|
-
args: cfg.args,
|
|
30
|
-
env: cfg.env,
|
|
31
|
-
headers: cfg.headers,
|
|
32
|
-
}));
|
|
33
|
-
if (servers.length > 0) {
|
|
34
|
-
console.error(`[config] Loaded .mcp.json from ${candidate}`);
|
|
35
|
-
return servers;
|
|
36
|
-
}
|
|
28
|
+
parsed = JSON.parse(readFileSync(candidate, "utf-8"));
|
|
37
29
|
}
|
|
38
30
|
catch {
|
|
39
|
-
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const servers = Object.entries(parsed.mcpServers ?? {}).map(([name, cfg]) => ({
|
|
34
|
+
name,
|
|
35
|
+
type: readTransport(name, cfg.type, cfg.url),
|
|
36
|
+
url: cfg.url,
|
|
37
|
+
command: cfg.command,
|
|
38
|
+
args: cfg.args,
|
|
39
|
+
env: cfg.env,
|
|
40
|
+
headers: cfg.headers,
|
|
41
|
+
}));
|
|
42
|
+
servers.forEach(assertUsable);
|
|
43
|
+
if (servers.length > 0) {
|
|
44
|
+
console.error(`[config] Loaded .mcp.json from ${candidate}`);
|
|
45
|
+
return servers;
|
|
40
46
|
}
|
|
41
47
|
}
|
|
42
48
|
throw new Error("Could not find .mcp.json — run acp-gateway from your workspace root");
|
|
43
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Reads an entry's transport, defaulting the way MCP clients conventionally do.
|
|
52
|
+
*
|
|
53
|
+
* An unknown transport is refused here rather than quietly treated as stdio,
|
|
54
|
+
* which produced a server with no command and an agent that could not say why.
|
|
55
|
+
*/
|
|
56
|
+
function readTransport(name, type, url) {
|
|
57
|
+
if (type === undefined)
|
|
58
|
+
return url ? "http" : "stdio";
|
|
59
|
+
if (MCP_TRANSPORTS.includes(type))
|
|
60
|
+
return type;
|
|
61
|
+
throw new Error(`MCP server "${name}" in .mcp.json has transport "${type}", which this gateway ` +
|
|
62
|
+
`cannot hand to an agent. Use one of: ${MCP_TRANSPORTS.join(", ")}.`);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Refuses an entry the agent could never connect to.
|
|
66
|
+
*
|
|
67
|
+
* The gateway passes these straight through to the agent, so an entry missing
|
|
68
|
+
* the one field its transport needs fails inside the agent, long after the
|
|
69
|
+
* mistake was made and with nothing to point at.
|
|
70
|
+
*/
|
|
71
|
+
function assertUsable(server) {
|
|
72
|
+
const missing = server.type === "stdio" ? !server.command : !server.url;
|
|
73
|
+
if (!missing)
|
|
74
|
+
return;
|
|
75
|
+
const field = server.type === "stdio" ? "command" : "url";
|
|
76
|
+
throw new Error(`MCP server "${server.name}" in .mcp.json is ${server.type} but has no ${field}.`);
|
|
77
|
+
}
|
|
44
78
|
/**
|
|
45
79
|
* Pick the primary agentrq MCP server from the list.
|
|
46
80
|
* Prefers servers with "agentrq" in the name; falls back to the first HTTP server.
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|