agent-mcp-hub 0.5.2

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.
@@ -0,0 +1,159 @@
1
+ import { accessSync, constants, statSync } from "node:fs";
2
+ import { delimiter, isAbsolute, join } from "node:path";
3
+ import { claudeAdapter } from "./adapters/claude.js";
4
+ import { codexAdapter } from "./adapters/codex.js";
5
+ import { cursorAdapter } from "./adapters/cursor.js";
6
+ import { opencodeAdapter } from "./adapters/opencode.js";
7
+ import { classifyFailure } from "./failure.js";
8
+ export function allAdapters() {
9
+ return [codexAdapter, cursorAdapter, opencodeAdapter, claudeAdapter];
10
+ }
11
+ /**
12
+ * Union of every adapter's credential env var, DERIVED from the adapter list so a
13
+ * new agent's key is covered automatically — there is no hand-maintained
14
+ * duplicate to forget (forgetting one silently leaked the new key to every other
15
+ * agent). This is the set of secrets that must be isolated between agents.
16
+ */
17
+ export function allCredentialEnvVars() {
18
+ return [
19
+ ...new Set(allAdapters()
20
+ .map((a) => a.apiKeyEnv)
21
+ .filter((k) => Boolean(k))),
22
+ ];
23
+ }
24
+ /**
25
+ * The credential keys to STRIP from `adapter`'s child environment: every OTHER
26
+ * agent's key. An adapter with its own `apiKeyEnv` keeps exactly that one. A
27
+ * provider-agnostic adapter (no `apiKeyEnv`, e.g. opencode) owns none, so ALL
28
+ * tracked keys are stripped — it must never inherit a sibling's secret. Applied
29
+ * to real runs AND to availability probes, so no spawn path leaks a key.
30
+ */
31
+ export function credentialStripKeys(adapter) {
32
+ return allCredentialEnvVars().filter((k) => k !== adapter.apiKeyEnv);
33
+ }
34
+ /**
35
+ * Selects which adapters to expose based on MCP_AGENTS (comma-separated,
36
+ * whitespace-tolerant, case-sensitive). Unset or empty-after-parse yields all
37
+ * adapters (never an empty server). Unknown names fail fast with an actionable
38
+ * message so typos are surfaced at wiring time rather than silently dropped.
39
+ */
40
+ export function enabledAdapters(agentsSpec = process.env.MCP_AGENTS) {
41
+ const requested = [
42
+ ...new Set((agentsSpec ?? "")
43
+ .split(",")
44
+ .map((name) => name.trim())
45
+ .filter(Boolean)),
46
+ ];
47
+ if (requested.length === 0)
48
+ return allAdapters();
49
+ const known = new Set(allAdapters().map((a) => a.name));
50
+ for (const name of requested) {
51
+ if (!known.has(name)) {
52
+ throw new Error(`Unknown agent "${name}" in MCP_AGENTS. Valid agents: ${allAdapters()
53
+ .map((a) => a.name)
54
+ .join(", ")}`);
55
+ }
56
+ }
57
+ const set = new Set(requested);
58
+ return allAdapters().filter((a) => set.has(a.name));
59
+ }
60
+ /**
61
+ * A probe may exit 0 and still be announcing that the CLI cannot function.
62
+ * These are read against combined stdout+stderr, not the exit code.
63
+ */
64
+ const FATAL_PROBE_SIGNATURES = [
65
+ /read-only file system/i,
66
+ /permission denied/i,
67
+ /\bEACCES\b/,
68
+ /\bEROFS\b/,
69
+ /\bENOSPC\b/,
70
+ ];
71
+ const PROBE_TIMEOUT_MS = 10_000;
72
+ function isExecutableFile(candidate) {
73
+ try {
74
+ if (!statSync(candidate).isFile())
75
+ return false;
76
+ accessSync(candidate, constants.X_OK);
77
+ return true;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ /**
84
+ * Resolves a binary the way spawn will: absolute paths as-is, otherwise the first
85
+ * executable match walking PATH. Returns undefined when nothing matches, which is
86
+ * the ONLY thing that may set `installed: false`.
87
+ */
88
+ export function resolveOnPath(binary, pathEnv = process.env.PATH) {
89
+ if (isAbsolute(binary))
90
+ return isExecutableFile(binary) ? binary : undefined;
91
+ for (const dir of (pathEnv ?? "").split(delimiter).filter(Boolean)) {
92
+ const candidate = join(dir, binary);
93
+ if (isExecutableFile(candidate))
94
+ return candidate;
95
+ }
96
+ return undefined;
97
+ }
98
+ /**
99
+ * Identifier-shaped lines only. A CLI can exit 0 while emitting a banner, an
100
+ * update notice, or "you are not logged in" prose — none of which are ids. Lines
101
+ * containing whitespace are prose; lines without it are candidate ids.
102
+ */
103
+ export function parseProbeIds(stdout) {
104
+ return stdout
105
+ .split("\n")
106
+ .map((line) => line.trim())
107
+ .filter((line) => line.length > 0 && !/\s/.test(line));
108
+ }
109
+ function firstFatalLine(output) {
110
+ for (const line of output.split("\n")) {
111
+ if (FATAL_PROBE_SIGNATURES.some((re) => re.test(line)))
112
+ return line.trim();
113
+ }
114
+ return undefined;
115
+ }
116
+ function result(name, installed, usable, reason) {
117
+ return reason === undefined
118
+ ? { name, installed, usable, available: usable }
119
+ : { name, installed, usable, available: usable, reason };
120
+ }
121
+ /**
122
+ * Probes one adapter. Never throws: a probe failure is data, not an exception.
123
+ * The probe command is per-adapter because the CLIs disagree — `opencode models`
124
+ * lists models, `codex models` is not a subcommand at all — so there is no single
125
+ * command that proves usability across all four.
126
+ */
127
+ export async function checkAvailability(adapter, exec, resolve = resolveOnPath) {
128
+ if (resolve(adapter.binary) === undefined) {
129
+ return result(adapter.name, false, false, `${adapter.binary} was not found on PATH. Fix: install the CLI and ensure it is on PATH.`);
130
+ }
131
+ const probeArgs = adapter.probeArgs ?? ["--version"];
132
+ let outcome;
133
+ try {
134
+ // Same credential isolation as a real run: a probe spawns the CLI, so it
135
+ // must not hand it a sibling agent's key either.
136
+ outcome = await exec(adapter.binary, probeArgs, {
137
+ timeoutMs: PROBE_TIMEOUT_MS,
138
+ stripEnvKeys: credentialStripKeys(adapter),
139
+ });
140
+ }
141
+ catch (error) {
142
+ // Installed (we just resolved it), but the probe could not complete.
143
+ return result(adapter.name, true, false, classifyFailure(adapter, { error }).message);
144
+ }
145
+ if (outcome.exitCode !== 0) {
146
+ return result(adapter.name, true, false, classifyFailure(adapter, { result: outcome }).message);
147
+ }
148
+ // Exit 0 is not a pass. Check what it actually said.
149
+ const fatal = firstFatalLine(`${outcome.stdout}\n${outcome.stderr}`);
150
+ if (fatal !== undefined) {
151
+ return result(adapter.name, true, false, `${adapter.name} exited 0 but reported a fatal condition: ${fatal}`);
152
+ }
153
+ if (adapter.probeRequiresOutput === true && parseProbeIds(outcome.stdout).length === 0) {
154
+ return result(adapter.name, true, false, `${adapter.name} probe (\`${adapter.binary} ${probeArgs.join(" ")}\`) exited 0 but listed nothing. ` +
155
+ `Fix: run \`${adapter.loginCommand}\`.`);
156
+ }
157
+ return result(adapter.name, true, true);
158
+ }
159
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type Exec } from "./exec.js";
3
+ import { type ResolveBinary } from "./registry.js";
4
+ import type { AgentAdapter } from "./types.js";
5
+ /**
6
+ * Model id, forwarded to the CLI as `--model <value>`. Validated with a positive
7
+ * allowlist (letters/digits then `. _ : / -`, ≤128 chars) so a flag-shaped value
8
+ * — anything starting with `-`, e.g. `--dir=/` or `--share` — is REJECTED. A CLI
9
+ * parses a hyphen-leading token in that position as another flag, not the model,
10
+ * which is argument injection even without a shell; the allowlist closes it and
11
+ * also bounds length. `o3`, `gpt-5.3-codex-high`, `openai/gpt-5`, `sonnet-4-thinking`
12
+ * all pass.
13
+ */
14
+ export declare const MODEL_RE: RegExp;
15
+ export declare function buildServer(adapters: AgentAdapter[], exec?: Exec, resolve?: ResolveBinary): McpServer;