@mmerterden/multi-agent-pipeline 20.8.1 → 20.8.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.
- package/CHANGELOG.md +36 -0
- package/docs/facts.json +3 -3
- package/index.js +1 -0
- package/install/_codex-agents.mjs +1 -1
- package/install/_common.mjs +486 -53
- package/install/_mcp-register.mjs +173 -117
- package/install/claude.mjs +281 -220
- package/install/codex.mjs +7 -7
- package/install/copilot.mjs +13 -11
- package/install/index.mjs +92 -27
- package/install/templates/claude-hooks.json +9 -9
- package/manifest.json +75 -78
- package/package.json +4 -1
- package/pipeline/commands/multi-agent/update/SKILL.md +28 -17
- package/pipeline/lib/confusables.json +79 -33
- package/pipeline/lib/extract-conventions.sh +3 -3
- package/pipeline/lib/json-file-lock.mjs +27 -7
- package/pipeline/lib/normalize-text.mjs +86 -17
- package/pipeline/lib/outbound-gate.mjs +13 -4
- package/pipeline/lib/redact.mjs +87 -14
- package/pipeline/multi-agent-refs/analysis/evidence.md +1 -1
- package/pipeline/multi-agent-refs/analysis/synthesis.md +1 -1
- package/pipeline/multi-agent-refs/component-dispatch.md +1 -1
- package/pipeline/multi-agent-refs/conventions-defaults.md +1 -1
- package/pipeline/multi-agent-refs/features/unattended-security.md +2 -2
- package/pipeline/scripts/agent-guard.py +150 -25
- package/pipeline/scripts/audit-log.sh +3 -4
- package/pipeline/scripts/autopilot-runner.mjs +14 -5
- package/pipeline/scripts/doctor.mjs +8 -2
- package/pipeline/scripts/log-metric.sh +9 -3
- package/pipeline/scripts/migrate-prefs.mjs +18 -4
- package/pipeline/scripts/pre-commit-check.sh +119 -31
- package/pipeline/scripts/scan-agent-config.sh +9 -9
- package/pipeline/scripts/unattended_policy.py +12 -3
- package/pipeline/scripts/uninstall.mjs +88 -1
- package/pipeline/scripts/usage-identity.mjs +1 -1
- package/pipeline/scripts/usage-register.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +11 -11
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +6 -3
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +43 -0
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +31 -0
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
- package/pipeline/skills/shared/external/ios-module-structure/modules/_TEMPLATE.yml +1 -1
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +2 -1
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +14 -13
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +7 -7
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +105 -4
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +20 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +8 -7
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +7 -8
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +20 -7
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +4 -3
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +8 -7
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +15 -8
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +17 -9
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +39 -17
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +28 -7
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +5 -4
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +3 -2
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +2 -2
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +4 -4
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +15 -6
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +2 -1
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -4
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +1 -1
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +25 -6
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/skill-creator/template.md +1 -1
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +3 -1
- package/pipeline/scripts/gen-ref-toc.mjs +0 -279
- package/pipeline/scripts/make-manifest.mjs +0 -199
- package/pipeline/scripts/scorecard-snapshot.mjs +0 -178
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Register the companion MCP server with a host CLI.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* neither did Claude Code. The result was a host that carried the full pipeline skill
|
|
8
|
-
* set and could not call a single one of the 83 toolkit tools those skills depend
|
|
9
|
-
* on - `design-check`, every `ios_*` / `android_*` simulator call, the archive audits.
|
|
10
|
-
* Nothing failed at install time, and the skills only fail when a run actually reaches
|
|
11
|
-
* for a tool.
|
|
12
|
-
*
|
|
13
|
-
* Claude Code was the longest-lived instance of that bug and the easiest to miss,
|
|
14
|
-
* because a maintainer who registered the server by hand once sees a working tree
|
|
15
|
-
* forever while every fresh install ships without it.
|
|
4
|
+
* Every host's skills call the toolkit's tools (`design-check`, every `ios_*` /
|
|
5
|
+
* `android_*` simulator call, the archive audits), and a missing server shows up
|
|
6
|
+
* only when a run reaches for one of them, so each installer registers it.
|
|
16
7
|
*
|
|
17
8
|
* All three CLIs take the same shape (`<cli> mcp add [scope] <name> -- <command>
|
|
18
|
-
* [args...]`), so
|
|
19
|
-
* implementation, three callers.
|
|
9
|
+
* [args...]`), so one implementation serves all three installers.
|
|
20
10
|
*
|
|
21
11
|
* @module install/_mcp-register
|
|
22
12
|
*/
|
|
@@ -32,18 +22,14 @@ export const MCP_SERVER_NAME = "multi-agent-toolkit";
|
|
|
32
22
|
export const MCP_SERVER_PACKAGE = "@mmerterden/multi-agent-toolkit-mcp";
|
|
33
23
|
|
|
34
24
|
/**
|
|
35
|
-
* The spec the host is registered with. `@latest`
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* npx found ANY cached version and reused it, and a machine that had cached
|
|
40
|
-
* 3.0.0 kept starting 3.0.0 after 3.1.0 was published and tagged `latest`. The
|
|
41
|
-
* new tool existed on the registry and in the docs, and in no running server.
|
|
25
|
+
* The spec the host is registered with. `@latest` makes each server start
|
|
26
|
+
* resolve the newest published toolkit: with a bare package name, npx reuses
|
|
27
|
+
* any cached version, so a machine keeps starting whatever it cached first
|
|
28
|
+
* after newer releases are published.
|
|
42
29
|
*
|
|
43
30
|
* The cost is a registry round-trip when the MCP server starts, and an offline
|
|
44
|
-
* start
|
|
45
|
-
*
|
|
46
|
-
* server is a silent wrong answer, a failed start is a loud one.
|
|
31
|
+
* start depends on what npx can resolve from its cache. A stale server is a
|
|
32
|
+
* silent wrong answer, a failed start is a loud one.
|
|
47
33
|
*/
|
|
48
34
|
export const MCP_SERVER_SPEC = `${MCP_SERVER_PACKAGE}@latest`;
|
|
49
35
|
|
|
@@ -87,7 +73,8 @@ export function scopeRegistryFlag(pkg = MCP_SERVER_PACKAGE, registry = MCP_REGIS
|
|
|
87
73
|
export const LEGACY_MCP_SERVER_NAME = "dev-toolkit";
|
|
88
74
|
|
|
89
75
|
/**
|
|
90
|
-
* Per-host CLI invocation details
|
|
76
|
+
* Per-host CLI invocation details, built at call time so a caller that changes
|
|
77
|
+
* HOME (a test, a sandboxed install) gets candidates under that HOME.
|
|
91
78
|
*
|
|
92
79
|
* `candidates`: where the CLI might live. A bare name is resolved through PATH by
|
|
93
80
|
* execFileSync. The explicit paths matter for Copilot: `copilot` is frequently a
|
|
@@ -97,26 +84,32 @@ export const LEGACY_MCP_SERVER_NAME = "dev-toolkit";
|
|
|
97
84
|
* ~/.local/share/gh/copilot/.
|
|
98
85
|
*
|
|
99
86
|
* `scopeArgs`: options inserted before the server name. Only Claude Code needs
|
|
100
|
-
* them
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
87
|
+
* them: `claude mcp add` defaults to `--scope local`, which binds the server to
|
|
88
|
+
* whichever directory the installer happened to run in. Codex writes
|
|
89
|
+
* `~/.codex/config.toml` and Copilot writes `~/.copilot/mcp-config.json`, both
|
|
90
|
+
* already user-global, so both take no scope flag - passing one errors.
|
|
91
|
+
*
|
|
92
|
+
* `getArgs`: extra options for `mcp get` that make it print JSON.
|
|
93
|
+
*
|
|
94
|
+
* @param {"claude"|"codex"|"copilot"} host
|
|
105
95
|
*/
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
96
|
+
function hostConfig(host) {
|
|
97
|
+
const home = homedir();
|
|
98
|
+
const hosts = {
|
|
99
|
+
claude: {
|
|
100
|
+
candidates: ["claude", join(home, ".local", "bin", "claude")],
|
|
101
|
+
scopeArgs: ["--scope", "user"],
|
|
102
|
+
getArgs: [],
|
|
103
|
+
},
|
|
104
|
+
codex: { candidates: ["codex"], scopeArgs: [], getArgs: ["--json"] },
|
|
105
|
+
copilot: {
|
|
106
|
+
candidates: ["copilot", join(home, ".local", "share", "gh", "copilot", "copilot")],
|
|
107
|
+
scopeArgs: [],
|
|
108
|
+
getArgs: ["--json"],
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
return hosts[host] || { candidates: [host], scopeArgs: [], getArgs: [] };
|
|
112
|
+
}
|
|
120
113
|
|
|
121
114
|
/**
|
|
122
115
|
* First candidate that exists as a file, or the first bare name to let PATH resolution
|
|
@@ -127,7 +120,7 @@ const HOSTS = Object.freeze({
|
|
|
127
120
|
* @returns {string}
|
|
128
121
|
*/
|
|
129
122
|
function resolveCli(host) {
|
|
130
|
-
const candidates =
|
|
123
|
+
const { candidates } = hostConfig(host);
|
|
131
124
|
for (const c of candidates) {
|
|
132
125
|
if (c.includes("/") && existsSync(c)) return c;
|
|
133
126
|
}
|
|
@@ -139,37 +132,76 @@ function saysAlreadyExists(text) {
|
|
|
139
132
|
return /already exists/i.test(text);
|
|
140
133
|
}
|
|
141
134
|
|
|
135
|
+
const outputOf = (e) =>
|
|
136
|
+
`${e?.stderr?.toString() ?? ""}\n${e?.stdout?.toString() ?? ""}\n${e?.message ?? ""}`;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The command line a registered server starts, as `[command, ...args]`, read
|
|
140
|
+
* from `<cli> mcp get`. Accepts the JSON form (Codex, Copilot: an object
|
|
141
|
+
* carrying `command` and `args`, possibly nested) and the text form (Claude
|
|
142
|
+
* Code: `Command:` and `Args:` lines; Copilot's text puts both on `Command:`).
|
|
143
|
+
*
|
|
144
|
+
* @param {string} text
|
|
145
|
+
* @returns {string[]|null}
|
|
146
|
+
*/
|
|
147
|
+
export function parseRegisteredSpec(text) {
|
|
148
|
+
const raw = String(text || "").trim();
|
|
149
|
+
if (!raw) return null;
|
|
150
|
+
try {
|
|
151
|
+
const find = (node) => {
|
|
152
|
+
if (!node || typeof node !== "object") return null;
|
|
153
|
+
if (typeof node.command === "string") {
|
|
154
|
+
return [node.command, ...(Array.isArray(node.args) ? node.args.map(String) : [])];
|
|
155
|
+
}
|
|
156
|
+
for (const v of Object.values(node)) {
|
|
157
|
+
const hit = find(v);
|
|
158
|
+
if (hit) return hit;
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
};
|
|
162
|
+
const hit = find(JSON.parse(raw));
|
|
163
|
+
if (hit) return hit;
|
|
164
|
+
} catch {
|
|
165
|
+
/* not JSON: read the text form */
|
|
166
|
+
}
|
|
167
|
+
const command = /^\s*Command:\s*(.+)$/m.exec(raw)?.[1]?.trim();
|
|
168
|
+
if (!command) return null;
|
|
169
|
+
const args = /^\s*Args:\s*(.*)$/m.exec(raw)?.[1]?.trim();
|
|
170
|
+
const words = (s) => s.split(/\s+/).filter(Boolean);
|
|
171
|
+
return args === undefined ? words(command) : [...words(command), ...words(args)];
|
|
172
|
+
}
|
|
173
|
+
|
|
142
174
|
/**
|
|
143
|
-
* Register the MCP server
|
|
144
|
-
*
|
|
175
|
+
* Register the MCP server, keeping whatever registration exists until a
|
|
176
|
+
* replacement is in place.
|
|
177
|
+
*
|
|
178
|
+
* The add runs first. The three CLIs answer a duplicate differently:
|
|
145
179
|
*
|
|
146
180
|
* - `codex mcp add` replaces an existing entry and exits 0.
|
|
147
181
|
* - `copilot mcp add` refuses, printing `Server "<name>" already exists` and exiting 1.
|
|
148
182
|
* - `claude mcp add` refuses, printing `MCP server <name> already exists in <scope>
|
|
149
183
|
* config` and exiting **0**.
|
|
150
184
|
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* An existing entry under the same name is removed and re-added so its spec follows
|
|
158
|
-
* the installed version (the entry is installer-owned; the reason is at the remove
|
|
159
|
-
* below). A user who points the toolkit at a local checkout registers it under a
|
|
160
|
-
* different name or re-applies the change after an update.
|
|
185
|
+
* so "already exists" is looked for on both the success and the failure path.
|
|
186
|
+
* On a duplicate, the registered spec is read back with `mcp get` and compared
|
|
187
|
+
* with the one this release wants. The same spec is left alone. A different
|
|
188
|
+
* spec is replaced (remove, then add); if that add fails, the previous spec is
|
|
189
|
+
* added back so the host never ends up with no server. A spec that cannot be
|
|
190
|
+
* read back is not replaced, because it could not be restored.
|
|
161
191
|
*
|
|
162
192
|
* Never fatal: a missing CLI or a failed write must not fail the install, because the
|
|
163
193
|
* rest of the tree is still correct and useful. It prints the manual command instead.
|
|
164
194
|
*
|
|
165
195
|
* @param {"claude"|"codex"|"copilot"} host
|
|
166
196
|
* @param {string} label - host label for log lines
|
|
167
|
-
* @
|
|
197
|
+
* @param {{cli?: string}} [opts] - `cli` overrides the CLI lookup (tests)
|
|
198
|
+
* @returns {{registered: boolean, alreadyPresent?: boolean, staleSpec?: boolean, replaced?: boolean, reason?: string}}
|
|
168
199
|
*/
|
|
169
|
-
export function registerMcpServer(host, label) {
|
|
170
|
-
const cli = resolveCli(host);
|
|
171
|
-
const scopeArgs =
|
|
200
|
+
export function registerMcpServer(host, label, { cli: cliOverride } = {}) {
|
|
201
|
+
const cli = cliOverride || resolveCli(host);
|
|
202
|
+
const { scopeArgs, getArgs } = hostConfig(host);
|
|
172
203
|
const npxArgs = ["-y", ...scopeRegistryFlag(), MCP_SERVER_SPEC];
|
|
204
|
+
const desired = ["npx", ...npxArgs];
|
|
173
205
|
const manual = `${host} mcp add ${scopeArgs.join(" ")}${scopeArgs.length ? " " : ""}${MCP_SERVER_NAME} -- npx ${npxArgs.join(" ")}`;
|
|
174
206
|
|
|
175
207
|
if (isDryRun()) {
|
|
@@ -186,75 +218,99 @@ export function registerMcpServer(host, label) {
|
|
|
186
218
|
return { registered: false, reason: "opted out" };
|
|
187
219
|
}
|
|
188
220
|
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
|
|
221
|
+
// Bounded: an installer must never hang on a child process. These are local
|
|
222
|
+
// config reads and writes and return in milliseconds.
|
|
223
|
+
const run = (args) => execFileSync(cli, args, { stdio: "pipe", timeout: 20_000 });
|
|
224
|
+
const add = (spec) => {
|
|
225
|
+
try {
|
|
226
|
+
const stdout = run(["mcp", "add", ...scopeArgs, MCP_SERVER_NAME, "--", ...spec]);
|
|
227
|
+
// Claude Code refuses a duplicate on the success path (exit 0).
|
|
228
|
+
return saysAlreadyExists(stdout?.toString() ?? "") ? "exists" : "added";
|
|
229
|
+
} catch (e) {
|
|
230
|
+
const out = outputOf(e);
|
|
231
|
+
if (saysAlreadyExists(out)) return "exists";
|
|
232
|
+
const why =
|
|
233
|
+
e?.code === "ENOENT"
|
|
234
|
+
? `${host} not on PATH`
|
|
235
|
+
: (out.split("\n").find((l) => l.trim()) || "unknown error").trim();
|
|
236
|
+
return { failed: why };
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// The pre-rename entry (LEGACY_MCP_SERVER_NAME) would advertise the same tools
|
|
241
|
+
// under a second name. Best-effort: "not registered" is the common case.
|
|
192
242
|
try {
|
|
193
|
-
|
|
194
|
-
stdio: "pipe",
|
|
195
|
-
timeout: 20_000,
|
|
196
|
-
});
|
|
243
|
+
run(["mcp", "remove", ...scopeArgs, LEGACY_MCP_SERVER_NAME]);
|
|
197
244
|
console.log(` -> ${label}: removed the legacy ${LEGACY_MCP_SERVER_NAME} registration`);
|
|
198
245
|
} catch {
|
|
199
246
|
/* not present, or the host declines - either way the add below is what matters */
|
|
200
247
|
}
|
|
201
248
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
249
|
+
const first = add(desired);
|
|
250
|
+
if (first === "added") {
|
|
251
|
+
console.log(
|
|
252
|
+
` -> ${label}: registered the ${MCP_SERVER_NAME} MCP server (${MCP_SERVER_SPEC})`,
|
|
253
|
+
);
|
|
254
|
+
return { registered: true };
|
|
255
|
+
}
|
|
256
|
+
if (first !== "exists") {
|
|
257
|
+
console.log(` -> ${label}: skipped MCP registration (${first.failed})`);
|
|
258
|
+
console.log(` run manually: ${manual}`);
|
|
259
|
+
return { registered: false, reason: first.failed };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
let previous;
|
|
210
263
|
try {
|
|
211
|
-
|
|
212
|
-
stdio: "pipe",
|
|
213
|
-
timeout: 20_000,
|
|
214
|
-
});
|
|
215
|
-
replacedExisting = true;
|
|
264
|
+
previous = parseRegisteredSpec(run(["mcp", "get", MCP_SERVER_NAME, ...getArgs])?.toString());
|
|
216
265
|
} catch {
|
|
217
|
-
|
|
266
|
+
previous = null;
|
|
267
|
+
}
|
|
268
|
+
if (previous && previous.join("\u0000") === desired.join("\u0000")) {
|
|
269
|
+
console.log(
|
|
270
|
+
` -> ${label}: ${MCP_SERVER_NAME} MCP server already registered (${MCP_SERVER_SPEC})`,
|
|
271
|
+
);
|
|
272
|
+
return { registered: true, alreadyPresent: true };
|
|
273
|
+
}
|
|
274
|
+
if (!previous) {
|
|
275
|
+
console.log(
|
|
276
|
+
` -> ${label}: ${MCP_SERVER_NAME} already registered; its spec could not be read, so it was ` +
|
|
277
|
+
`left as-is. To make sure it runs ${MCP_SERVER_SPEC}, re-register with: ${manual}`,
|
|
278
|
+
);
|
|
279
|
+
return { registered: true, alreadyPresent: true, staleSpec: true };
|
|
218
280
|
}
|
|
219
281
|
|
|
220
282
|
try {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
283
|
+
run(["mcp", "remove", ...scopeArgs, MCP_SERVER_NAME]);
|
|
284
|
+
} catch (e) {
|
|
285
|
+
console.log(
|
|
286
|
+
` -> ${label}: ${MCP_SERVER_NAME} is registered with an older spec and the host declined ` +
|
|
287
|
+
`to remove it (${(
|
|
288
|
+
outputOf(e)
|
|
289
|
+
.split("\n")
|
|
290
|
+
.find((l) => l.trim()) || "unknown error"
|
|
291
|
+
).trim()}). ` +
|
|
292
|
+
`Re-register manually with: ${manual}`,
|
|
227
293
|
);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// its spec is whatever was there before, so this is not a current
|
|
233
|
-
// registration - say that instead of reporting a clean pass.
|
|
234
|
-
console.log(
|
|
235
|
-
` -> ${label}: ${MCP_SERVER_NAME} already registered and the host declined to replace it; ` +
|
|
236
|
-
`its spec may predate ${MCP_SERVER_SPEC}. Re-register manually with: ${manual}`,
|
|
237
|
-
);
|
|
238
|
-
return { registered: true, alreadyPresent: true, staleSpec: true };
|
|
239
|
-
}
|
|
294
|
+
return { registered: true, alreadyPresent: true, staleSpec: true };
|
|
295
|
+
}
|
|
296
|
+
const replaced = add(desired);
|
|
297
|
+
if (replaced === "added" || replaced === "exists") {
|
|
240
298
|
console.log(
|
|
241
|
-
` -> ${label}:
|
|
299
|
+
` -> ${label}: re-registered the ${MCP_SERVER_NAME} MCP server (${MCP_SERVER_SPEC})`,
|
|
242
300
|
);
|
|
243
|
-
return { registered: true };
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
252
|
-
const why =
|
|
253
|
-
e?.code === "ENOENT"
|
|
254
|
-
? `${host} not on PATH`
|
|
255
|
-
: (out.split("\n").find((l) => l.trim()) || "unknown error").trim();
|
|
256
|
-
console.log(` -> ${label}: skipped MCP registration (${why})`);
|
|
257
|
-
console.log(` run manually: ${manual}`);
|
|
258
|
-
return { registered: false, reason: why };
|
|
301
|
+
return { registered: true, replaced: true };
|
|
302
|
+
}
|
|
303
|
+
const restored = add(previous);
|
|
304
|
+
if (restored === "added" || restored === "exists") {
|
|
305
|
+
console.log(
|
|
306
|
+
` -> ${label}: could not update ${MCP_SERVER_NAME} (${replaced.failed}); the previous ` +
|
|
307
|
+
`registration was restored. Re-register manually with: ${manual}`,
|
|
308
|
+
);
|
|
309
|
+
return { registered: true, alreadyPresent: true, staleSpec: true, reason: replaced.failed };
|
|
259
310
|
}
|
|
311
|
+
console.log(
|
|
312
|
+
` -> ${label}: could not update ${MCP_SERVER_NAME} (${replaced.failed}) or restore the previous ` +
|
|
313
|
+
`registration (${restored.failed}). Register it manually with: ${manual}`,
|
|
314
|
+
);
|
|
315
|
+
return { registered: false, reason: replaced.failed };
|
|
260
316
|
}
|