@leo000001/claude-code-mcp 2.0.3 → 2.2.0

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/index.js CHANGED
@@ -15,37 +15,69 @@ function normalizePermissionUpdatedInput(input) {
15
15
  return { input };
16
16
  }
17
17
 
18
- // src/utils/normalize-tool-input.ts
19
- function normalizeMsysToWindowsPath(path2) {
20
- if (/^[a-zA-Z]:[\\/]/.test(path2) || path2.startsWith("\\\\")) return void 0;
21
- if (path2.startsWith("//")) {
22
- const m2 = path2.match(/^\/\/([^/]{2,})\/(.*)$/);
18
+ // src/utils/normalize-windows-path.ts
19
+ import path from "path";
20
+ import os from "os";
21
+ function maybeAddWindowsLongPathPrefix(value) {
22
+ if (value.startsWith("\\\\?\\") || value.startsWith("\\\\.\\") || value.length < 240)
23
+ return value;
24
+ return path.win32.toNamespacedPath(value);
25
+ }
26
+ function expandTilde(value, platform) {
27
+ if (value === "~") return os.homedir();
28
+ if (!value.startsWith("~")) return value;
29
+ const next = value[1];
30
+ if (next !== "/" && next !== "\\") return value;
31
+ const rest = value.slice(2);
32
+ const join2 = platform === "win32" ? path.win32.join : path.posix.join;
33
+ return join2(os.homedir(), rest);
34
+ }
35
+ function normalizeMsysToWindowsPathInner(rawPath) {
36
+ if (/^[a-zA-Z]:[\\/]/.test(rawPath) || rawPath.startsWith("\\\\")) return void 0;
37
+ if (rawPath.startsWith("//")) {
38
+ const m2 = rawPath.match(/^\/\/([^/]{2,})\/(.*)$/);
23
39
  if (m2) {
24
40
  const host = m2[1];
25
41
  const rest2 = m2[2] ?? "";
26
42
  return `\\\\${host}\\${rest2.replace(/\//g, "\\")}`;
27
43
  }
28
44
  }
29
- const cyg = path2.match(/^\/cygdrive\/([a-zA-Z])\/(.*)$/);
45
+ const cyg = rawPath.match(/^\/cygdrive\/([a-zA-Z])\/(.*)$/);
30
46
  if (cyg) {
31
47
  const drive2 = cyg[1].toUpperCase();
32
48
  const rest2 = cyg[2] ?? "";
33
49
  return `${drive2}:\\${rest2.replace(/\//g, "\\")}`;
34
50
  }
35
- const m = path2.match(/^\/(?:mnt\/)?([a-zA-Z])\/(.*)$/);
51
+ const m = rawPath.match(/^\/(?:mnt\/)?([a-zA-Z])\/(.*)$/);
36
52
  if (!m) return void 0;
37
53
  const drive = m[1].toUpperCase();
38
54
  const rest = m[2] ?? "";
39
55
  return `${drive}:\\${rest.replace(/\//g, "\\")}`;
40
56
  }
41
- function normalizeToolInput(toolName, input, platform = process.platform) {
57
+ function normalizeMsysToWindowsPath(rawPath) {
58
+ const converted = normalizeMsysToWindowsPathInner(rawPath);
59
+ if (!converted) return void 0;
60
+ return path.win32.normalize(converted);
61
+ }
62
+ function normalizeWindowsPathLike(value, platform = process.platform) {
63
+ const expanded = expandTilde(value, platform);
64
+ if (platform !== "win32") return expanded;
65
+ const converted = normalizeMsysToWindowsPath(expanded);
66
+ if (!converted && expanded.startsWith("/")) return expanded;
67
+ const normalized = path.win32.normalize(converted ?? expanded);
68
+ return maybeAddWindowsLongPathPrefix(normalized);
69
+ }
70
+ function normalizeWindowsPathArray(values, platform = process.platform) {
71
+ return values.map((v) => normalizeWindowsPathLike(v, platform));
72
+ }
73
+
74
+ // src/utils/normalize-tool-input.ts
75
+ function normalizeToolInput(_toolName, input, platform = process.platform) {
42
76
  if (platform !== "win32") return input;
43
- if (toolName !== "NotebookEdit") return input;
44
77
  const filePath = input.file_path;
45
78
  if (typeof filePath !== "string") return input;
46
- const normalized = normalizeMsysToWindowsPath(filePath);
47
- if (!normalized) return input;
48
- return { ...input, file_path: normalized };
79
+ const normalized = normalizeWindowsPathLike(filePath, platform);
80
+ return normalized === filePath ? input : { ...input, file_path: normalized };
49
81
  }
50
82
 
51
83
  // src/session/manager.ts
@@ -54,20 +86,46 @@ var DEFAULT_RUNNING_SESSION_MAX_MS = 4 * 60 * 60 * 1e3;
54
86
  var DEFAULT_CLEANUP_INTERVAL_MS = 6e4;
55
87
  var DEFAULT_EVENT_BUFFER_MAX_SIZE = 1e3;
56
88
  var DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE = 2e3;
89
+ var DEFAULT_MAX_SESSIONS = 128;
90
+ var DEFAULT_MAX_PENDING_PERMISSIONS_PER_SESSION = 64;
57
91
  var SessionManager = class _SessionManager {
58
92
  sessions = /* @__PURE__ */ new Map();
59
93
  runtime = /* @__PURE__ */ new Map();
94
+ drainingSessions = /* @__PURE__ */ new Map();
60
95
  cleanupTimer;
61
96
  sessionTtlMs = DEFAULT_SESSION_TTL_MS;
62
97
  runningSessionMaxMs = DEFAULT_RUNNING_SESSION_MAX_MS;
63
98
  platform;
99
+ maxSessions;
100
+ maxPendingPermissionsPerSession;
64
101
  constructor(opts) {
65
102
  this.platform = opts?.platform ?? process.platform;
103
+ const envRaw = process.env.CLAUDE_CODE_MCP_MAX_SESSIONS;
104
+ const envParsed = typeof envRaw === "string" && envRaw.trim() !== "" ? Number.parseInt(envRaw, 10) : NaN;
105
+ const configured = opts?.maxSessions ?? (Number.isFinite(envParsed) ? envParsed : void 0);
106
+ this.maxSessions = typeof configured === "number" ? configured <= 0 ? Number.POSITIVE_INFINITY : configured : DEFAULT_MAX_SESSIONS;
107
+ const maxPendingEnvRaw = process.env.CLAUDE_CODE_MCP_MAX_PENDING_PERMISSIONS;
108
+ const maxPendingEnvParsed = typeof maxPendingEnvRaw === "string" && maxPendingEnvRaw.trim() !== "" ? Number.parseInt(maxPendingEnvRaw, 10) : NaN;
109
+ const maxPendingConfigured = opts?.maxPendingPermissionsPerSession ?? (Number.isFinite(maxPendingEnvParsed) ? maxPendingEnvParsed : void 0);
110
+ this.maxPendingPermissionsPerSession = typeof maxPendingConfigured === "number" ? maxPendingConfigured <= 0 ? Number.POSITIVE_INFINITY : maxPendingConfigured : DEFAULT_MAX_PENDING_PERMISSIONS_PER_SESSION;
66
111
  this.cleanupTimer = setInterval(() => this.cleanup(), DEFAULT_CLEANUP_INTERVAL_MS);
67
112
  if (this.cleanupTimer.unref) {
68
113
  this.cleanupTimer.unref();
69
114
  }
70
115
  }
116
+ getSessionCount() {
117
+ return this.sessions.size;
118
+ }
119
+ getMaxSessions() {
120
+ return this.maxSessions;
121
+ }
122
+ getMaxPendingPermissionsPerSession() {
123
+ return this.maxPendingPermissionsPerSession;
124
+ }
125
+ hasCapacityFor(additionalSessions) {
126
+ if (additionalSessions <= 0) return true;
127
+ return this.sessions.size + additionalSessions <= this.maxSessions;
128
+ }
71
129
  create(params) {
72
130
  const now = (/* @__PURE__ */ new Date()).toISOString();
73
131
  const existing = this.sessions.get(params.sessionId);
@@ -160,7 +218,10 @@ var SessionManager = class _SessionManager {
160
218
  if (info.status === "waiting_permission") {
161
219
  this.finishAllPending(
162
220
  sessionId,
163
- { behavior: "deny", message: "Session cancelled", interrupt: true },
221
+ // `cancel()` already aborts the running query below. Avoid sending an additional
222
+ // interrupt=true control response while the stream is being torn down, which can race
223
+ // into SDK-level "Operation aborted" write errors on stdio clients.
224
+ { behavior: "deny", message: "Session cancelled", interrupt: false },
164
225
  "cancel"
165
226
  );
166
227
  }
@@ -178,8 +239,10 @@ var SessionManager = class _SessionManager {
178
239
  this.finishAllPending(
179
240
  sessionId,
180
241
  { behavior: "deny", message: "Session deleted", interrupt: true },
181
- "cleanup"
242
+ "cleanup",
243
+ { restoreRunning: false }
182
244
  );
245
+ this.drainingSessions.delete(sessionId);
183
246
  this.runtime.delete(sessionId);
184
247
  return this.sessions.delete(sessionId);
185
248
  }
@@ -235,7 +298,53 @@ var SessionManager = class _SessionManager {
235
298
  const state = this.runtime.get(sessionId);
236
299
  const info = this.sessions.get(sessionId);
237
300
  if (!state || !info) return false;
301
+ if (info.status !== "running" && info.status !== "waiting_permission") {
302
+ try {
303
+ finish({
304
+ behavior: "deny",
305
+ message: `Session is not accepting permission requests (status: ${info.status}).`,
306
+ interrupt: true
307
+ });
308
+ } catch {
309
+ }
310
+ return false;
311
+ }
312
+ if (this.isDraining(sessionId)) {
313
+ try {
314
+ finish({
315
+ behavior: "deny",
316
+ message: "Session is finishing pending permission requests.",
317
+ interrupt: true
318
+ });
319
+ } catch {
320
+ }
321
+ return false;
322
+ }
238
323
  if (!state.pendingPermissions.has(req.requestId)) {
324
+ if (state.pendingPermissions.size >= this.maxPendingPermissionsPerSession) {
325
+ const deny = {
326
+ behavior: "deny",
327
+ message: "Too many pending permission requests for this session.",
328
+ interrupt: false
329
+ };
330
+ this.pushEvent(sessionId, {
331
+ type: "permission_result",
332
+ data: {
333
+ requestId: req.requestId,
334
+ toolName: req.toolName,
335
+ behavior: "deny",
336
+ source: "policy",
337
+ message: deny.message,
338
+ interrupt: deny.interrupt
339
+ },
340
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
341
+ });
342
+ try {
343
+ finish(deny);
344
+ } catch {
345
+ }
346
+ return false;
347
+ }
239
348
  const inferredExpiresAt = new Date(Date.now() + timeoutMs).toISOString();
240
349
  const record = {
241
350
  ...req,
@@ -321,6 +430,11 @@ var SessionManager = class _SessionManager {
321
430
  if (finalResult.behavior === "deny") {
322
431
  eventData.message = finalResult.message;
323
432
  eventData.interrupt = finalResult.interrupt;
433
+ } else {
434
+ const allow = finalResult;
435
+ if (allow.updatedInput !== void 0) eventData.updatedInput = allow.updatedInput;
436
+ if (allow.updatedPermissions !== void 0)
437
+ eventData.updatedPermissions = allow.updatedPermissions;
324
438
  }
325
439
  this.pushEvent(sessionId, {
326
440
  type: "permission_result",
@@ -331,17 +445,41 @@ var SessionManager = class _SessionManager {
331
445
  pending.finish(finalResult);
332
446
  } catch {
333
447
  }
334
- if (info.status === "waiting_permission" && state.pendingPermissions.size === 0) {
448
+ if (info.status === "waiting_permission" && state.pendingPermissions.size === 0 && !this.isDraining(sessionId)) {
335
449
  info.status = "running";
336
450
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
337
451
  }
338
452
  return true;
339
453
  }
340
- finishAllPending(sessionId, result, source) {
454
+ finishAllPending(sessionId, result, source, opts) {
341
455
  const state = this.runtime.get(sessionId);
342
456
  if (!state) return;
343
- for (const requestId of Array.from(state.pendingPermissions.keys())) {
344
- this.finishRequest(sessionId, requestId, result, source);
457
+ if (state.pendingPermissions.size === 0) return;
458
+ this.beginDraining(sessionId);
459
+ try {
460
+ while (state.pendingPermissions.size > 0) {
461
+ const next = state.pendingPermissions.keys().next();
462
+ if (next.done || typeof next.value !== "string") break;
463
+ const requestId = next.value;
464
+ const handled = this.finishRequest(sessionId, requestId, result, source);
465
+ if (!handled) {
466
+ const pending = state.pendingPermissions.get(requestId);
467
+ if (pending?.timeoutId) clearTimeout(pending.timeoutId);
468
+ state.pendingPermissions.delete(requestId);
469
+ try {
470
+ pending?.finish(result);
471
+ } catch {
472
+ }
473
+ }
474
+ }
475
+ } finally {
476
+ this.endDraining(sessionId);
477
+ }
478
+ const restoreRunning = opts?.restoreRunning ?? true;
479
+ const info = this.sessions.get(sessionId);
480
+ if (restoreRunning && info && info.status === "waiting_permission" && state.pendingPermissions.size === 0) {
481
+ info.status = "running";
482
+ info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
345
483
  }
346
484
  }
347
485
  /** Remove sessions that have been idle for too long, or stuck running too long */
@@ -353,29 +491,40 @@ var SessionManager = class _SessionManager {
353
491
  this.finishAllPending(
354
492
  id,
355
493
  { behavior: "deny", message: "Session expired", interrupt: true },
356
- "cleanup"
494
+ "cleanup",
495
+ { restoreRunning: false }
357
496
  );
497
+ this.drainingSessions.delete(id);
358
498
  this.sessions.delete(id);
359
499
  this.runtime.delete(id);
360
500
  } else if (info.status === "running" && now - lastActive > this.runningSessionMaxMs) {
361
501
  if (info.abortController) info.abortController.abort();
502
+ info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
503
+ info.cancelledReason = info.cancelledReason ?? "Session timed out";
504
+ info.cancelledSource = info.cancelledSource ?? "cleanup";
362
505
  info.status = "error";
363
506
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
364
507
  } else if (info.status === "waiting_permission" && now - lastActive > this.runningSessionMaxMs) {
365
508
  this.finishAllPending(
366
509
  id,
367
510
  { behavior: "deny", message: "Session timed out", interrupt: true },
368
- "cleanup"
511
+ "cleanup",
512
+ { restoreRunning: false }
369
513
  );
370
514
  if (info.abortController) info.abortController.abort();
515
+ info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
516
+ info.cancelledReason = info.cancelledReason ?? "Session timed out";
517
+ info.cancelledSource = info.cancelledSource ?? "cleanup";
371
518
  info.status = "error";
372
519
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
373
520
  } else if (info.status !== "running" && info.status !== "waiting_permission" && now - lastActive > this.sessionTtlMs) {
374
521
  this.finishAllPending(
375
522
  id,
376
523
  { behavior: "deny", message: "Session expired", interrupt: true },
377
- "cleanup"
524
+ "cleanup",
525
+ { restoreRunning: false }
378
526
  );
527
+ this.drainingSessions.delete(id);
379
528
  this.sessions.delete(id);
380
529
  this.runtime.delete(id);
381
530
  }
@@ -424,7 +573,8 @@ var SessionManager = class _SessionManager {
424
573
  this.finishAllPending(
425
574
  info.sessionId,
426
575
  { behavior: "deny", message: "Server shutting down", interrupt: true },
427
- "destroy"
576
+ "destroy",
577
+ { restoreRunning: false }
428
578
  );
429
579
  if ((info.status === "running" || info.status === "waiting_permission") && info.abortController) {
430
580
  info.abortController.abort();
@@ -494,6 +644,20 @@ var SessionManager = class _SessionManager {
494
644
  static clearTerminalEvents(buffer) {
495
645
  buffer.events = buffer.events.filter((e) => e.type !== "result" && e.type !== "error");
496
646
  }
647
+ isDraining(sessionId) {
648
+ return (this.drainingSessions.get(sessionId) ?? 0) > 0;
649
+ }
650
+ beginDraining(sessionId) {
651
+ this.drainingSessions.set(sessionId, (this.drainingSessions.get(sessionId) ?? 0) + 1);
652
+ }
653
+ endDraining(sessionId) {
654
+ const current = this.drainingSessions.get(sessionId) ?? 0;
655
+ if (current <= 1) {
656
+ this.drainingSessions.delete(sessionId);
657
+ return;
658
+ }
659
+ this.drainingSessions.set(sessionId, current - 1);
660
+ }
497
661
  };
498
662
 
499
663
  // src/types.ts
@@ -510,23 +674,98 @@ import { AbortError, query } from "@anthropic-ai/claude-agent-sdk";
510
674
  // src/utils/windows.ts
511
675
  import { existsSync } from "fs";
512
676
  import { execSync } from "child_process";
513
- import path from "path";
514
- var { join, dirname, normalize } = path.win32;
677
+ import path2 from "path";
678
+ var { join, dirname, normalize, isAbsolute } = path2.win32;
679
+ var LONG_PATH_PREFIX = "\\\\?\\";
680
+ var UNC_LONG_PATH_PREFIX = "\\\\?\\UNC\\";
515
681
  function isWindows() {
516
682
  return process.platform === "win32";
517
683
  }
684
+ function normalizeMaybeQuotedPath(raw) {
685
+ return normalize(raw.trim().replace(/^"|"$/g, ""));
686
+ }
687
+ function existsSyncSafe(candidate) {
688
+ try {
689
+ return existsSync(candidate);
690
+ } catch {
691
+ return false;
692
+ }
693
+ }
694
+ function ensureLongPathPrefix(normalized) {
695
+ if (!normalized || normalized.startsWith(LONG_PATH_PREFIX)) return normalized;
696
+ if (!isAbsolute(normalized)) return normalized;
697
+ if (normalized.startsWith("\\\\")) {
698
+ return `${UNC_LONG_PATH_PREFIX}${normalized.slice(2)}`;
699
+ }
700
+ return `${LONG_PATH_PREFIX}${normalized}`;
701
+ }
702
+ function existsPath(raw) {
703
+ if (!raw) return false;
704
+ const normalized = normalizeMaybeQuotedPath(raw);
705
+ if (existsSyncSafe(normalized)) return true;
706
+ const longPath = ensureLongPathPrefix(normalized);
707
+ if (longPath === normalized) return false;
708
+ return existsSyncSafe(longPath);
709
+ }
710
+ function tryWhere(exe) {
711
+ try {
712
+ const output = execSync(`where ${exe}`, {
713
+ encoding: "utf8",
714
+ timeout: 2e3,
715
+ windowsHide: true
716
+ });
717
+ return output.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((p) => normalizeMaybeQuotedPath(p));
718
+ } catch {
719
+ return [];
720
+ }
721
+ }
722
+ function pathEntries() {
723
+ const raw = process.env.PATH;
724
+ if (typeof raw !== "string" || raw.trim() === "") return [];
725
+ return raw.split(path2.delimiter).map((p) => p.trim().replace(/^"|"$/g, "")).filter(Boolean).map((p) => normalizeMaybeQuotedPath(p));
726
+ }
727
+ function tryFromPath(exeNames) {
728
+ const results = [];
729
+ for (const dir of pathEntries()) {
730
+ for (const exe of exeNames) {
731
+ const full = normalize(path2.win32.join(dir, exe));
732
+ if (existsPath(full)) results.push(full);
733
+ }
734
+ }
735
+ return results;
736
+ }
737
+ function firstExisting(candidates) {
738
+ for (const p of candidates) {
739
+ if (p && existsPath(p)) return p;
740
+ }
741
+ return null;
742
+ }
743
+ function isWindowsSystemBash(pathLike) {
744
+ const p = normalizeMaybeQuotedPath(pathLike).toLowerCase();
745
+ return p.endsWith("\\windows\\system32\\bash.exe") || p.endsWith("\\windows\\syswow64\\bash.exe") || p.includes("\\windows\\system32\\bash.exe") || p.includes("\\windows\\syswow64\\bash.exe");
746
+ }
518
747
  function findGitBash() {
519
748
  const envPathRaw = process.env.CLAUDE_CODE_GIT_BASH_PATH;
520
749
  if (envPathRaw && envPathRaw.trim() !== "") {
521
- const envPath = normalize(envPathRaw.trim().replace(/^"|"$/g, ""));
522
- if (existsSync(envPath)) return envPath;
523
- return null;
524
- }
750
+ const envPath = normalizeMaybeQuotedPath(envPathRaw);
751
+ if (existsPath(envPath)) return envPath;
752
+ }
753
+ const programFilesRoots = [
754
+ process.env.ProgramW6432,
755
+ process.env.ProgramFiles,
756
+ process.env["ProgramFiles(x86)"]
757
+ ].filter((v) => typeof v === "string" && v.trim() !== "");
758
+ const defaultCandidates = [];
759
+ for (const root of programFilesRoots) {
760
+ const base = join(normalizeMaybeQuotedPath(root), "Git");
761
+ defaultCandidates.push(join(base, "bin", "bash.exe"));
762
+ defaultCandidates.push(join(base, "usr", "bin", "bash.exe"));
763
+ }
764
+ const fromDefault = firstExisting(defaultCandidates);
765
+ if (fromDefault) return fromDefault;
525
766
  try {
526
- const output = execSync("where git", { encoding: "utf8" });
527
- const gitCandidates = output.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
528
- for (const gitPathRaw of gitCandidates) {
529
- const gitPath = normalize(gitPathRaw.replace(/^"|"$/g, ""));
767
+ const gitCandidates = [...tryFromPath(["git.exe", "git.cmd", "git.bat"]), ...tryWhere("git")];
768
+ for (const gitPath of gitCandidates) {
530
769
  if (!gitPath) continue;
531
770
  const gitDir = dirname(gitPath);
532
771
  const gitDirLower = gitDir.toLowerCase();
@@ -548,19 +787,38 @@ function findGitBash() {
548
787
  bashCandidates.push(join(root, "mingw64", "bin", "bash.exe"));
549
788
  }
550
789
  for (const bashPath of bashCandidates) {
551
- const normalized = normalize(bashPath);
552
- if (existsSync(normalized)) return normalized;
790
+ const normalizedPath = normalize(bashPath);
791
+ if (existsPath(normalizedPath)) return normalizedPath;
553
792
  }
554
793
  }
555
794
  } catch {
556
795
  }
796
+ const fromWhereBash = firstExisting(
797
+ [...tryFromPath(["bash.exe"]), ...tryWhere("bash")].filter(
798
+ (p) => p.toLowerCase().endsWith("\\bash.exe") && !isWindowsSystemBash(p)
799
+ )
800
+ );
801
+ if (fromWhereBash) return fromWhereBash;
557
802
  return null;
558
803
  }
559
804
  function checkWindowsBashAvailability() {
560
805
  if (!isWindows()) return;
806
+ const envPathRaw = process.env.CLAUDE_CODE_GIT_BASH_PATH;
807
+ const envPath = envPathRaw && envPathRaw.trim() !== "" ? normalizeMaybeQuotedPath(envPathRaw) : null;
808
+ const envValid = !!(envPath && existsPath(envPath));
561
809
  const bashPath = findGitBash();
562
810
  if (bashPath) {
563
- console.error(`[windows] Git Bash detected: ${bashPath}`);
811
+ if (!envValid) {
812
+ process.env.CLAUDE_CODE_GIT_BASH_PATH = bashPath;
813
+ if (envPathRaw && envPathRaw.trim() !== "") {
814
+ console.error(
815
+ `[windows] WARNING: CLAUDE_CODE_GIT_BASH_PATH is set to "${envPathRaw}" but the file does not exist.`
816
+ );
817
+ }
818
+ console.error(`[windows] Git Bash detected: ${bashPath} (set CLAUDE_CODE_GIT_BASH_PATH)`);
819
+ } else {
820
+ console.error(`[windows] Git Bash detected: ${bashPath}`);
821
+ }
564
822
  return;
565
823
  }
566
824
  const hint = process.env.CLAUDE_CODE_GIT_BASH_PATH ? `CLAUDE_CODE_GIT_BASH_PATH is set to "${process.env.CLAUDE_CODE_GIT_BASH_PATH}" but the file does not exist.` : "CLAUDE_CODE_GIT_BASH_PATH is not set and git was not found in PATH.";
@@ -577,7 +835,9 @@ function checkWindowsBashAvailability() {
577
835
  var WINDOWS_BASH_HINT = '\n\n[Windows] The Claude Code CLI requires Git Bash. Set CLAUDE_CODE_GIT_BASH_PATH in your MCP server config or system environment. See README.md "Windows Support" section for details.';
578
836
  function enhanceWindowsError(errorMessage) {
579
837
  if (!isWindows()) return errorMessage;
580
- if (errorMessage.includes("git-bash") || errorMessage.includes("bash.exe") || errorMessage.includes("CLAUDE_CODE_GIT_BASH_PATH")) {
838
+ const lower = errorMessage.toLowerCase();
839
+ const looksLikeMissingBash = lower.includes("enoent") && (lower.includes("bash") || lower.includes("git-bash")) || lower.includes("claude code on windows requires git-bash");
840
+ if (looksLikeMissingBash || lower.includes("claude_code_git_bash_path")) {
581
841
  return errorMessage + WINDOWS_BASH_HINT;
582
842
  }
583
843
  return errorMessage;
@@ -586,6 +846,13 @@ function enhanceWindowsError(errorMessage) {
586
846
  // src/tools/query-consumer.ts
587
847
  var MAX_TRANSIENT_RETRIES = 3;
588
848
  var INITIAL_RETRY_DELAY_MS = 1e3;
849
+ var DEFAULT_PERMISSION_REQUEST_TIMEOUT_MS = 6e4;
850
+ var MAX_PERMISSION_REQUEST_TIMEOUT_MS = 5 * 6e4;
851
+ var MAX_PREINIT_BUFFER_MESSAGES = 200;
852
+ function clampPermissionRequestTimeoutMs(ms) {
853
+ if (!Number.isFinite(ms) || ms <= 0) return DEFAULT_PERMISSION_REQUEST_TIMEOUT_MS;
854
+ return Math.min(ms, MAX_PERMISSION_REQUEST_TIMEOUT_MS);
855
+ }
589
856
  function classifyError(err, abortSignal) {
590
857
  if (abortSignal.aborted) return "abort";
591
858
  if (err instanceof AbortError || err instanceof Error && err.name === "AbortError") {
@@ -610,12 +877,16 @@ function describeTool(toolName, toolCache) {
610
877
  return found?.description;
611
878
  }
612
879
  function sdkResultToAgentResult(result) {
880
+ const sessionTotalTurns = result.session_total_turns;
881
+ const sessionTotalCostUsd = result.session_total_cost_usd;
613
882
  const base = {
614
883
  sessionId: result.session_id,
615
884
  durationMs: result.duration_ms,
616
885
  durationApiMs: result.duration_api_ms,
617
886
  numTurns: result.num_turns,
618
887
  totalCostUsd: result.total_cost_usd,
888
+ sessionTotalTurns: typeof sessionTotalTurns === "number" ? sessionTotalTurns : void 0,
889
+ sessionTotalCostUsd: typeof sessionTotalCostUsd === "number" ? sessionTotalCostUsd : void 0,
619
890
  stopReason: result.stop_reason,
620
891
  usage: result.usage,
621
892
  modelUsage: result.modelUsage,
@@ -727,6 +998,9 @@ function consumeQuery(params) {
727
998
  return activeSessionId;
728
999
  };
729
1000
  let initTimeoutId;
1001
+ const permissionRequestTimeoutMs = clampPermissionRequestTimeoutMs(
1002
+ params.permissionRequestTimeoutMs
1003
+ );
730
1004
  const canUseTool = async (toolName, input, options2) => {
731
1005
  const sessionId = await getSessionId();
732
1006
  const normalizedInput = normalizeToolInput(toolName, input, params.platform);
@@ -744,7 +1018,7 @@ function consumeQuery(params) {
744
1018
  }
745
1019
  const requestId = `${options2.toolUseID}:${toolName}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
746
1020
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
747
- const timeoutMs = params.permissionRequestTimeoutMs;
1021
+ const timeoutMs = permissionRequestTimeoutMs;
748
1022
  const expiresAt = new Date(Date.now() + timeoutMs).toISOString();
749
1023
  const record = {
750
1024
  requestId,
@@ -781,10 +1055,14 @@ function consumeQuery(params) {
781
1055
  sessionId,
782
1056
  record,
783
1057
  finish,
784
- params.permissionRequestTimeoutMs
1058
+ permissionRequestTimeoutMs
785
1059
  );
786
1060
  if (!registered) {
787
- finish({ behavior: "deny", message: "Session no longer exists.", interrupt: true });
1061
+ finish({
1062
+ behavior: "deny",
1063
+ message: "Session no longer exists.",
1064
+ interrupt: true
1065
+ });
788
1066
  return;
789
1067
  }
790
1068
  options2.signal.addEventListener("abort", abortListener, { once: true });
@@ -819,6 +1097,7 @@ function consumeQuery(params) {
819
1097
  };
820
1098
  const done = (async () => {
821
1099
  const preInit = [];
1100
+ let preInitDropped = 0;
822
1101
  if (shouldWaitForInit) {
823
1102
  initTimeoutId = setTimeout(() => {
824
1103
  close();
@@ -843,6 +1122,13 @@ function consumeQuery(params) {
843
1122
  sessionIdResolved = true;
844
1123
  resolveSessionId(activeSessionId);
845
1124
  if (initTimeoutId) clearTimeout(initTimeoutId);
1125
+ if (preInitDropped > 0) {
1126
+ params.sessionManager.pushEvent(activeSessionId, {
1127
+ type: "progress",
1128
+ data: { type: "pre_init_dropped", dropped: preInitDropped },
1129
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1130
+ });
1131
+ }
846
1132
  for (const buffered of preInit) {
847
1133
  const event2 = messageToEvent(buffered);
848
1134
  if (!event2) continue;
@@ -858,35 +1144,50 @@ function consumeQuery(params) {
858
1144
  }
859
1145
  if (shouldWaitForInit && !sessionIdResolved) {
860
1146
  preInit.push(message);
1147
+ if (preInit.length > MAX_PREINIT_BUFFER_MESSAGES) {
1148
+ preInit.shift();
1149
+ preInitDropped++;
1150
+ }
861
1151
  continue;
862
1152
  }
863
1153
  if (message.type === "result") {
864
1154
  const sessionId2 = message.session_id ?? await getSessionId();
865
1155
  const agentResult = sdkResultToAgentResult(message);
1156
+ const current = params.sessionManager.get(sessionId2);
1157
+ const previousTotalTurns = current?.totalTurns ?? 0;
1158
+ const previousTotalCostUsd = current?.totalCostUsd ?? 0;
1159
+ const computedTotalTurns = previousTotalTurns + agentResult.numTurns;
1160
+ const computedTotalCostUsd = previousTotalCostUsd + agentResult.totalCostUsd;
1161
+ const sessionTotalTurns = typeof agentResult.sessionTotalTurns === "number" ? Math.max(agentResult.sessionTotalTurns, computedTotalTurns) : computedTotalTurns;
1162
+ const sessionTotalCostUsd = typeof agentResult.sessionTotalCostUsd === "number" ? Math.max(agentResult.sessionTotalCostUsd, computedTotalCostUsd) : computedTotalCostUsd;
1163
+ const resultWithSessionTotals = {
1164
+ ...agentResult,
1165
+ sessionTotalTurns,
1166
+ sessionTotalCostUsd
1167
+ };
866
1168
  const stored = {
867
1169
  type: agentResult.isError ? "error" : "result",
868
- result: agentResult,
1170
+ result: resultWithSessionTotals,
869
1171
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
870
1172
  };
871
1173
  params.sessionManager.setResult(sessionId2, stored);
872
1174
  params.sessionManager.clearTerminalEvents(sessionId2);
873
1175
  params.sessionManager.pushEvent(sessionId2, {
874
1176
  type: agentResult.isError ? "error" : "result",
875
- data: agentResult,
1177
+ data: resultWithSessionTotals,
876
1178
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
877
1179
  });
878
- const current = params.sessionManager.get(sessionId2);
879
1180
  if (current && current.status !== "cancelled") {
880
1181
  params.sessionManager.update(sessionId2, {
881
1182
  status: agentResult.isError ? "error" : "idle",
882
- totalTurns: agentResult.numTurns,
883
- totalCostUsd: agentResult.totalCostUsd,
1183
+ totalTurns: sessionTotalTurns,
1184
+ totalCostUsd: sessionTotalCostUsd,
884
1185
  abortController: void 0
885
1186
  });
886
1187
  } else if (current) {
887
1188
  params.sessionManager.update(sessionId2, {
888
- totalTurns: agentResult.numTurns,
889
- totalCostUsd: agentResult.totalCostUsd,
1189
+ totalTurns: sessionTotalTurns,
1190
+ totalCostUsd: sessionTotalCostUsd,
890
1191
  abortController: void 0
891
1192
  });
892
1193
  }
@@ -1035,15 +1336,23 @@ function consumeQuery(params) {
1035
1336
 
1036
1337
  // src/utils/resume-token.ts
1037
1338
  import { createHmac } from "crypto";
1339
+ function getResumeSecrets() {
1340
+ const raw = process.env.CLAUDE_CODE_MCP_RESUME_SECRET;
1341
+ if (typeof raw !== "string") return [];
1342
+ return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
1343
+ }
1038
1344
  function getResumeSecret() {
1039
- const secret = process.env.CLAUDE_CODE_MCP_RESUME_SECRET;
1040
- if (typeof secret !== "string") return void 0;
1041
- const trimmed = secret.trim();
1042
- return trimmed.length > 0 ? trimmed : void 0;
1345
+ return getResumeSecrets()[0];
1043
1346
  }
1044
1347
  function computeResumeToken(sessionId, secret) {
1045
1348
  return createHmac("sha256", secret).update(sessionId).digest("base64url");
1046
1349
  }
1350
+ function isValidResumeToken(sessionId, token, secrets) {
1351
+ for (const secret of secrets) {
1352
+ if (computeResumeToken(sessionId, secret) === token) return true;
1353
+ }
1354
+ return false;
1355
+ }
1047
1356
 
1048
1357
  // src/utils/race-with-abort.ts
1049
1358
  function raceWithAbort(promise, signal, onAbort) {
@@ -1070,7 +1379,7 @@ function raceWithAbort(promise, signal, onAbort) {
1070
1379
 
1071
1380
  // src/utils/build-options.ts
1072
1381
  function buildOptions(src) {
1073
- const opts = { cwd: src.cwd };
1382
+ const opts = { cwd: normalizeWindowsPathLike(src.cwd) };
1074
1383
  if (src.allowedTools !== void 0) opts.allowedTools = src.allowedTools;
1075
1384
  if (src.disallowedTools !== void 0) opts.disallowedTools = src.disallowedTools;
1076
1385
  if (src.tools !== void 0) opts.tools = src.tools;
@@ -1082,13 +1391,13 @@ function buildOptions(src) {
1082
1391
  if (src.effort !== void 0) opts.effort = src.effort;
1083
1392
  if (src.betas !== void 0) opts.betas = src.betas;
1084
1393
  if (src.additionalDirectories !== void 0)
1085
- opts.additionalDirectories = src.additionalDirectories;
1394
+ opts.additionalDirectories = normalizeWindowsPathArray(src.additionalDirectories);
1086
1395
  if (src.outputFormat !== void 0) opts.outputFormat = src.outputFormat;
1087
1396
  if (src.thinking !== void 0) opts.thinking = src.thinking;
1088
1397
  if (src.persistSession !== void 0) opts.persistSession = src.persistSession;
1089
1398
  if (src.resumeSessionAt !== void 0) opts.resumeSessionAt = src.resumeSessionAt;
1090
1399
  if (src.pathToClaudeCodeExecutable !== void 0)
1091
- opts.pathToClaudeCodeExecutable = src.pathToClaudeCodeExecutable;
1400
+ opts.pathToClaudeCodeExecutable = normalizeWindowsPathLike(src.pathToClaudeCodeExecutable);
1092
1401
  if (src.agent !== void 0) opts.agent = src.agent;
1093
1402
  if (src.mcpServers !== void 0) opts.mcpServers = src.mcpServers;
1094
1403
  if (src.sandbox !== void 0) opts.sandbox = src.sandbox;
@@ -1101,11 +1410,48 @@ function buildOptions(src) {
1101
1410
  if (src.settingSources !== void 0) opts.settingSources = src.settingSources;
1102
1411
  else opts.settingSources = DEFAULT_SETTING_SOURCES;
1103
1412
  if (src.debug !== void 0) opts.debug = src.debug;
1104
- if (src.debugFile !== void 0) opts.debugFile = src.debugFile;
1413
+ if (src.debugFile !== void 0) opts.debugFile = normalizeWindowsPathLike(src.debugFile);
1105
1414
  if (src.env !== void 0) opts.env = { ...process.env, ...src.env };
1106
1415
  return opts;
1107
1416
  }
1108
1417
 
1418
+ // src/utils/session-create.ts
1419
+ function toSessionCreateParams(input) {
1420
+ const src = input.source;
1421
+ return {
1422
+ sessionId: input.sessionId,
1423
+ cwd: src.cwd,
1424
+ model: src.model,
1425
+ permissionMode: input.permissionMode,
1426
+ allowedTools: src.allowedTools,
1427
+ disallowedTools: src.disallowedTools,
1428
+ tools: src.tools,
1429
+ maxTurns: src.maxTurns,
1430
+ systemPrompt: src.systemPrompt,
1431
+ agents: src.agents,
1432
+ maxBudgetUsd: src.maxBudgetUsd,
1433
+ effort: src.effort,
1434
+ betas: src.betas,
1435
+ additionalDirectories: src.additionalDirectories,
1436
+ outputFormat: src.outputFormat,
1437
+ thinking: src.thinking,
1438
+ persistSession: src.persistSession,
1439
+ pathToClaudeCodeExecutable: src.pathToClaudeCodeExecutable,
1440
+ agent: src.agent,
1441
+ mcpServers: src.mcpServers,
1442
+ sandbox: src.sandbox,
1443
+ fallbackModel: src.fallbackModel,
1444
+ enableFileCheckpointing: src.enableFileCheckpointing,
1445
+ includePartialMessages: src.includePartialMessages,
1446
+ strictMcpConfig: src.strictMcpConfig,
1447
+ settingSources: src.settingSources ?? DEFAULT_SETTING_SOURCES,
1448
+ debug: src.debug,
1449
+ debugFile: src.debugFile,
1450
+ env: src.env,
1451
+ abortController: input.abortController
1452
+ };
1453
+ }
1454
+
1109
1455
  // src/tools/claude-code.ts
1110
1456
  async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, requestSignal) {
1111
1457
  const cwd = input.cwd !== void 0 ? input.cwd : serverCwd;
@@ -1116,10 +1462,28 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1116
1462
  error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a non-empty string.`
1117
1463
  };
1118
1464
  }
1465
+ if (!sessionManager.hasCapacityFor(1)) {
1466
+ return {
1467
+ sessionId: "",
1468
+ status: "error",
1469
+ error: `Error [${"RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */}]: Too many sessions (limit: ${sessionManager.getMaxSessions()}).`
1470
+ };
1471
+ }
1119
1472
  const abortController = new AbortController();
1120
1473
  const adv = input.advanced ?? {};
1121
1474
  const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
1122
- const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ?? 1e4;
1475
+ const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ?? input.sessionInitTimeoutMs ?? 1e4;
1476
+ const compatWarnings = [];
1477
+ if (input.sessionInitTimeoutMs !== void 0) {
1478
+ compatWarnings.push(
1479
+ "Top-level sessionInitTimeoutMs for claude_code is a compatibility alias; prefer advanced.sessionInitTimeoutMs."
1480
+ );
1481
+ }
1482
+ if (input.sessionInitTimeoutMs !== void 0 && adv.sessionInitTimeoutMs !== void 0 && input.sessionInitTimeoutMs !== adv.sessionInitTimeoutMs) {
1483
+ compatWarnings.push(
1484
+ `Both advanced.sessionInitTimeoutMs (${adv.sessionInitTimeoutMs}) and top-level sessionInitTimeoutMs (${input.sessionInitTimeoutMs}) were provided; using advanced.sessionInitTimeoutMs.`
1485
+ );
1486
+ }
1123
1487
  const flat = {
1124
1488
  cwd,
1125
1489
  allowedTools: input.allowedTools,
@@ -1127,52 +1491,37 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1127
1491
  maxTurns: input.maxTurns,
1128
1492
  model: input.model,
1129
1493
  systemPrompt: input.systemPrompt,
1130
- ...adv
1494
+ ...adv,
1495
+ effort: input.effort ?? adv.effort,
1496
+ thinking: input.thinking ?? adv.thinking
1497
+ };
1498
+ const normalizedFlat = {
1499
+ ...flat,
1500
+ cwd: normalizeWindowsPathLike(flat.cwd),
1501
+ additionalDirectories: flat.additionalDirectories !== void 0 ? normalizeWindowsPathArray(flat.additionalDirectories) : void 0,
1502
+ debugFile: flat.debugFile !== void 0 ? normalizeWindowsPathLike(flat.debugFile) : void 0,
1503
+ pathToClaudeCodeExecutable: flat.pathToClaudeCodeExecutable !== void 0 ? normalizeWindowsPathLike(flat.pathToClaudeCodeExecutable) : void 0
1131
1504
  };
1132
1505
  try {
1133
1506
  const handle = consumeQuery({
1134
1507
  mode: "start",
1135
1508
  prompt: input.prompt,
1136
1509
  abortController,
1137
- options: buildOptions(flat),
1510
+ options: buildOptions(normalizedFlat),
1138
1511
  permissionRequestTimeoutMs,
1139
1512
  sessionInitTimeoutMs,
1140
1513
  sessionManager,
1141
1514
  toolCache,
1142
1515
  onInit: (init) => {
1143
1516
  if (sessionManager.get(init.session_id)) return;
1144
- sessionManager.create({
1145
- sessionId: init.session_id,
1146
- cwd,
1147
- model: input.model,
1148
- permissionMode: "default",
1149
- allowedTools: input.allowedTools,
1150
- disallowedTools: input.disallowedTools,
1151
- tools: adv.tools,
1152
- maxTurns: input.maxTurns,
1153
- systemPrompt: input.systemPrompt,
1154
- agents: adv.agents,
1155
- maxBudgetUsd: adv.maxBudgetUsd,
1156
- effort: adv.effort,
1157
- betas: adv.betas,
1158
- additionalDirectories: adv.additionalDirectories,
1159
- outputFormat: adv.outputFormat,
1160
- thinking: adv.thinking,
1161
- persistSession: adv.persistSession,
1162
- pathToClaudeCodeExecutable: adv.pathToClaudeCodeExecutable,
1163
- agent: adv.agent,
1164
- mcpServers: adv.mcpServers,
1165
- sandbox: adv.sandbox,
1166
- fallbackModel: adv.fallbackModel,
1167
- enableFileCheckpointing: adv.enableFileCheckpointing,
1168
- includePartialMessages: adv.includePartialMessages,
1169
- strictMcpConfig: adv.strictMcpConfig,
1170
- settingSources: adv.settingSources ?? DEFAULT_SETTING_SOURCES,
1171
- debug: adv.debug,
1172
- debugFile: adv.debugFile,
1173
- env: adv.env,
1174
- abortController
1175
- });
1517
+ sessionManager.create(
1518
+ toSessionCreateParams({
1519
+ sessionId: init.session_id,
1520
+ source: normalizedFlat,
1521
+ permissionMode: "default",
1522
+ abortController
1523
+ })
1524
+ );
1176
1525
  }
1177
1526
  });
1178
1527
  const sessionId = await raceWithAbort(
@@ -1185,7 +1534,8 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1185
1534
  sessionId,
1186
1535
  status: "running",
1187
1536
  pollInterval: 3e3,
1188
- resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0
1537
+ resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0,
1538
+ compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0
1189
1539
  };
1190
1540
  } catch (err) {
1191
1541
  const message = err instanceof Error ? err.message : String(err);
@@ -1224,6 +1574,13 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1224
1574
  const sessionInitTimeoutMs = input.sessionInitTimeoutMs ?? 1e4;
1225
1575
  const existing = sessionManager.get(input.sessionId);
1226
1576
  if (!existing) {
1577
+ if (!sessionManager.hasCapacityFor(1)) {
1578
+ return {
1579
+ sessionId: input.sessionId,
1580
+ status: "error",
1581
+ error: `Error [${"RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */}]: Too many sessions (limit: ${sessionManager.getMaxSessions()}).`
1582
+ };
1583
+ }
1227
1584
  const allowDiskResume = process.env.CLAUDE_CODE_MCP_ALLOW_DISK_RESUME === "1";
1228
1585
  if (!allowDiskResume) {
1229
1586
  return {
@@ -1232,7 +1589,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1232
1589
  error: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`
1233
1590
  };
1234
1591
  }
1235
- const resumeSecret = getResumeSecret();
1592
+ const resumeSecrets = getResumeSecrets();
1593
+ const resumeSecret = resumeSecrets[0];
1236
1594
  if (!resumeSecret) {
1237
1595
  return {
1238
1596
  sessionId: input.sessionId,
@@ -1248,8 +1606,7 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1248
1606
  error: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: resumeToken is required for disk resume fallback.`
1249
1607
  };
1250
1608
  }
1251
- const expectedToken = computeResumeToken(input.sessionId, resumeSecret);
1252
- if (dr.resumeToken !== expectedToken) {
1609
+ if (!isValidResumeToken(input.sessionId, dr.resumeToken, resumeSecrets)) {
1253
1610
  return {
1254
1611
  sessionId: input.sessionId,
1255
1612
  status: "error",
@@ -1259,38 +1616,27 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1259
1616
  try {
1260
1617
  const abortController2 = new AbortController();
1261
1618
  const options2 = buildOptionsFromDiskResume(dr);
1262
- sessionManager.create({
1263
- sessionId: input.sessionId,
1619
+ if (input.effort !== void 0) options2.effort = input.effort;
1620
+ if (input.thinking !== void 0) options2.thinking = input.thinking;
1621
+ const { resumeToken: _resumeToken, ...rest } = dr;
1622
+ void _resumeToken;
1623
+ const source = {
1624
+ ...rest,
1264
1625
  cwd: options2.cwd ?? dr.cwd ?? "",
1265
- model: dr.model,
1266
- permissionMode: "default",
1267
- allowedTools: dr.allowedTools,
1268
- disallowedTools: dr.disallowedTools,
1269
- tools: dr.tools,
1270
- maxTurns: dr.maxTurns,
1271
- systemPrompt: dr.systemPrompt,
1272
- agents: dr.agents,
1273
- maxBudgetUsd: dr.maxBudgetUsd,
1274
- effort: dr.effort,
1275
- betas: dr.betas,
1276
- additionalDirectories: dr.additionalDirectories,
1277
- outputFormat: dr.outputFormat,
1278
- thinking: dr.thinking,
1279
- persistSession: dr.persistSession,
1280
- pathToClaudeCodeExecutable: dr.pathToClaudeCodeExecutable,
1281
- agent: dr.agent,
1282
- mcpServers: dr.mcpServers,
1283
- sandbox: dr.sandbox,
1284
- fallbackModel: dr.fallbackModel,
1285
- enableFileCheckpointing: dr.enableFileCheckpointing,
1286
- includePartialMessages: dr.includePartialMessages,
1287
- strictMcpConfig: dr.strictMcpConfig,
1288
- settingSources: dr.settingSources ?? DEFAULT_SETTING_SOURCES,
1289
- debug: dr.debug,
1290
- debugFile: dr.debugFile,
1291
- env: dr.env,
1292
- abortController: abortController2
1293
- });
1626
+ additionalDirectories: options2.additionalDirectories ?? rest.additionalDirectories,
1627
+ debugFile: options2.debugFile ?? rest.debugFile,
1628
+ pathToClaudeCodeExecutable: options2.pathToClaudeCodeExecutable ?? rest.pathToClaudeCodeExecutable,
1629
+ effort: input.effort ?? rest.effort,
1630
+ thinking: input.thinking ?? rest.thinking
1631
+ };
1632
+ sessionManager.create(
1633
+ toSessionCreateParams({
1634
+ sessionId: input.sessionId,
1635
+ source,
1636
+ permissionMode: "default",
1637
+ abortController: abortController2
1638
+ })
1639
+ );
1294
1640
  try {
1295
1641
  consumeQuery({
1296
1642
  mode: "disk-resume",
@@ -1371,8 +1717,29 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1371
1717
  error: current ? `Error [${"SESSION_BUSY" /* SESSION_BUSY */}]: Session is not available (status: ${current.status}).` : `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`
1372
1718
  };
1373
1719
  }
1374
- const options = buildOptions(existing);
1720
+ const session = acquired;
1721
+ const options = buildOptions(session);
1375
1722
  if (input.forkSession) options.forkSession = true;
1723
+ if (input.forkSession && !sessionManager.hasCapacityFor(1)) {
1724
+ sessionManager.update(input.sessionId, { status: originalStatus, abortController: void 0 });
1725
+ return {
1726
+ sessionId: input.sessionId,
1727
+ status: "error",
1728
+ error: `Error [${"RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */}]: Too many sessions (limit: ${sessionManager.getMaxSessions()}).`
1729
+ };
1730
+ }
1731
+ const sourceOverrides = {
1732
+ effort: input.effort ?? session.effort,
1733
+ thinking: input.thinking ?? session.thinking
1734
+ };
1735
+ if (input.effort !== void 0) options.effort = input.effort;
1736
+ if (input.thinking !== void 0) options.thinking = input.thinking;
1737
+ if (!input.forkSession && (input.effort !== void 0 || input.thinking !== void 0)) {
1738
+ const patch = {};
1739
+ if (input.effort !== void 0) patch.effort = input.effort;
1740
+ if (input.thinking !== void 0) patch.thinking = input.thinking;
1741
+ sessionManager.update(input.sessionId, patch);
1742
+ }
1376
1743
  try {
1377
1744
  const handle = consumeQuery({
1378
1745
  mode: "resume",
@@ -1388,44 +1755,20 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1388
1755
  onInit: (init) => {
1389
1756
  if (!input.forkSession) return;
1390
1757
  if (init.session_id === input.sessionId) return;
1391
- if (!sessionManager.get(init.session_id)) {
1392
- sessionManager.create({
1393
- sessionId: init.session_id,
1394
- cwd: existing.cwd,
1395
- model: existing.model,
1396
- permissionMode: "default",
1397
- allowedTools: existing.allowedTools,
1398
- disallowedTools: existing.disallowedTools,
1399
- tools: existing.tools,
1400
- maxTurns: existing.maxTurns,
1401
- systemPrompt: existing.systemPrompt,
1402
- agents: existing.agents,
1403
- maxBudgetUsd: existing.maxBudgetUsd,
1404
- effort: existing.effort,
1405
- betas: existing.betas,
1406
- additionalDirectories: existing.additionalDirectories,
1407
- outputFormat: existing.outputFormat,
1408
- thinking: existing.thinking,
1409
- persistSession: existing.persistSession,
1410
- pathToClaudeCodeExecutable: existing.pathToClaudeCodeExecutable,
1411
- agent: existing.agent,
1412
- mcpServers: existing.mcpServers,
1413
- sandbox: existing.sandbox,
1414
- fallbackModel: existing.fallbackModel,
1415
- enableFileCheckpointing: existing.enableFileCheckpointing,
1416
- includePartialMessages: existing.includePartialMessages,
1417
- strictMcpConfig: existing.strictMcpConfig,
1418
- settingSources: existing.settingSources ?? DEFAULT_SETTING_SOURCES,
1419
- debug: existing.debug,
1420
- debugFile: existing.debugFile,
1421
- env: existing.env,
1422
- abortController
1423
- });
1424
- }
1425
1758
  sessionManager.update(input.sessionId, {
1426
1759
  status: originalStatus,
1427
1760
  abortController: void 0
1428
1761
  });
1762
+ if (!sessionManager.get(init.session_id)) {
1763
+ sessionManager.create(
1764
+ toSessionCreateParams({
1765
+ sessionId: init.session_id,
1766
+ source: { ...session, ...sourceOverrides },
1767
+ permissionMode: "default",
1768
+ abortController
1769
+ })
1770
+ );
1771
+ }
1429
1772
  }
1430
1773
  });
1431
1774
  const sessionId = input.forkSession ? await raceWithAbort(
@@ -1478,54 +1821,54 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1478
1821
  // src/tools/tool-discovery.ts
1479
1822
  var TOOL_CATALOG = {
1480
1823
  Bash: {
1481
- description: "Run shell commands (e.g. npm install, git commit, ls) in the project directory.",
1824
+ description: "Run shell commands",
1482
1825
  category: "execute"
1483
1826
  },
1484
1827
  Read: {
1485
- description: "Read the contents of a file given its path (large files may hit per-call size caps; use offset/limit or Grep chunking).",
1828
+ description: "Read file contents (large files: use offset/limit or Grep)",
1486
1829
  category: "file_read"
1487
1830
  },
1488
1831
  Write: {
1489
- description: "Create a new file or completely replace an existing file's contents.",
1832
+ description: "Create or overwrite files",
1490
1833
  category: "file_write"
1491
1834
  },
1492
1835
  Edit: {
1493
- description: "Make targeted changes to specific parts of an existing file without rewriting the whole file (replace_all is substring-based).",
1836
+ description: "Targeted edits (replace_all is substring-based)",
1494
1837
  category: "file_write"
1495
1838
  },
1496
1839
  Glob: {
1497
- description: "Find files by name pattern (e.g. '**/*.ts' finds all TypeScript files).",
1840
+ description: "Find files by glob pattern",
1498
1841
  category: "file_read"
1499
1842
  },
1500
1843
  Grep: {
1501
- description: "Search inside files for text or regex patterns (like grep/ripgrep).",
1844
+ description: "Search file contents (regex)",
1502
1845
  category: "file_read"
1503
1846
  },
1504
1847
  NotebookEdit: {
1505
- description: "Edit individual cells in Jupyter notebooks (.ipynb files) (expects native Windows paths; this server normalizes /d/... when possible).",
1848
+ description: "Edit Jupyter notebook cells (Windows paths normalized)",
1506
1849
  category: "file_write"
1507
1850
  },
1508
1851
  WebFetch: {
1509
- description: "Download and read the content of a web page or API endpoint.",
1852
+ description: "Fetch web page or API content",
1510
1853
  category: "network"
1511
1854
  },
1512
- WebSearch: { description: "Search the web and return relevant results.", category: "network" },
1855
+ WebSearch: { description: "Web search", category: "network" },
1513
1856
  Task: {
1514
- description: "Spawn a subagent to handle a subtask independently (requires this tool to be in allowedTools).",
1857
+ description: "Spawn subagent (must be in allowedTools)",
1515
1858
  category: "agent"
1516
1859
  },
1517
- TaskOutput: { description: "Get the output from a background subagent task.", category: "agent" },
1518
- TaskStop: { description: "Cancel a running background subagent task.", category: "agent" },
1860
+ TaskOutput: { description: "Get subagent output", category: "agent" },
1861
+ TaskStop: { description: "Cancel subagent", category: "agent" },
1519
1862
  TodoWrite: {
1520
- description: "Create and update a structured task/todo checklist.",
1863
+ description: "Task/todo checklist",
1521
1864
  category: "agent"
1522
1865
  },
1523
1866
  AskUserQuestion: {
1524
- description: "Ask the user a question and wait for their answer before continuing.",
1867
+ description: "Ask user a question",
1525
1868
  category: "interaction"
1526
1869
  },
1527
1870
  TeamDelete: {
1528
- description: "Delete a team and its resources (may require all active members to shutdown_approved; cleanup may complete asynchronously).",
1871
+ description: "Delete team (may need shutdown_approved first)",
1529
1872
  category: "agent"
1530
1873
  }
1531
1874
  };
@@ -1545,8 +1888,10 @@ function defaultCatalogTools() {
1545
1888
  }
1546
1889
  var ToolDiscoveryCache = class {
1547
1890
  cached;
1548
- constructor(initial) {
1891
+ onUpdated;
1892
+ constructor(initial, onUpdated) {
1549
1893
  this.cached = initial ?? defaultCatalogTools();
1894
+ this.onUpdated = onUpdated;
1550
1895
  }
1551
1896
  getTools() {
1552
1897
  return this.cached;
@@ -1555,7 +1900,13 @@ var ToolDiscoveryCache = class {
1555
1900
  const discovered = discoverToolsFromInit(initTools);
1556
1901
  const next = mergeToolLists(discovered, defaultCatalogTools());
1557
1902
  const updated = JSON.stringify(next) !== JSON.stringify(this.cached);
1558
- if (updated) this.cached = next;
1903
+ if (updated) {
1904
+ this.cached = next;
1905
+ try {
1906
+ this.onUpdated?.(this.cached);
1907
+ } catch {
1908
+ }
1909
+ }
1559
1910
  return { updated, tools: this.cached };
1560
1911
  }
1561
1912
  };
@@ -1580,9 +1931,8 @@ function groupByCategory(tools) {
1580
1931
  function buildInternalToolsDescription(tools) {
1581
1932
  const grouped = groupByCategory(tools);
1582
1933
  const categories = Object.keys(grouped).sort((a, b) => a.localeCompare(b));
1583
- let desc = 'Start a new Claude Code agent session.\n\nLaunches an autonomous coding agent that can read/write files, run shell commands, search code, manage git, access the web, and more. Returns immediately with a sessionId \u2014 the agent runs asynchronously in the background.\n\nWorkflow:\n1. Call claude_code with a prompt \u2192 returns { sessionId, status: "running", pollInterval }\n2. Poll with claude_code_check (action="poll") to receive progress events and the final result\n3. If the agent needs permission for a tool call, approve or deny via claude_code_check (action="respond_permission")\n\n';
1584
- desc += "Defaults:\n- settingSources: ['user', 'project', 'local'] (loads ~/.claude/settings.json, .claude/settings.json, .claude/settings.local.json, and CLAUDE.md)\n- persistSession: true\n- sessionInitTimeoutMs: 10000\n- permissionRequestTimeoutMs: 60000\n- allowedTools/disallowedTools: [] (none)\n- resumeToken: omitted unless CLAUDE_CODE_MCP_RESUME_SECRET is set on the server\n- Permission prompts auto-deny on timeout; use claude_code_check actions[].expiresAt/remainingMs\n\n";
1585
- desc += "Internal tools available to the agent (use allowedTools/disallowedTools to control approval policy; authoritative list returned by claude_code_check with includeTools=true):\n";
1934
+ let desc = "Start a Claude Code session and return sessionId.\nUse claude_code_check to poll events/results and handle permissions.\n\n";
1935
+ desc += "Internal tools (authoritative list: includeTools=true in claude_code_check):\n";
1586
1936
  for (const category of categories) {
1587
1937
  desc += `
1588
1938
  [${category}]
@@ -1592,8 +1942,7 @@ function buildInternalToolsDescription(tools) {
1592
1942
  `;
1593
1943
  }
1594
1944
  }
1595
- desc += "\nSecurity: You MUST configure allowedTools/disallowedTools based on your own permission scope. Only allow tools that you yourself are authorized to perform \u2014 do not grant the agent broader permissions than you have. For example, if you lack write access to a directory, do not include Write/Edit in allowedTools. When in doubt, leave both lists empty and review each permission request individually via claude_code_check.\n\n";
1596
- desc += 'Use `allowedTools` to pre-approve tools (no permission prompts). Use `disallowedTools` to permanently block specific tools. Any tool not in either list will pause the session (status: "waiting_permission") until approved or denied via claude_code_check.\n';
1945
+ desc += "\nPermission control: allowedTools auto-approves; disallowedTools always denies; others require approval.\n";
1597
1946
  return desc;
1598
1947
  }
1599
1948
 
@@ -1652,6 +2001,117 @@ function toEvents(events, opts) {
1652
2001
  return { id: e.id, type: e.type, data: e.data, timestamp: e.timestamp };
1653
2002
  });
1654
2003
  }
2004
+ function uniqSorted(values) {
2005
+ if (!Array.isArray(values) || values.length === 0) return [];
2006
+ const filtered = values.filter((v) => typeof v === "string").map((v) => v.trim()).filter(Boolean);
2007
+ return Array.from(new Set(filtered)).sort((a, b) => a.localeCompare(b));
2008
+ }
2009
+ function detectPathCompatibilityWarnings(session) {
2010
+ if (!session) return [];
2011
+ const cwd = session.cwd;
2012
+ if (typeof cwd !== "string" || cwd.trim() === "") return [];
2013
+ const warnings = [];
2014
+ if (process.platform === "win32" && cwd.startsWith("/") && !cwd.startsWith("//")) {
2015
+ warnings.push(
2016
+ `cwd '${cwd}' looks POSIX-style on Windows. Consider using a Windows path (e.g. C:\\\\repo) to avoid path compatibility issues.`
2017
+ );
2018
+ }
2019
+ if (process.platform !== "win32" && /^[a-zA-Z]:[\\/]/.test(cwd)) {
2020
+ warnings.push(
2021
+ `cwd '${cwd}' looks Windows-style on ${process.platform}. This may indicate cross-platform path mismatch (for example WSL boundary).`
2022
+ );
2023
+ }
2024
+ if (process.platform !== "win32" && cwd.startsWith("\\\\")) {
2025
+ warnings.push(
2026
+ `cwd '${cwd}' looks like a Windows UNC path on ${process.platform}. Confirm MCP client/server run on the same platform.`
2027
+ );
2028
+ }
2029
+ return warnings;
2030
+ }
2031
+ function isPosixHomePath(value) {
2032
+ return /^\/home\/[^/]+(?:\/|$)/.test(value);
2033
+ }
2034
+ function detectPendingPermissionPathWarnings(pending, session) {
2035
+ if (process.platform !== "win32" || pending.length === 0) return [];
2036
+ const cwd = session?.cwd;
2037
+ const cwdHint = typeof cwd === "string" && cwd.trim() !== "" ? ` under cwd '${cwd}'` : " under the current cwd";
2038
+ const warnings = [];
2039
+ for (const req of pending) {
2040
+ const candidates = [];
2041
+ if (typeof req.blockedPath === "string") candidates.push(req.blockedPath);
2042
+ const filePath = req.input.file_path;
2043
+ if (typeof filePath === "string") candidates.push(filePath);
2044
+ const badPath = candidates.find((p) => isPosixHomePath(p));
2045
+ if (!badPath) continue;
2046
+ warnings.push(
2047
+ `Permission request '${req.requestId}' uses POSIX home path '${badPath}' on Windows. Prefer an absolute Windows path${cwdHint} to avoid out-of-bounds permission prompts.`
2048
+ );
2049
+ }
2050
+ return warnings;
2051
+ }
2052
+ function computeToolValidation(session, initTools) {
2053
+ if (!session) return { summary: void 0, warnings: [] };
2054
+ const allowedTools = uniqSorted(session.allowedTools);
2055
+ const disallowedTools = uniqSorted(session.disallowedTools);
2056
+ if (allowedTools.length === 0 && disallowedTools.length === 0) {
2057
+ return { summary: void 0, warnings: [] };
2058
+ }
2059
+ if (!Array.isArray(initTools) || initTools.length === 0) {
2060
+ return {
2061
+ summary: {
2062
+ runtimeToolsKnown: false,
2063
+ unknownAllowedTools: [],
2064
+ unknownDisallowedTools: []
2065
+ },
2066
+ warnings: [
2067
+ "Runtime tool list is not available yet; unknown allowedTools/disallowedTools names cannot be validated until system/init tools arrive."
2068
+ ]
2069
+ };
2070
+ }
2071
+ const runtime = new Set(
2072
+ initTools.filter((name) => typeof name === "string").map((name) => name.trim()).filter(Boolean)
2073
+ );
2074
+ const unknownAllowedTools = allowedTools.filter((name) => !runtime.has(name));
2075
+ const unknownDisallowedTools = disallowedTools.filter((name) => !runtime.has(name));
2076
+ const warnings = [];
2077
+ if (unknownAllowedTools.length > 0) {
2078
+ warnings.push(
2079
+ `Unknown allowedTools (not present in runtime tools): ${unknownAllowedTools.join(", ")}.`
2080
+ );
2081
+ }
2082
+ if (unknownDisallowedTools.length > 0) {
2083
+ warnings.push(
2084
+ `Unknown disallowedTools (not present in runtime tools): ${unknownDisallowedTools.join(", ")}.`
2085
+ );
2086
+ }
2087
+ return {
2088
+ summary: {
2089
+ runtimeToolsKnown: true,
2090
+ unknownAllowedTools,
2091
+ unknownDisallowedTools
2092
+ },
2093
+ warnings
2094
+ };
2095
+ }
2096
+ function byteLength(value) {
2097
+ return new TextEncoder().encode(JSON.stringify(value)).length;
2098
+ }
2099
+ function capEventsByBytes(events, maxBytes) {
2100
+ if (!Number.isFinite(maxBytes) || typeof maxBytes !== "number" || maxBytes <= 0) {
2101
+ return { events, truncated: false };
2102
+ }
2103
+ const budget = Math.floor(maxBytes);
2104
+ const kept = [];
2105
+ let bytes = 2;
2106
+ for (const evt of events) {
2107
+ const evtBytes = byteLength(evt);
2108
+ const separatorBytes = kept.length === 0 ? 0 : 1;
2109
+ if (bytes + separatorBytes + evtBytes > budget) break;
2110
+ kept.push(evt);
2111
+ bytes += separatorBytes + evtBytes;
2112
+ }
2113
+ return { events: kept, truncated: kept.length < events.length };
2114
+ }
1655
2115
  function buildResult(sessionManager, toolCache, input) {
1656
2116
  const responseMode = input.responseMode ?? "minimal";
1657
2117
  const po = input.pollOptions ?? {};
@@ -1664,6 +2124,7 @@ function buildResult(sessionManager, toolCache, input) {
1664
2124
  const includeStructuredOutput = po.includeStructuredOutput ?? responseMode === "full";
1665
2125
  const includeTerminalEvents = po.includeTerminalEvents ?? responseMode === "full";
1666
2126
  const includeProgressEvents = po.includeProgressEvents ?? responseMode === "full";
2127
+ const maxBytes = po.maxBytes;
1667
2128
  const maxEvents = input.maxEvents ?? (responseMode === "minimal" ? 200 : void 0);
1668
2129
  const sessionId = input.sessionId;
1669
2130
  const session = sessionManager.get(sessionId);
@@ -1676,7 +2137,7 @@ function buildResult(sessionManager, toolCache, input) {
1676
2137
  let truncated = false;
1677
2138
  const truncatedFields = [];
1678
2139
  const windowEvents = maxEvents !== void 0 && rawEvents.length > maxEvents ? rawEvents.slice(0, maxEvents) : rawEvents;
1679
- const nextCursor = maxEvents !== void 0 && rawEvents.length > maxEvents ? windowEvents.length > 0 ? windowEvents[windowEvents.length - 1].id + 1 : rawNextCursor : rawNextCursor;
2140
+ let nextCursor = maxEvents !== void 0 && rawEvents.length > maxEvents ? windowEvents.length > 0 ? windowEvents[windowEvents.length - 1].id + 1 : rawNextCursor : rawNextCursor;
1680
2141
  if (maxEvents !== void 0 && rawEvents.length > maxEvents) {
1681
2142
  truncated = true;
1682
2143
  truncatedFields.push("events");
@@ -1699,8 +2160,28 @@ function buildResult(sessionManager, toolCache, input) {
1699
2160
  })();
1700
2161
  const pending = status === "waiting_permission" ? sessionManager.listPendingPermissions(sessionId) : [];
1701
2162
  const stored = status === "idle" || status === "error" ? sessionManager.getResult(sessionId) : void 0;
1702
- const initTools = includeTools ? sessionManager.getInitTools(sessionId) : void 0;
2163
+ const initTools = sessionManager.getInitTools(sessionId);
1703
2164
  const availableTools = includeTools && initTools ? discoverToolsFromInit(initTools) : void 0;
2165
+ const toolValidation = computeToolValidation(session, initTools);
2166
+ const compatWarnings = Array.from(
2167
+ /* @__PURE__ */ new Set([
2168
+ ...toolValidation.warnings,
2169
+ ...detectPathCompatibilityWarnings(session),
2170
+ ...detectPendingPermissionPathWarnings(pending, session)
2171
+ ])
2172
+ );
2173
+ const shapedEvents = toEvents(outputEvents, {
2174
+ includeUsage,
2175
+ includeModelUsage,
2176
+ includeStructuredOutput,
2177
+ slim: responseMode === "minimal"
2178
+ });
2179
+ const cappedEvents = capEventsByBytes(shapedEvents, maxBytes);
2180
+ if (cappedEvents.truncated) {
2181
+ truncated = true;
2182
+ truncatedFields.push("events_bytes");
2183
+ nextCursor = cappedEvents.events.length > 0 ? cappedEvents.events[cappedEvents.events.length - 1].id + 1 : cursorResetTo ?? input.cursor ?? 0;
2184
+ }
1704
2185
  return {
1705
2186
  sessionId,
1706
2187
  status,
@@ -1708,14 +2189,11 @@ function buildResult(sessionManager, toolCache, input) {
1708
2189
  cursorResetTo,
1709
2190
  truncated: truncated ? true : void 0,
1710
2191
  truncatedFields: truncatedFields.length > 0 ? truncatedFields : void 0,
1711
- events: toEvents(outputEvents, {
1712
- includeUsage,
1713
- includeModelUsage,
1714
- includeStructuredOutput,
1715
- slim: responseMode === "minimal"
1716
- }),
2192
+ events: cappedEvents.events,
1717
2193
  nextCursor,
1718
2194
  availableTools,
2195
+ toolValidation: toolValidation.summary,
2196
+ compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0,
1719
2197
  actions: includeActions && status === "waiting_permission" ? pending.map((req) => {
1720
2198
  const expiresMs = req.expiresAt ? Date.parse(req.expiresAt) : Number.NaN;
1721
2199
  const remainingMs = Number.isFinite(expiresMs) ? Math.max(0, expiresMs - Date.now()) : void 0;
@@ -1775,7 +2253,14 @@ function redactAgentResult(result, opts) {
1775
2253
  structuredOutput: opts.includeStructuredOutput ? structuredOutput : void 0
1776
2254
  };
1777
2255
  }
1778
- function executeClaudeCodeCheck(input, sessionManager, toolCache) {
2256
+ function executeClaudeCodeCheck(input, sessionManager, toolCache, requestSignal) {
2257
+ if (requestSignal?.aborted) {
2258
+ return {
2259
+ sessionId: input.sessionId ?? "",
2260
+ error: `Error [${"CANCELLED" /* CANCELLED */}]: request was cancelled.`,
2261
+ isError: true
2262
+ };
2263
+ }
1779
2264
  if (typeof input.sessionId !== "string" || input.sessionId.trim() === "") {
1780
2265
  return {
1781
2266
  sessionId: "",
@@ -1831,7 +2316,14 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache) {
1831
2316
  }
1832
2317
 
1833
2318
  // src/tools/claude-code-session.ts
1834
- function executeClaudeCodeSession(input, sessionManager) {
2319
+ function executeClaudeCodeSession(input, sessionManager, requestSignal) {
2320
+ if (requestSignal?.aborted) {
2321
+ return {
2322
+ sessions: [],
2323
+ message: `Error [${"CANCELLED" /* CANCELLED */}]: request was cancelled.`,
2324
+ isError: true
2325
+ };
2326
+ }
1835
2327
  const toSessionJson = (s) => input.includeSensitive ? sessionManager.toSensitiveJSON(s) : sessionManager.toPublicJSON(s);
1836
2328
  switch (input.action) {
1837
2329
  case "list": {
@@ -1903,7 +2395,8 @@ var RESOURCE_SCHEME = "claude-code-mcp";
1903
2395
  var RESOURCE_URIS = {
1904
2396
  serverInfo: `${RESOURCE_SCHEME}:///server-info`,
1905
2397
  internalTools: `${RESOURCE_SCHEME}:///internal-tools`,
1906
- gotchas: `${RESOURCE_SCHEME}:///gotchas`
2398
+ gotchas: `${RESOURCE_SCHEME}:///gotchas`,
2399
+ compatReport: `${RESOURCE_SCHEME}:///compat-report`
1907
2400
  };
1908
2401
  function asTextResource(uri, text, mimeType) {
1909
2402
  return {
@@ -1923,7 +2416,7 @@ function registerResources(server, deps) {
1923
2416
  serverInfoUri.toString(),
1924
2417
  {
1925
2418
  title: "Server Info",
1926
- description: "Static server metadata (version/platform/runtime).",
2419
+ description: "Server metadata (version/platform/runtime).",
1927
2420
  mimeType: "application/json"
1928
2421
  },
1929
2422
  () => asTextResource(
@@ -1949,7 +2442,7 @@ function registerResources(server, deps) {
1949
2442
  toolsUri.toString(),
1950
2443
  {
1951
2444
  title: "Internal Tools",
1952
- description: "Claude Code internal tool catalog (static + runtime-discovered).",
2445
+ description: "Claude Code internal tool catalog (runtime-aware).",
1953
2446
  mimeType: "application/json"
1954
2447
  },
1955
2448
  () => asTextResource(
@@ -1972,10 +2465,10 @@ function registerResources(server, deps) {
1972
2465
  [
1973
2466
  "# claude-code-mcp: gotchas",
1974
2467
  "",
1975
- "- Permission approvals have a timeout (default 60s) and auto-deny (`actions[].expiresAt`/`remainingMs`).",
2468
+ "- Permission approvals have a timeout (default 60s, server-clamped to 5min) and auto-deny (`actions[].expiresAt`/`remainingMs`).",
1976
2469
  "- `Read` has a per-call size cap in practice (often ~256KB); for large files use `offset`/`limit` or chunk with `Grep`.",
1977
2470
  "- `Edit` with `replace_all=true` is substring replacement; if no match is found the tool returns an error.",
1978
- "- `NotebookEdit` expects native Windows paths; this server normalizes MSYS paths like `/d/...` when possible.",
2471
+ "- On Windows, this server normalizes common MSYS-style paths (e.g. `/d/...`, `/mnt/c/...`, `/cygdrive/c/...`, `//server/share/...`) for `cwd`, `additionalDirectories`, and tool inputs that include `file_path`.",
1979
2472
  "- `TeamDelete` may require members to reach `shutdown_approved`; cleanup can be asynchronous during shutdown.",
1980
2473
  '- Skills may become available later in the same session (early calls may show "Unknown").',
1981
2474
  "- Some internal features (e.g. ToolSearch) may not appear in `availableTools` because it is derived from SDK `system/init.tools`.",
@@ -1984,34 +2477,105 @@ function registerResources(server, deps) {
1984
2477
  "text/markdown"
1985
2478
  )
1986
2479
  );
2480
+ const compatReportUri = new URL(RESOURCE_URIS.compatReport);
2481
+ server.registerResource(
2482
+ "compat_report",
2483
+ compatReportUri.toString(),
2484
+ {
2485
+ title: "Compatibility Report",
2486
+ description: "Compatibility diagnostics for MCP clients and local runtime assumptions.",
2487
+ mimeType: "application/json"
2488
+ },
2489
+ () => {
2490
+ const runtimeWarnings = [];
2491
+ if (process.platform === "win32" && !process.env.CLAUDE_CODE_GIT_BASH_PATH) {
2492
+ runtimeWarnings.push(
2493
+ "CLAUDE_CODE_GIT_BASH_PATH is not set. Auto-detection exists, but explicit path is more reliable for GUI-launched MCP clients."
2494
+ );
2495
+ }
2496
+ return asTextResource(
2497
+ compatReportUri,
2498
+ JSON.stringify(
2499
+ {
2500
+ transport: "stdio",
2501
+ samePlatformRequired: true,
2502
+ runtime: {
2503
+ node: process.version,
2504
+ platform: process.platform,
2505
+ arch: process.arch
2506
+ },
2507
+ features: {
2508
+ resources: true,
2509
+ toolsListChanged: true,
2510
+ resourcesListChanged: true,
2511
+ prompts: false,
2512
+ completions: false
2513
+ },
2514
+ guidance: [
2515
+ "Some clients cache tool descriptions at connect time. Prefer claude_code_check(pollOptions.includeTools=true) for runtime-authoritative tool lists.",
2516
+ "Use allowedTools/disallowedTools only with exact runtime tool names.",
2517
+ "This server assumes MCP client and server run on the same machine/platform."
2518
+ ],
2519
+ toolCatalogCount: deps.toolCache.getTools().length,
2520
+ runtimeWarnings
2521
+ },
2522
+ null,
2523
+ 2
2524
+ ),
2525
+ "application/json"
2526
+ );
2527
+ }
2528
+ );
1987
2529
  }
1988
2530
 
1989
2531
  // src/server.ts
1990
- var SERVER_VERSION = true ? "2.0.3" : "0.0.0-dev";
1991
- function createServer(serverCwd) {
2532
+ var SERVER_VERSION = true ? "2.2.0" : "0.0.0-dev";
2533
+ function createServerContext(serverCwd) {
1992
2534
  const sessionManager = new SessionManager();
1993
- const toolCache = new ToolDiscoveryCache();
1994
- const server = new McpServer({
1995
- name: "claude-code-mcp",
1996
- version: SERVER_VERSION
2535
+ const server = new McpServer(
2536
+ {
2537
+ name: "claude-code-mcp",
2538
+ version: SERVER_VERSION,
2539
+ title: "Claude Code MCP",
2540
+ description: "MCP server that runs Claude Code via the Claude Agent SDK with async polling and interactive permissions.",
2541
+ websiteUrl: "https://github.com/xihuai18/claude-code-mcp",
2542
+ icons: []
2543
+ },
2544
+ {
2545
+ capabilities: {
2546
+ logging: {},
2547
+ tools: { listChanged: true },
2548
+ resources: { listChanged: true }
2549
+ }
2550
+ }
2551
+ );
2552
+ const claudeCodeToolRef = {};
2553
+ const toolCache = new ToolDiscoveryCache(void 0, (tools) => {
2554
+ try {
2555
+ claudeCodeToolRef.current?.update({ description: buildInternalToolsDescription(tools) });
2556
+ if (server.isConnected()) {
2557
+ server.sendToolListChanged();
2558
+ }
2559
+ } catch {
2560
+ }
1997
2561
  });
1998
2562
  const agentDefinitionSchema = z.object({
1999
2563
  description: z.string(),
2000
2564
  prompt: z.string(),
2001
- tools: z.array(z.string()).optional(),
2002
- disallowedTools: z.array(z.string()).optional(),
2003
- model: z.enum(AGENT_MODELS).optional(),
2004
- maxTurns: z.number().int().positive().optional(),
2005
- mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional(),
2006
- skills: z.array(z.string()).optional(),
2007
- criticalSystemReminder_EXPERIMENTAL: z.string().optional()
2565
+ tools: z.array(z.string()).optional().describe("Default: inherit"),
2566
+ disallowedTools: z.array(z.string()).optional().describe("Default: none"),
2567
+ model: z.enum(AGENT_MODELS).optional().describe("Default: inherit"),
2568
+ maxTurns: z.number().int().positive().optional().describe("Default: none"),
2569
+ mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional().describe("Default: inherit"),
2570
+ skills: z.array(z.string()).optional().describe("Default: none"),
2571
+ criticalSystemReminder_EXPERIMENTAL: z.string().optional().describe("Default: none")
2008
2572
  });
2009
2573
  const systemPromptSchema = z.union([
2010
2574
  z.string(),
2011
2575
  z.object({
2012
2576
  type: z.literal("preset"),
2013
2577
  preset: z.literal("claude_code"),
2014
- append: z.string().optional().describe("Appended to preset prompt")
2578
+ append: z.string().optional().describe("Default: none")
2015
2579
  })
2016
2580
  ]);
2017
2581
  const toolsConfigSchema = z.union([
@@ -2029,47 +2593,135 @@ function createServer(serverCwd) {
2029
2593
  }),
2030
2594
  z.object({ type: z.literal("disabled") })
2031
2595
  ]);
2596
+ const effortOptionSchema = z.enum(EFFORT_LEVELS).optional();
2597
+ const thinkingOptionSchema = thinkingSchema.optional();
2032
2598
  const outputFormatSchema = z.object({
2033
2599
  type: z.literal("json_schema"),
2034
2600
  schema: z.record(z.string(), z.unknown())
2035
2601
  });
2036
- const advancedOptionsSchema = z.object({
2037
- tools: toolsConfigSchema.optional().describe("Visible tool set. Default: SDK"),
2602
+ const sharedOptionFieldsSchemaShape = {
2603
+ tools: toolsConfigSchema.optional().describe("Tool set. Default: SDK"),
2038
2604
  persistSession: z.boolean().optional().describe("Default: true"),
2039
- sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000"),
2040
- agents: z.record(z.string(), agentDefinitionSchema).optional().describe("Sub-agent definitions. Default: none"),
2041
- agent: z.string().optional().describe("Primary agent name (from 'agents'). Default: none"),
2605
+ agents: z.record(z.string(), agentDefinitionSchema).optional().describe("Default: none"),
2606
+ agent: z.string().optional().describe("Default: none"),
2042
2607
  maxBudgetUsd: z.number().positive().optional().describe("Default: none"),
2043
- effort: z.enum(EFFORT_LEVELS).optional().describe("Default: SDK"),
2044
2608
  betas: z.array(z.string()).optional().describe("Default: none"),
2045
2609
  additionalDirectories: z.array(z.string()).optional().describe("Default: none"),
2046
- outputFormat: outputFormatSchema.optional().describe("Default: none (plain text)"),
2047
- thinking: thinkingSchema.optional().describe("Default: SDK"),
2610
+ outputFormat: outputFormatSchema.optional().describe("Default: none"),
2048
2611
  pathToClaudeCodeExecutable: z.string().optional().describe("Default: SDK-bundled"),
2049
2612
  mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("Default: none"),
2050
2613
  sandbox: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
2051
2614
  fallbackModel: z.string().optional().describe("Default: none"),
2052
2615
  enableFileCheckpointing: z.boolean().optional().describe("Default: false"),
2053
- includePartialMessages: z.boolean().optional().describe("Stream events to claude_code_check. Default: false"),
2616
+ includePartialMessages: z.boolean().optional().describe("Default: false"),
2054
2617
  strictMcpConfig: z.boolean().optional().describe("Default: false"),
2055
- settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. [] = isolation mode"),
2618
+ settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. []=isolation"),
2056
2619
  debug: z.boolean().optional().describe("Default: false"),
2057
- debugFile: z.string().optional().describe("Enables debug. Default: none"),
2058
- env: z.record(z.string(), z.string().optional()).optional().describe("Merged with process.env. Default: none")
2059
- }).optional().describe("Low-frequency SDK options (all optional)");
2060
- server.tool(
2620
+ debugFile: z.string().optional().describe("Default: none"),
2621
+ env: z.record(z.string(), z.string().optional()).optional().describe("Default: none")
2622
+ };
2623
+ const advancedOptionFieldsSchemaShape = {
2624
+ ...sharedOptionFieldsSchemaShape,
2625
+ effort: effortOptionSchema.describe("Deprecated, use top-level. Default: SDK"),
2626
+ thinking: thinkingOptionSchema.describe("Deprecated, use top-level. Default: SDK")
2627
+ };
2628
+ const diskResumeOptionFieldsSchemaShape = {
2629
+ ...sharedOptionFieldsSchemaShape,
2630
+ effort: effortOptionSchema.describe("Default: SDK"),
2631
+ thinking: thinkingOptionSchema.describe("Default: SDK")
2632
+ };
2633
+ const advancedOptionsSchema = z.object({
2634
+ ...advancedOptionFieldsSchemaShape,
2635
+ sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000")
2636
+ }).optional().describe("Default: none");
2637
+ const diskResumeConfigSchema = z.object({
2638
+ resumeToken: z.string(),
2639
+ cwd: z.string(),
2640
+ allowedTools: z.array(z.string()).optional().describe("Default: []"),
2641
+ disallowedTools: z.array(z.string()).optional().describe("Default: []"),
2642
+ maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
2643
+ model: z.string().optional().describe("Default: SDK"),
2644
+ systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
2645
+ resumeSessionAt: z.string().optional().describe("Default: none"),
2646
+ ...diskResumeOptionFieldsSchemaShape
2647
+ }).optional().describe("Default: none");
2648
+ const startResultSchema = z.object({
2649
+ sessionId: z.string(),
2650
+ status: z.enum(["running", "error"]),
2651
+ pollInterval: z.number().optional(),
2652
+ resumeToken: z.string().optional(),
2653
+ compatWarnings: z.array(z.string()).optional(),
2654
+ error: z.string().optional()
2655
+ }).passthrough();
2656
+ const sessionResultSchema = z.object({
2657
+ sessions: z.array(z.record(z.string(), z.unknown())),
2658
+ message: z.string().optional(),
2659
+ isError: z.boolean().optional()
2660
+ }).passthrough();
2661
+ const checkEventSchema = z.object({
2662
+ id: z.number().int().nonnegative(),
2663
+ type: z.string(),
2664
+ data: z.unknown(),
2665
+ timestamp: z.string()
2666
+ }).passthrough();
2667
+ const checkActionSchema = z.object({
2668
+ type: z.string(),
2669
+ requestId: z.string().optional(),
2670
+ toolName: z.string().optional(),
2671
+ input: z.record(z.string(), z.unknown()).optional(),
2672
+ summary: z.string().optional()
2673
+ }).passthrough();
2674
+ const checkResultSchema = z.object({
2675
+ sessionId: z.string(),
2676
+ status: z.string(),
2677
+ pollInterval: z.number().optional(),
2678
+ cursorResetTo: z.number().optional(),
2679
+ truncated: z.boolean().optional(),
2680
+ truncatedFields: z.array(z.string()).optional(),
2681
+ events: z.array(checkEventSchema),
2682
+ nextCursor: z.number().optional(),
2683
+ availableTools: z.array(z.record(z.string(), z.unknown())).optional(),
2684
+ toolValidation: z.object({
2685
+ runtimeToolsKnown: z.boolean(),
2686
+ unknownAllowedTools: z.array(z.string()),
2687
+ unknownDisallowedTools: z.array(z.string())
2688
+ }).optional(),
2689
+ compatWarnings: z.array(z.string()).optional(),
2690
+ actions: z.array(checkActionSchema).optional(),
2691
+ result: z.unknown().optional(),
2692
+ cancelledAt: z.string().optional(),
2693
+ cancelledReason: z.string().optional(),
2694
+ cancelledSource: z.string().optional(),
2695
+ lastEventId: z.number().optional(),
2696
+ lastToolUseId: z.string().optional(),
2697
+ isError: z.boolean().optional(),
2698
+ error: z.string().optional()
2699
+ }).passthrough();
2700
+ claudeCodeToolRef.current = server.registerTool(
2061
2701
  "claude_code",
2062
- buildInternalToolsDescription(toolCache.getTools()),
2063
2702
  {
2064
- prompt: z.string().describe("Task or question"),
2065
- cwd: z.string().optional().describe("Working directory. Default: server cwd"),
2066
- allowedTools: z.array(z.string()).optional().describe("Auto-approved tools, e.g. ['Bash','Read','Write','Edit']. Default: []"),
2067
- disallowedTools: z.array(z.string()).optional().describe("Forbidden tools (priority over allowedTools). Default: []"),
2068
- maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
2069
- model: z.string().optional().describe("e.g. 'opus'. Default: SDK"),
2070
- systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
2071
- permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Auto-deny timeout (ms). Default: 60000"),
2072
- advanced: advancedOptionsSchema
2703
+ description: buildInternalToolsDescription(toolCache.getTools()),
2704
+ inputSchema: {
2705
+ prompt: z.string().describe("Prompt"),
2706
+ cwd: z.string().optional().describe("Working dir. Default: server cwd"),
2707
+ allowedTools: z.array(z.string()).optional().describe("Default: []"),
2708
+ disallowedTools: z.array(z.string()).optional().describe("Default: []"),
2709
+ maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
2710
+ model: z.string().optional().describe("Default: SDK"),
2711
+ effort: effortOptionSchema.describe("Default: SDK"),
2712
+ thinking: thinkingOptionSchema.describe("Default: SDK"),
2713
+ systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
2714
+ permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000"),
2715
+ sessionInitTimeoutMs: z.number().int().positive().optional().describe("Deprecated, use advanced.sessionInitTimeoutMs. Default: 10000"),
2716
+ advanced: advancedOptionsSchema
2717
+ },
2718
+ outputSchema: startResultSchema,
2719
+ annotations: {
2720
+ readOnlyHint: false,
2721
+ destructiveHint: true,
2722
+ idempotentHint: false,
2723
+ openWorldHint: true
2724
+ }
2073
2725
  },
2074
2726
  async (args, extra) => {
2075
2727
  try {
@@ -2088,76 +2740,50 @@ function createServer(serverCwd) {
2088
2740
  text: JSON.stringify(result, null, 2)
2089
2741
  }
2090
2742
  ],
2743
+ structuredContent: result,
2091
2744
  isError
2092
2745
  };
2093
2746
  } catch (err) {
2094
2747
  const message = err instanceof Error ? err.message : String(err);
2748
+ const errorResult = {
2749
+ sessionId: "",
2750
+ status: "error",
2751
+ error: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
2752
+ };
2095
2753
  return {
2096
2754
  content: [
2097
2755
  {
2098
2756
  type: "text",
2099
- text: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
2757
+ text: JSON.stringify(errorResult, null, 2)
2100
2758
  }
2101
2759
  ],
2760
+ structuredContent: errorResult,
2102
2761
  isError: true
2103
2762
  };
2104
2763
  }
2105
2764
  }
2106
2765
  );
2107
- server.tool(
2766
+ server.registerTool(
2108
2767
  "claude_code_reply",
2109
- `Send a follow-up message to an existing Claude Code session.
2110
-
2111
- The agent retains full context from previous turns (files read, code analyzed, conversation history). Returns immediately \u2014 use claude_code_check to poll for the result.
2112
-
2113
- Supports session forking (forkSession=true) to explore alternative approaches without modifying the original session.
2114
-
2115
- Defaults:
2116
- - forkSession: false
2117
- - sessionInitTimeoutMs: 10000 (only used when forkSession=true)
2118
- - permissionRequestTimeoutMs: 60000
2119
- - Disk resume: disabled unless CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1
2120
-
2121
- Disk resume: If the server restarted and the session is no longer in memory, set CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1 to let the agent resume from its on-disk transcript. Pass diskResumeConfig with resumeToken and session parameters.`,
2122
2768
  {
2123
- sessionId: z.string().describe("Session ID from claude_code"),
2124
- prompt: z.string().describe("Follow-up message"),
2125
- forkSession: z.boolean().optional().describe("Branch into new session copy. Default: false"),
2126
- sessionInitTimeoutMs: z.number().int().positive().optional().describe("Fork init timeout (ms). Default: 10000"),
2127
- permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Auto-deny timeout (ms). Default: 60000"),
2128
- diskResumeConfig: z.object({
2129
- resumeToken: z.string().optional().describe("Required"),
2130
- cwd: z.string().optional().describe("Required"),
2131
- allowedTools: z.array(z.string()).optional().describe("Default: []"),
2132
- disallowedTools: z.array(z.string()).optional().describe("Default: []"),
2133
- tools: toolsConfigSchema.optional().describe("Default: SDK"),
2134
- persistSession: z.boolean().optional().describe("Default: true"),
2135
- maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
2136
- model: z.string().optional().describe("Default: SDK"),
2137
- systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
2138
- agents: z.record(z.string(), agentDefinitionSchema).optional().describe("Default: none"),
2139
- agent: z.string().optional().describe("Default: none"),
2140
- maxBudgetUsd: z.number().positive().optional().describe("Default: none"),
2141
- effort: z.enum(EFFORT_LEVELS).optional().describe("Default: SDK"),
2142
- betas: z.array(z.string()).optional().describe("Default: none"),
2143
- additionalDirectories: z.array(z.string()).optional().describe("Default: none"),
2144
- outputFormat: outputFormatSchema.optional().describe("Default: none"),
2145
- thinking: thinkingSchema.optional().describe("Default: SDK"),
2146
- resumeSessionAt: z.string().optional().describe("Resume to specific message UUID. Default: none"),
2147
- pathToClaudeCodeExecutable: z.string().optional().describe("Default: SDK-bundled"),
2148
- mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("Default: none"),
2149
- sandbox: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
2150
- fallbackModel: z.string().optional().describe("Default: none"),
2151
- enableFileCheckpointing: z.boolean().optional().describe("Default: false"),
2152
- includePartialMessages: z.boolean().optional().describe("Stream events to claude_code_check. Default: false"),
2153
- strictMcpConfig: z.boolean().optional().describe("Default: false"),
2154
- settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. [] = isolation mode"),
2155
- debug: z.boolean().optional().describe("Default: false"),
2156
- debugFile: z.string().optional().describe("Enables debug. Default: none"),
2157
- env: z.record(z.string(), z.string().optional()).optional().describe("Default: none")
2158
- }).optional().describe(
2159
- "Disk resume config (needs CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1). Requires resumeToken + cwd."
2160
- )
2769
+ description: "Send a follow-up to an existing session. Returns immediately; use claude_code_check to poll.",
2770
+ inputSchema: {
2771
+ sessionId: z.string().describe("Session ID"),
2772
+ prompt: z.string().describe("Prompt"),
2773
+ forkSession: z.boolean().optional().describe("Default: false"),
2774
+ effort: effortOptionSchema.describe("Default: SDK"),
2775
+ thinking: thinkingOptionSchema.describe("Default: SDK"),
2776
+ sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000"),
2777
+ permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000"),
2778
+ diskResumeConfig: diskResumeConfigSchema
2779
+ },
2780
+ outputSchema: startResultSchema,
2781
+ annotations: {
2782
+ readOnlyHint: false,
2783
+ destructiveHint: true,
2784
+ idempotentHint: false,
2785
+ openWorldHint: true
2786
+ }
2161
2787
  },
2162
2788
  async (args, extra) => {
2163
2789
  try {
@@ -2170,137 +2796,339 @@ Disk resume: If the server restarted and the session is no longer in memory, set
2170
2796
  text: JSON.stringify(result, null, 2)
2171
2797
  }
2172
2798
  ],
2799
+ structuredContent: result,
2173
2800
  isError
2174
2801
  };
2175
2802
  } catch (err) {
2176
2803
  const message = err instanceof Error ? err.message : String(err);
2804
+ const errorResult = {
2805
+ sessionId: "",
2806
+ status: "error",
2807
+ error: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
2808
+ };
2177
2809
  return {
2178
2810
  content: [
2179
2811
  {
2180
2812
  type: "text",
2181
- text: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
2813
+ text: JSON.stringify(errorResult, null, 2)
2182
2814
  }
2183
2815
  ],
2816
+ structuredContent: errorResult,
2184
2817
  isError: true
2185
2818
  };
2186
2819
  }
2187
2820
  }
2188
2821
  );
2189
- server.tool(
2822
+ server.registerTool(
2190
2823
  "claude_code_session",
2191
- `List, inspect, or cancel Claude Code sessions.
2192
-
2193
- - action="list": Get all sessions with their status, cost, turn count, and settings.
2194
- - action="get": Get full details for one session (pass sessionId). Add includeSensitive=true to also see cwd, systemPrompt, agents, and additionalDirectories.
2195
- - action="cancel": Stop a running session immediately (pass sessionId).`,
2196
2824
  {
2197
- action: z.enum(SESSION_ACTIONS),
2198
- sessionId: z.string().optional().describe("Required for 'get' and 'cancel'"),
2199
- includeSensitive: z.boolean().optional().describe("Include cwd/systemPrompt/agents/additionalDirectories. Default: false")
2825
+ description: "List, inspect, or cancel sessions.",
2826
+ inputSchema: {
2827
+ action: z.enum(SESSION_ACTIONS),
2828
+ sessionId: z.string().optional().describe("Required for get/cancel"),
2829
+ includeSensitive: z.boolean().optional().describe("Default: false")
2830
+ },
2831
+ outputSchema: sessionResultSchema,
2832
+ annotations: {
2833
+ readOnlyHint: false,
2834
+ destructiveHint: true,
2835
+ idempotentHint: false,
2836
+ openWorldHint: false
2837
+ }
2200
2838
  },
2201
- async (args) => {
2202
- const result = executeClaudeCodeSession(args, sessionManager);
2203
- return {
2204
- content: [
2205
- {
2206
- type: "text",
2207
- text: JSON.stringify(result, null, 2)
2208
- }
2209
- ],
2210
- isError: result.isError ?? false
2211
- };
2839
+ async (args, extra) => {
2840
+ try {
2841
+ const result = executeClaudeCodeSession(args, sessionManager, extra.signal);
2842
+ return {
2843
+ content: [
2844
+ {
2845
+ type: "text",
2846
+ text: JSON.stringify(result, null, 2)
2847
+ }
2848
+ ],
2849
+ structuredContent: result,
2850
+ isError: result.isError ?? false
2851
+ };
2852
+ } catch (err) {
2853
+ const message = err instanceof Error ? err.message : String(err);
2854
+ const errorResult = {
2855
+ sessions: [],
2856
+ message: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`,
2857
+ isError: true
2858
+ };
2859
+ return {
2860
+ content: [
2861
+ {
2862
+ type: "text",
2863
+ text: JSON.stringify(errorResult, null, 2)
2864
+ }
2865
+ ],
2866
+ structuredContent: errorResult,
2867
+ isError: true
2868
+ };
2869
+ }
2212
2870
  }
2213
2871
  );
2214
- server.tool(
2872
+ server.registerTool(
2215
2873
  "claude_code_check",
2216
- `Query a running session for new events, retrieve the final result, or respond to permission requests.
2217
-
2218
- Two actions:
2219
-
2220
- Defaults (poll):
2221
- - responseMode: "minimal"
2222
- - minimal mode strips verbose fields from assistant messages (usage, model, id, cache_control) and filters out noisy progress events (tool_progress, auth_status)
2223
- - maxEvents: 200 in minimal mode (unlimited in full mode unless maxEvents is set)
2224
-
2225
- action="poll" \u2014 Retrieve events since the last poll.
2226
- Returns events (agent output, progress updates, permission requests, errors, final result).
2227
- Pass the cursor from the previous poll's nextCursor for incremental updates. Omit cursor to get all buffered events.
2228
- If the agent is waiting for permission, the response includes an "actions" array with pending requests.
2229
-
2230
- action="respond_permission" \u2014 Approve or deny a pending permission request.
2231
- Pass the requestId from the actions array, plus decision="allow" or decision="deny".
2232
- Approving resumes agent execution. Denying (with optional interrupt=true) can halt the entire session.
2233
- The response also includes the latest poll state (events, status, etc.), so a separate poll call is not needed.`,
2234
2874
  {
2235
- action: z.enum(CHECK_ACTIONS),
2236
- sessionId: z.string().describe("Target session ID"),
2237
- cursor: z.number().int().nonnegative().optional().describe("Event offset for incremental poll. Default: 0"),
2238
- responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Default: 'minimal'"),
2239
- maxEvents: z.number().int().positive().optional().describe("Events per poll. Default: 200 (minimal)"),
2240
- requestId: z.string().optional().describe("Permission request ID (from actions[])"),
2241
- decision: z.enum(["allow", "deny"]).optional().describe("For respond_permission"),
2242
- denyMessage: z.string().optional().describe("Reason shown to agent on deny. Default: 'Permission denied by caller'"),
2243
- interrupt: z.boolean().optional().describe("Stop session on deny. Default: false"),
2244
- pollOptions: z.object({
2245
- includeTools: z.boolean().optional().describe("Default: false"),
2246
- includeEvents: z.boolean().optional().describe("Default: true"),
2247
- includeActions: z.boolean().optional().describe("Default: true"),
2248
- includeResult: z.boolean().optional().describe("Default: true"),
2249
- includeUsage: z.boolean().optional().describe("Default: full=true, minimal=false"),
2250
- includeModelUsage: z.boolean().optional().describe("Default: full=true, minimal=false"),
2251
- includeStructuredOutput: z.boolean().optional().describe("Default: full=true, minimal=false"),
2252
- includeTerminalEvents: z.boolean().optional().describe("Default: full=true, minimal=false"),
2253
- includeProgressEvents: z.boolean().optional().describe("Default: full=true, minimal=false")
2254
- }).optional().describe("Override responseMode defaults"),
2255
- permissionOptions: z.object({
2256
- updatedInput: z.record(z.string(), z.unknown()).optional().describe("Replace tool input on allow. Default: none"),
2257
- updatedPermissions: z.array(z.record(z.string(), z.unknown())).optional().describe("Update permission rules on allow. Default: none")
2258
- }).optional().describe("Allow-only: modify tool input or update rules")
2875
+ description: "Poll session events or respond to permission requests.",
2876
+ inputSchema: {
2877
+ action: z.enum(CHECK_ACTIONS),
2878
+ sessionId: z.string().describe("Session ID"),
2879
+ cursor: z.number().int().nonnegative().optional().describe("Default: 0"),
2880
+ responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Default: 'minimal'"),
2881
+ maxEvents: z.number().int().positive().optional().describe("Default: 200 (minimal), unlimited (full)"),
2882
+ requestId: z.string().optional().describe("Permission request ID"),
2883
+ decision: z.enum(["allow", "deny"]).optional().describe("Decision"),
2884
+ denyMessage: z.string().optional().describe("Default: 'Permission denied by caller'"),
2885
+ interrupt: z.boolean().optional().describe("Default: false"),
2886
+ pollOptions: z.object({
2887
+ includeTools: z.boolean().optional().describe("Default: false"),
2888
+ includeEvents: z.boolean().optional().describe("Default: true"),
2889
+ includeActions: z.boolean().optional().describe("Default: true"),
2890
+ includeResult: z.boolean().optional().describe("Default: true"),
2891
+ includeUsage: z.boolean().optional().describe("Default: full=true, minimal=false"),
2892
+ includeModelUsage: z.boolean().optional().describe("Default: full=true, minimal=false"),
2893
+ includeStructuredOutput: z.boolean().optional().describe("Default: full=true, minimal=false"),
2894
+ includeTerminalEvents: z.boolean().optional().describe("Default: full=true, minimal=false"),
2895
+ includeProgressEvents: z.boolean().optional().describe("Default: full=true, minimal=false"),
2896
+ maxBytes: z.number().int().positive().optional().describe("Default: unlimited")
2897
+ }).optional().describe("Default: none"),
2898
+ permissionOptions: z.object({
2899
+ updatedInput: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
2900
+ updatedPermissions: z.array(z.record(z.string(), z.unknown())).optional().describe("Default: none")
2901
+ }).optional().describe("Default: none")
2902
+ },
2903
+ outputSchema: checkResultSchema,
2904
+ annotations: {
2905
+ readOnlyHint: false,
2906
+ destructiveHint: true,
2907
+ idempotentHint: false,
2908
+ openWorldHint: false
2909
+ }
2259
2910
  },
2260
- async (args) => {
2261
- const result = executeClaudeCodeCheck(args, sessionManager, toolCache);
2262
- const isError = result.isError === true;
2263
- return {
2264
- content: [
2265
- {
2266
- type: "text",
2267
- text: JSON.stringify(result, null, 2)
2268
- }
2269
- ],
2270
- isError
2271
- };
2911
+ async (args, extra) => {
2912
+ try {
2913
+ const result = executeClaudeCodeCheck(args, sessionManager, toolCache, extra.signal);
2914
+ const isError = result.isError === true;
2915
+ return {
2916
+ content: [
2917
+ {
2918
+ type: "text",
2919
+ text: JSON.stringify(result, null, 2)
2920
+ }
2921
+ ],
2922
+ structuredContent: result,
2923
+ isError
2924
+ };
2925
+ } catch (err) {
2926
+ const message = err instanceof Error ? err.message : String(err);
2927
+ const errorResult = {
2928
+ sessionId: args.sessionId ?? "",
2929
+ status: "error",
2930
+ events: [],
2931
+ isError: true,
2932
+ error: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
2933
+ };
2934
+ return {
2935
+ content: [
2936
+ {
2937
+ type: "text",
2938
+ text: JSON.stringify(errorResult, null, 2)
2939
+ }
2940
+ ],
2941
+ structuredContent: errorResult,
2942
+ isError: true
2943
+ };
2944
+ }
2272
2945
  }
2273
2946
  );
2274
2947
  registerResources(server, { toolCache });
2275
- const originalClose = server.close.bind(server);
2276
- server.close = async () => {
2277
- sessionManager.destroy();
2278
- await originalClose();
2279
- };
2280
- return server;
2948
+ return { server, sessionManager, toolCache };
2949
+ }
2950
+
2951
+ // src/utils/runtime-errors.ts
2952
+ var BENIGN_CODES = /* @__PURE__ */ new Set([
2953
+ "EPIPE",
2954
+ "ECONNRESET",
2955
+ "ERR_STREAM_WRITE_AFTER_END",
2956
+ "ERR_STREAM_DESTROYED",
2957
+ "ABORT_ERR"
2958
+ ]);
2959
+ var BENIGN_MESSAGE_PATTERNS = [
2960
+ "operation aborted",
2961
+ "request was cancelled",
2962
+ "transport closed",
2963
+ "write after end",
2964
+ "stream was destroyed",
2965
+ "cannot call write after a stream was destroyed",
2966
+ "the pipe is being closed"
2967
+ ];
2968
+ function isBenignRuntimeError(error) {
2969
+ if (!(error instanceof Error)) return false;
2970
+ const withCode = error;
2971
+ const code = typeof withCode.code === "string" ? withCode.code : void 0;
2972
+ if (code && BENIGN_CODES.has(code)) return true;
2973
+ if (error.name === "AbortError") return true;
2974
+ const message = error.message.toLowerCase();
2975
+ return BENIGN_MESSAGE_PATTERNS.some((pattern) => message.includes(pattern));
2281
2976
  }
2282
2977
 
2283
2978
  // src/index.ts
2979
+ var STDIN_SHUTDOWN_CHECK_MS = 750;
2980
+ var STDIN_SHUTDOWN_MAX_WAIT_MS = process.platform === "win32" ? 15e3 : 1e4;
2981
+ function summarizeSessions(ctx) {
2982
+ const sessions = ctx.sessionManager.list();
2983
+ const running = sessions.filter((s) => s.status === "running").length;
2984
+ const waitingPermission = sessions.filter((s) => s.status === "waiting_permission").length;
2985
+ return {
2986
+ total: sessions.length,
2987
+ running,
2988
+ waitingPermission,
2989
+ terminal: sessions.length - running - waitingPermission
2990
+ };
2991
+ }
2284
2992
  async function main() {
2285
2993
  const serverCwd = process.cwd();
2286
- const server = createServer(serverCwd);
2994
+ const ctx = createServerContext(serverCwd);
2995
+ const server = ctx.server;
2996
+ const sessionManager = ctx.sessionManager;
2287
2997
  const transport = new StdioServerTransport();
2288
2998
  let closing = false;
2289
- const shutdown = async () => {
2999
+ let lastExitCode = 0;
3000
+ let stdinClosedAt;
3001
+ let stdinClosedReason;
3002
+ let stdinShutdownTimer;
3003
+ const onStdinEnd = () => handleStdinTerminated("end");
3004
+ const onStdinClose = () => handleStdinTerminated("close");
3005
+ const clearStdinShutdownTimer = () => {
3006
+ if (stdinShutdownTimer) {
3007
+ clearTimeout(stdinShutdownTimer);
3008
+ stdinShutdownTimer = void 0;
3009
+ }
3010
+ };
3011
+ const shutdown = async (reason = "unknown") => {
2290
3012
  if (closing) return;
2291
3013
  closing = true;
3014
+ clearStdinShutdownTimer();
3015
+ if (typeof process.stdin.off === "function") {
3016
+ process.stdin.off("error", handleStdinError);
3017
+ process.stdin.off("end", onStdinEnd);
3018
+ process.stdin.off("close", onStdinClose);
3019
+ }
3020
+ const forceExitMs = process.platform === "win32" ? 1e4 : 5e3;
3021
+ const forceExitTimer = setTimeout(() => process.exit(lastExitCode), forceExitMs);
3022
+ if (forceExitTimer.unref) forceExitTimer.unref();
3023
+ const sessionSummary = summarizeSessions(ctx);
2292
3024
  try {
3025
+ if (server?.isConnected()) {
3026
+ await server.sendLoggingMessage({
3027
+ level: "info",
3028
+ data: { event: "server_stopping", reason, ...sessionSummary }
3029
+ });
3030
+ }
3031
+ sessionManager.destroy();
2293
3032
  await server.close();
2294
3033
  } catch {
2295
3034
  }
2296
- process.exitCode = 0;
2297
- setTimeout(() => process.exit(0), 100);
3035
+ process.exitCode = lastExitCode;
3036
+ try {
3037
+ await new Promise((resolve) => process.stderr.write("", () => resolve()));
3038
+ } catch {
3039
+ } finally {
3040
+ clearTimeout(forceExitTimer);
3041
+ }
3042
+ };
3043
+ function handleStdinError(error) {
3044
+ console.error("stdin error:", error);
3045
+ lastExitCode = 1;
3046
+ void shutdown("stdin_error");
3047
+ }
3048
+ function hasActiveSessions() {
3049
+ return sessionManager.list().some((s) => s.status === "running" || s.status === "waiting_permission");
3050
+ }
3051
+ const evaluateStdinTermination = () => {
3052
+ if (closing || stdinClosedAt === void 0) return;
3053
+ const stdinUnavailable = process.stdin.destroyed || process.stdin.readableEnded || !process.stdin.readable;
3054
+ if (!stdinUnavailable) {
3055
+ stdinClosedAt = void 0;
3056
+ stdinClosedReason = void 0;
3057
+ return;
3058
+ }
3059
+ const elapsedMs = Date.now() - stdinClosedAt;
3060
+ const active = hasActiveSessions();
3061
+ const connected = server.isConnected();
3062
+ if (!active && !connected) {
3063
+ void shutdown(`stdin_${stdinClosedReason ?? "closed"}`);
3064
+ return;
3065
+ }
3066
+ if (elapsedMs >= STDIN_SHUTDOWN_MAX_WAIT_MS) {
3067
+ void shutdown(`stdin_${stdinClosedReason ?? "closed"}_timeout`);
3068
+ return;
3069
+ }
3070
+ stdinShutdownTimer = setTimeout(evaluateStdinTermination, STDIN_SHUTDOWN_CHECK_MS);
3071
+ if (stdinShutdownTimer.unref) stdinShutdownTimer.unref();
2298
3072
  };
2299
- process.on("SIGINT", shutdown);
2300
- process.on("SIGTERM", shutdown);
3073
+ function handleStdinTerminated(event) {
3074
+ if (closing) return;
3075
+ if (stdinClosedAt === void 0) {
3076
+ stdinClosedAt = Date.now();
3077
+ stdinClosedReason = event;
3078
+ console.error(`[lifecycle] stdin ${event} observed; entering guarded shutdown checks`);
3079
+ }
3080
+ clearStdinShutdownTimer();
3081
+ stdinShutdownTimer = setTimeout(evaluateStdinTermination, STDIN_SHUTDOWN_CHECK_MS);
3082
+ if (stdinShutdownTimer.unref) stdinShutdownTimer.unref();
3083
+ }
3084
+ const handleUnexpectedError = (error) => {
3085
+ if (isBenignRuntimeError(error)) {
3086
+ console.error("Ignored benign runtime abort:", error);
3087
+ return;
3088
+ }
3089
+ console.error("Unhandled runtime error:", error);
3090
+ lastExitCode = 1;
3091
+ void shutdown("runtime_error");
3092
+ };
3093
+ process.on("SIGINT", () => {
3094
+ void shutdown("SIGINT");
3095
+ });
3096
+ process.on("SIGTERM", () => {
3097
+ void shutdown("SIGTERM");
3098
+ });
3099
+ process.on("SIGHUP", () => {
3100
+ void shutdown("SIGHUP");
3101
+ });
3102
+ process.on("beforeExit", () => {
3103
+ void shutdown("beforeExit");
3104
+ });
3105
+ process.on("uncaughtException", handleUnexpectedError);
3106
+ process.on("unhandledRejection", handleUnexpectedError);
3107
+ if (process.platform === "win32") {
3108
+ process.on("SIGBREAK", () => {
3109
+ void shutdown("SIGBREAK");
3110
+ });
3111
+ }
3112
+ if (typeof process.stdin.resume === "function") {
3113
+ process.stdin.resume();
3114
+ }
3115
+ process.stdin.on("error", handleStdinError);
3116
+ process.stdin.on("end", onStdinEnd);
3117
+ process.stdin.on("close", onStdinClose);
2301
3118
  await server.connect(transport);
3119
+ server.sendToolListChanged();
3120
+ server.sendResourceListChanged();
2302
3121
  checkWindowsBashAvailability();
2303
- console.error(`claude-code-mcp server started (cwd: ${serverCwd})`);
3122
+ try {
3123
+ if (transport && server) {
3124
+ await server.sendLoggingMessage({
3125
+ level: "info",
3126
+ data: { event: "server_started", cwd: serverCwd }
3127
+ });
3128
+ }
3129
+ } catch {
3130
+ }
3131
+ console.error(`claude-code-mcp server started (transport=stdio, cwd: ${serverCwd})`);
2304
3132
  }
2305
3133
  main().catch((err) => {
2306
3134
  console.error("Fatal error:", err);