@heretyc/subagent-mcp 2.6.1 → 2.6.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/dist/doctor.js +6 -6
- package/dist/setup.js +118 -36
- package/package.json +1 -1
package/dist/doctor.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// `subagent-mcp doctor` — read-only health check for the installed addon.
|
|
3
3
|
//
|
|
4
|
-
// Diagnoses
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// Diagnoses install completeness, vendor presence, and whether each vendor's
|
|
5
|
+
// wiring (MCP server + hooks) points at THIS install. Doctor self-repairs
|
|
6
|
+
// missing MCP registrations via vendor CLIs; use `subagent-mcp setup` for
|
|
7
|
+
// config-file and hook repairs.
|
|
8
8
|
//
|
|
9
9
|
// Exit code: 0 = everything healthy, 1 = at least one check failed.
|
|
10
10
|
import { verifyWiring } from "./setup.js";
|
|
11
11
|
export async function runDoctor() {
|
|
12
|
-
console.log("subagent-mcp doctor (
|
|
12
|
+
console.log("subagent-mcp doctor (checks wiring; repairs missing MCP registrations via vendor CLIs)\n");
|
|
13
13
|
const major = Number(process.versions.node.split(".")[0]);
|
|
14
14
|
console.log(` ${major >= 18 ? "PASS" : "FAIL"} node version — ${process.versions.node}` +
|
|
15
15
|
(major >= 18 ? "" : " (Node >= 18 required)"));
|
|
16
16
|
let failed = major < 18 ? 1 : 0;
|
|
17
|
-
for (const r of verifyWiring()) {
|
|
17
|
+
for (const r of verifyWiring(undefined, true)) {
|
|
18
18
|
console.log(` ${r.ok ? "PASS" : "FAIL"} ${r.label} — ${r.detail}`);
|
|
19
19
|
if (!r.ok)
|
|
20
20
|
failed++;
|
package/dist/setup.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
// - Every config file is backed up before its first edit.
|
|
17
17
|
// - Failures never abort the run: they are collected and reported at the end
|
|
18
18
|
// with a copy-paste repair prompt the user can hand to Claude/Codex.
|
|
19
|
-
import { existsSync, readFileSync, writeFileSync, copyFileSync, } from "node:fs";
|
|
19
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync, } from "node:fs";
|
|
20
20
|
import { homedir } from "node:os";
|
|
21
21
|
import { join, dirname, resolve } from "node:path";
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
@@ -104,17 +104,14 @@ export function reconcileClaudeJson(cj, serverPath) {
|
|
|
104
104
|
const cur = servers["subagent-mcp"];
|
|
105
105
|
if (cur) {
|
|
106
106
|
const args = cur.args;
|
|
107
|
-
const exact = cur.command === "
|
|
108
|
-
Array.isArray(args) &&
|
|
109
|
-
args.length === 1 &&
|
|
110
|
-
args[0] === serverPath;
|
|
107
|
+
const exact = cur.command === "subagent-mcp" && Array.isArray(args) && args.length === 0;
|
|
111
108
|
if (exact)
|
|
112
109
|
return { changed: false, status: "ok" };
|
|
113
110
|
}
|
|
114
111
|
servers["subagent-mcp"] = {
|
|
115
112
|
type: "stdio",
|
|
116
|
-
command: "
|
|
117
|
-
args: [
|
|
113
|
+
command: "subagent-mcp",
|
|
114
|
+
args: [],
|
|
118
115
|
env: {},
|
|
119
116
|
};
|
|
120
117
|
return { changed: true, status: cur ? "repaired" : "added" };
|
|
@@ -144,7 +141,7 @@ export function reconcileCodexToml(toml, serverPath) {
|
|
|
144
141
|
status: "added",
|
|
145
142
|
};
|
|
146
143
|
}
|
|
147
|
-
if (m[0].includes(`args = ["${serverPath}"]`)) {
|
|
144
|
+
if (m[0].includes(`command = "node"`) && m[0].includes(`args = ["${serverPath}"]`)) {
|
|
148
145
|
return { toml, changed: false, status: "ok" };
|
|
149
146
|
}
|
|
150
147
|
return {
|
|
@@ -242,26 +239,60 @@ function backup(file) {
|
|
|
242
239
|
}
|
|
243
240
|
}
|
|
244
241
|
function runCmd(cmd, cmdArgs) {
|
|
242
|
+
return runCmdCapture(cmd, cmdArgs).ok;
|
|
243
|
+
}
|
|
244
|
+
function quoteWinShellArg(arg) {
|
|
245
|
+
if (!/[ \t"]/.test(arg))
|
|
246
|
+
return arg;
|
|
247
|
+
return `"${arg.replace(/"/g, '\\"')}"`;
|
|
248
|
+
}
|
|
249
|
+
function quoteWinShellExe(exe) {
|
|
250
|
+
return `"${exe.replace(/"/g, '\\"')}"`;
|
|
251
|
+
}
|
|
252
|
+
function runCmdCapture(cmd, cmdArgs) {
|
|
245
253
|
console.log(` $ ${cmd} ${cmdArgs.join(" ")}`);
|
|
246
254
|
if (DRY_RUN) {
|
|
247
255
|
console.log(" (dry-run: skipped)");
|
|
248
|
-
return true;
|
|
256
|
+
return { ok: true, stdout: "" };
|
|
249
257
|
}
|
|
250
258
|
try {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
execSync(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
execFileSync(cmd, cmdArgs, { stdio: "pipe" });
|
|
258
|
-
}
|
|
259
|
-
return true;
|
|
259
|
+
const exe = findOnPath(cmd) ?? cmd;
|
|
260
|
+
const isWinCmdShim = process.platform === "win32" && /\.(?:cmd|bat)$/i.test(exe);
|
|
261
|
+
const stdout = isWinCmdShim
|
|
262
|
+
? execSync([quoteWinShellExe(exe), ...cmdArgs.map(quoteWinShellArg)].join(" "), { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] })
|
|
263
|
+
: execFileSync(exe, cmdArgs, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
264
|
+
return { ok: true, stdout };
|
|
260
265
|
}
|
|
261
266
|
catch {
|
|
262
|
-
return false;
|
|
267
|
+
return { ok: false, stdout: "" };
|
|
263
268
|
}
|
|
264
269
|
}
|
|
270
|
+
export function claudeAddArgs() {
|
|
271
|
+
return ["mcp", "add", "subagent-mcp", "subagent-mcp", "-s", "user"];
|
|
272
|
+
}
|
|
273
|
+
export function claudeRemoveArgs() {
|
|
274
|
+
return ["mcp", "remove", "subagent-mcp", "-s", "user"];
|
|
275
|
+
}
|
|
276
|
+
export function codexAddArgs(serverPath) {
|
|
277
|
+
return ["mcp", "add", "subagent-mcp", "--", "node", serverPath];
|
|
278
|
+
}
|
|
279
|
+
export function codexRemoveArgs() {
|
|
280
|
+
return ["mcp", "remove", "subagent-mcp"];
|
|
281
|
+
}
|
|
282
|
+
function claudeRegisteredViaCli() {
|
|
283
|
+
const get = runCmdCapture("claude", ["mcp", "get", "subagent-mcp"]);
|
|
284
|
+
if (get.ok && get.stdout.includes("subagent-mcp"))
|
|
285
|
+
return true;
|
|
286
|
+
const list = runCmdCapture("claude", ["mcp", "list"]);
|
|
287
|
+
return list.ok && list.stdout.includes("subagent-mcp");
|
|
288
|
+
}
|
|
289
|
+
function codexRegisteredViaCli() {
|
|
290
|
+
const get = runCmdCapture("codex", ["mcp", "get", "subagent-mcp"]);
|
|
291
|
+
if (get.ok && get.stdout.includes("subagent-mcp"))
|
|
292
|
+
return true;
|
|
293
|
+
const list = runCmdCapture("codex", ["mcp", "list"]);
|
|
294
|
+
return list.ok && list.stdout.includes("subagent-mcp");
|
|
295
|
+
}
|
|
265
296
|
const issues = [];
|
|
266
297
|
function repairPromptFor(vendor, problem) {
|
|
267
298
|
const p = serverPaths();
|
|
@@ -269,7 +300,7 @@ function repairPromptFor(vendor, problem) {
|
|
|
269
300
|
return (`subagent-mcp setup hit a problem on my machine: ${problem}. ` +
|
|
270
301
|
`The install root is "${fwd(INSTALL_ROOT)}". Please repair my Claude Code wiring: ` +
|
|
271
302
|
`(1) register a user-scope MCP server named "subagent-mcp" running ` +
|
|
272
|
-
`
|
|
303
|
+
`the global bin shim "subagent-mcp" (use 'claude mcp add subagent-mcp subagent-mcp -s user' or edit the mcpServers ` +
|
|
273
304
|
`key in ~/.claude.json), and (2) ensure ~/.claude/settings.json has a ` +
|
|
274
305
|
`hooks.UserPromptSubmit entry {type:"command", command:"node", args:["${p.claudeHook}"]}. ` +
|
|
275
306
|
`Back up any file before editing it.`);
|
|
@@ -308,20 +339,27 @@ function wireClaude() {
|
|
|
308
339
|
const probe = JSON.parse(JSON.stringify(cj));
|
|
309
340
|
const { status } = reconcileClaudeJson(probe, p.server);
|
|
310
341
|
if (status === "ok") {
|
|
311
|
-
|
|
342
|
+
let registered = claudeRegisteredViaCli();
|
|
343
|
+
if (!registered) {
|
|
344
|
+
runCmd("claude", claudeAddArgs());
|
|
345
|
+
registered = claudeRegisteredViaCli();
|
|
346
|
+
}
|
|
347
|
+
if (registered)
|
|
348
|
+
describe("ok", "MCP server (user scope)");
|
|
349
|
+
else
|
|
350
|
+
fail("claude", "MCP server file shape is correct, but 'claude mcp add' failed to register it with the CLI");
|
|
312
351
|
}
|
|
313
352
|
else {
|
|
314
353
|
if (status === "repaired") {
|
|
315
354
|
console.log(" MCP server registration points at a stale path — re-registering.");
|
|
316
|
-
runCmd("claude",
|
|
355
|
+
runCmd("claude", claudeRemoveArgs());
|
|
317
356
|
}
|
|
318
|
-
const cliOk = runCmd("claude",
|
|
319
|
-
|
|
320
|
-
]);
|
|
357
|
+
const cliOk = runCmd("claude", claudeAddArgs());
|
|
358
|
+
const cliVerified = cliOk && claudeRegisteredViaCli();
|
|
321
359
|
// Read back; if the CLI failed or didn't take, write the entry directly.
|
|
322
360
|
const after = readJson(cjFile, {});
|
|
323
361
|
const verify = reconcileClaudeJson(after, p.server);
|
|
324
|
-
if (verify.status !== "ok" && !DRY_RUN) {
|
|
362
|
+
if (!cliVerified && verify.status !== "ok" && !DRY_RUN) {
|
|
325
363
|
if (!cliOk)
|
|
326
364
|
console.log(" 'claude mcp add' failed — writing ~/.claude.json directly.");
|
|
327
365
|
backup(cjFile);
|
|
@@ -359,7 +397,14 @@ function wireCodex() {
|
|
|
359
397
|
const cfg = join(codexDir, "config.toml");
|
|
360
398
|
const toml = existsSync(cfg) ? readFileSync(cfg, "utf8") : "";
|
|
361
399
|
const r = reconcileCodexToml(toml, p.server);
|
|
362
|
-
if (r.
|
|
400
|
+
if (r.status === "repaired") {
|
|
401
|
+
console.log(" MCP server registration points at a stale path — re-registering.");
|
|
402
|
+
runCmd("codex", codexRemoveArgs());
|
|
403
|
+
}
|
|
404
|
+
const cliOk = r.status === "ok" && codexRegisteredViaCli() ? true : runCmd("codex", codexAddArgs(p.server));
|
|
405
|
+
if (!cliOk && r.changed && !DRY_RUN) {
|
|
406
|
+
console.log(" 'codex mcp add' failed — writing ~/.codex/config.toml directly.");
|
|
407
|
+
mkdirSync(codexDir, { recursive: true });
|
|
363
408
|
backup(cfg);
|
|
364
409
|
writeFileSync(cfg, r.toml);
|
|
365
410
|
}
|
|
@@ -377,6 +422,7 @@ function wireCodex() {
|
|
|
377
422
|
const hookCmd = `node "${p.codexHook}"`;
|
|
378
423
|
const { changed, statuses } = reconcileCodexHooks(h, hookCmd);
|
|
379
424
|
if (changed && !DRY_RUN) {
|
|
425
|
+
mkdirSync(codexDir, { recursive: true });
|
|
380
426
|
backup(hfile);
|
|
381
427
|
writeFileSync(hfile, JSON.stringify(h, null, 2));
|
|
382
428
|
}
|
|
@@ -392,7 +438,7 @@ function wireCodex() {
|
|
|
392
438
|
fail("codex", `could not write hooks.json: ${e.message}`);
|
|
393
439
|
}
|
|
394
440
|
}
|
|
395
|
-
export function verifyWiring(root = INSTALL_ROOT) {
|
|
441
|
+
export function verifyWiring(root = INSTALL_ROOT, repair = false) {
|
|
396
442
|
const p = serverPaths(root);
|
|
397
443
|
const results = [];
|
|
398
444
|
const home = homedir();
|
|
@@ -403,7 +449,29 @@ export function verifyWiring(root = INSTALL_ROOT) {
|
|
|
403
449
|
detail: missing.length === 0 ? `all present under ${fwd(root)}` : `missing: ${missing.join(", ")}`,
|
|
404
450
|
});
|
|
405
451
|
const hasClaude = findOnPath("claude") !== null;
|
|
452
|
+
const hasClaudeConfig = existsSync(join(home, ".claude.json"));
|
|
406
453
|
if (hasClaude) {
|
|
454
|
+
const sj = readJson(join(home, ".claude", "settings.json"), {});
|
|
455
|
+
let registered = claudeRegisteredViaCli();
|
|
456
|
+
let repaired = false;
|
|
457
|
+
if (!registered && repair) {
|
|
458
|
+
runCmd("claude", claudeAddArgs());
|
|
459
|
+
repaired = true;
|
|
460
|
+
registered = claudeRegisteredViaCli();
|
|
461
|
+
}
|
|
462
|
+
const hk = reconcileClaudeSettings(JSON.parse(JSON.stringify(sj)), p.claudeHook);
|
|
463
|
+
results.push({
|
|
464
|
+
label: "claude: MCP server (user scope)",
|
|
465
|
+
ok: registered,
|
|
466
|
+
detail: registered ? (repaired ? "repaired" : "registered") : "not registered; CLI repair failed",
|
|
467
|
+
});
|
|
468
|
+
results.push({
|
|
469
|
+
label: "claude: UserPromptSubmit hook",
|
|
470
|
+
ok: hk.status === "ok",
|
|
471
|
+
detail: hk.status === "ok" ? "wired" : `${hk.status === "repaired" ? "stale path" : "not wired"} - run: subagent-mcp setup`,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
else if (hasClaudeConfig) {
|
|
407
475
|
const cj = readJson(join(home, ".claude.json"), {});
|
|
408
476
|
const sj = readJson(join(home, ".claude", "settings.json"), {});
|
|
409
477
|
const srv = reconcileClaudeJson(JSON.parse(JSON.stringify(cj)), p.server);
|
|
@@ -411,34 +479,48 @@ export function verifyWiring(root = INSTALL_ROOT) {
|
|
|
411
479
|
results.push({
|
|
412
480
|
label: "claude: MCP server (user scope)",
|
|
413
481
|
ok: srv.status === "ok",
|
|
414
|
-
detail: srv.status === "ok" ? "registered
|
|
482
|
+
detail: srv.status === "ok" ? "registered (file fallback)" : "not registered; claude CLI not on PATH",
|
|
415
483
|
});
|
|
416
484
|
results.push({
|
|
417
485
|
label: "claude: UserPromptSubmit hook",
|
|
418
486
|
ok: hk.status === "ok",
|
|
419
|
-
detail: hk.status === "ok" ? "wired" : `${hk.status === "repaired" ? "stale path" : "not wired"}
|
|
487
|
+
detail: hk.status === "ok" ? "wired" : `${hk.status === "repaired" ? "stale path" : "not wired"} - run: subagent-mcp setup`,
|
|
420
488
|
});
|
|
421
489
|
}
|
|
422
|
-
const
|
|
490
|
+
const hasCodexCli = findOnPath("codex") !== null;
|
|
491
|
+
const hasCodex = hasCodexCli || existsSync(join(home, ".codex"));
|
|
423
492
|
if (hasCodex) {
|
|
424
493
|
const cfg = join(home, ".codex", "config.toml");
|
|
425
494
|
const toml = existsSync(cfg) ? readFileSync(cfg, "utf8") : "";
|
|
426
495
|
const tomlR = reconcileCodexToml(toml, p.server);
|
|
427
496
|
const hj = readJson(join(home, ".codex", "hooks.json"), { hooks: {} });
|
|
428
497
|
const hkR = reconcileCodexHooks(hj, `node "${p.codexHook}"`);
|
|
498
|
+
let registered = false;
|
|
499
|
+
let repaired = false;
|
|
500
|
+
if (hasCodexCli) {
|
|
501
|
+
registered = codexRegisteredViaCli();
|
|
502
|
+
if (!registered && repair) {
|
|
503
|
+
runCmd("codex", codexAddArgs(p.server));
|
|
504
|
+
repaired = true;
|
|
505
|
+
registered = codexRegisteredViaCli();
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
registered = tomlR.status === "ok";
|
|
510
|
+
}
|
|
429
511
|
results.push({
|
|
430
512
|
label: "codex: config.toml MCP server block",
|
|
431
|
-
ok:
|
|
432
|
-
detail:
|
|
513
|
+
ok: registered,
|
|
514
|
+
detail: registered ? (repaired ? "repaired" : "registered") : "not registered; CLI repair failed",
|
|
433
515
|
});
|
|
434
516
|
const allOk = Object.values(hkR.statuses).every((s) => s === "ok");
|
|
435
517
|
results.push({
|
|
436
518
|
label: "codex: SessionStart + UserPromptSubmit hooks",
|
|
437
519
|
ok: allOk,
|
|
438
|
-
detail: allOk ? "wired (trust via /hooks in Codex)" : "incomplete
|
|
520
|
+
detail: allOk ? "wired (trust via /hooks in Codex)" : "incomplete - run: subagent-mcp setup",
|
|
439
521
|
});
|
|
440
522
|
}
|
|
441
|
-
if (!hasClaude && !hasCodex) {
|
|
523
|
+
if (!hasClaude && !hasClaudeConfig && !hasCodex) {
|
|
442
524
|
results.push({
|
|
443
525
|
label: "vendors",
|
|
444
526
|
ok: false,
|
|
@@ -483,7 +565,7 @@ export async function runSetup() {
|
|
|
483
565
|
// Read-back verification: report what is ACTUALLY on disk now.
|
|
484
566
|
if (!DRY_RUN) {
|
|
485
567
|
console.log("\n--- Verification (read-back) ---");
|
|
486
|
-
for (const r of verifyWiring()) {
|
|
568
|
+
for (const r of verifyWiring(INSTALL_ROOT, false)) {
|
|
487
569
|
console.log(` ${r.ok ? "PASS" : "FAIL"} ${r.label} — ${r.detail}`);
|
|
488
570
|
}
|
|
489
571
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heretyc/subagent-mcp",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.2",
|
|
4
4
|
"description": "MCP server that launches and manages local Claude Code and Codex CLI sub-agents as child processes (no direct Anthropic/OpenAI API).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|