@leo000001/claude-code-mcp 2.0.1 → 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
@@ -7,24 +7,125 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
  import { z } from "zod";
9
9
 
10
+ // src/utils/permission-updated-input.ts
11
+ function normalizePermissionUpdatedInput(input) {
12
+ if (input && typeof input === "object" && !Array.isArray(input)) {
13
+ return input;
14
+ }
15
+ return { input };
16
+ }
17
+
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,})\/(.*)$/);
39
+ if (m2) {
40
+ const host = m2[1];
41
+ const rest2 = m2[2] ?? "";
42
+ return `\\\\${host}\\${rest2.replace(/\//g, "\\")}`;
43
+ }
44
+ }
45
+ const cyg = rawPath.match(/^\/cygdrive\/([a-zA-Z])\/(.*)$/);
46
+ if (cyg) {
47
+ const drive2 = cyg[1].toUpperCase();
48
+ const rest2 = cyg[2] ?? "";
49
+ return `${drive2}:\\${rest2.replace(/\//g, "\\")}`;
50
+ }
51
+ const m = rawPath.match(/^\/(?:mnt\/)?([a-zA-Z])\/(.*)$/);
52
+ if (!m) return void 0;
53
+ const drive = m[1].toUpperCase();
54
+ const rest = m[2] ?? "";
55
+ return `${drive}:\\${rest.replace(/\//g, "\\")}`;
56
+ }
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) {
76
+ if (platform !== "win32") return input;
77
+ const filePath = input.file_path;
78
+ if (typeof filePath !== "string") return input;
79
+ const normalized = normalizeWindowsPathLike(filePath, platform);
80
+ return normalized === filePath ? input : { ...input, file_path: normalized };
81
+ }
82
+
10
83
  // src/session/manager.ts
11
84
  var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
12
85
  var DEFAULT_RUNNING_SESSION_MAX_MS = 4 * 60 * 60 * 1e3;
13
86
  var DEFAULT_CLEANUP_INTERVAL_MS = 6e4;
14
87
  var DEFAULT_EVENT_BUFFER_MAX_SIZE = 1e3;
15
88
  var DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE = 2e3;
89
+ var DEFAULT_MAX_SESSIONS = 128;
90
+ var DEFAULT_MAX_PENDING_PERMISSIONS_PER_SESSION = 64;
16
91
  var SessionManager = class _SessionManager {
17
92
  sessions = /* @__PURE__ */ new Map();
18
93
  runtime = /* @__PURE__ */ new Map();
94
+ drainingSessions = /* @__PURE__ */ new Map();
19
95
  cleanupTimer;
20
96
  sessionTtlMs = DEFAULT_SESSION_TTL_MS;
21
97
  runningSessionMaxMs = DEFAULT_RUNNING_SESSION_MAX_MS;
22
- constructor() {
98
+ platform;
99
+ maxSessions;
100
+ maxPendingPermissionsPerSession;
101
+ constructor(opts) {
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;
23
111
  this.cleanupTimer = setInterval(() => this.cleanup(), DEFAULT_CLEANUP_INTERVAL_MS);
24
112
  if (this.cleanupTimer.unref) {
25
113
  this.cleanupTimer.unref();
26
114
  }
27
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
+ }
28
129
  create(params) {
29
130
  const now = (/* @__PURE__ */ new Date()).toISOString();
30
131
  const existing = this.sessions.get(params.sessionId);
@@ -117,7 +218,10 @@ var SessionManager = class _SessionManager {
117
218
  if (info.status === "waiting_permission") {
118
219
  this.finishAllPending(
119
220
  sessionId,
120
- { 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 },
121
225
  "cancel"
122
226
  );
123
227
  }
@@ -135,8 +239,10 @@ var SessionManager = class _SessionManager {
135
239
  this.finishAllPending(
136
240
  sessionId,
137
241
  { behavior: "deny", message: "Session deleted", interrupt: true },
138
- "cleanup"
242
+ "cleanup",
243
+ { restoreRunning: false }
139
244
  );
245
+ this.drainingSessions.delete(sessionId);
140
246
  this.runtime.delete(sessionId);
141
247
  return this.sessions.delete(sessionId);
142
248
  }
@@ -192,11 +298,63 @@ var SessionManager = class _SessionManager {
192
298
  const state = this.runtime.get(sessionId);
193
299
  const info = this.sessions.get(sessionId);
194
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
+ }
195
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
+ }
348
+ const inferredExpiresAt = new Date(Date.now() + timeoutMs).toISOString();
349
+ const record = {
350
+ ...req,
351
+ timeoutMs,
352
+ expiresAt: inferredExpiresAt
353
+ };
196
354
  const timeoutId = setTimeout(() => {
197
355
  this.finishRequest(
198
356
  sessionId,
199
- req.requestId,
357
+ record.requestId,
200
358
  {
201
359
  behavior: "deny",
202
360
  message: `Permission request timed out after ${timeoutMs}ms.`,
@@ -205,12 +363,12 @@ var SessionManager = class _SessionManager {
205
363
  "timeout"
206
364
  );
207
365
  }, timeoutMs);
208
- state.pendingPermissions.set(req.requestId, { record: req, finish, timeoutId });
366
+ state.pendingPermissions.set(record.requestId, { record, finish, timeoutId });
209
367
  info.status = "waiting_permission";
210
368
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
211
369
  this.pushEvent(sessionId, {
212
370
  type: "permission_request",
213
- data: req,
371
+ data: record,
214
372
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
215
373
  });
216
374
  return true;
@@ -242,28 +400,86 @@ var SessionManager = class _SessionManager {
242
400
  };
243
401
  }
244
402
  }
403
+ if (finalResult.behavior === "allow") {
404
+ const updatedInput = finalResult.updatedInput;
405
+ const validRecord = updatedInput !== null && updatedInput !== void 0 && typeof updatedInput === "object" && !Array.isArray(updatedInput);
406
+ if (!validRecord) {
407
+ finalResult = {
408
+ ...finalResult,
409
+ updatedInput: normalizePermissionUpdatedInput(pending.record.input)
410
+ };
411
+ } else {
412
+ finalResult = {
413
+ ...finalResult,
414
+ updatedInput: normalizeToolInput(
415
+ pending.record.toolName,
416
+ updatedInput,
417
+ this.platform
418
+ )
419
+ };
420
+ }
421
+ }
245
422
  if (pending.timeoutId) clearTimeout(pending.timeoutId);
246
423
  state.pendingPermissions.delete(requestId);
424
+ const eventData = {
425
+ requestId,
426
+ toolName: pending.record.toolName,
427
+ behavior: finalResult.behavior,
428
+ source
429
+ };
430
+ if (finalResult.behavior === "deny") {
431
+ eventData.message = finalResult.message;
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;
438
+ }
247
439
  this.pushEvent(sessionId, {
248
440
  type: "permission_result",
249
- data: { requestId, behavior: finalResult.behavior, source },
441
+ data: eventData,
250
442
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
251
443
  });
252
444
  try {
253
445
  pending.finish(finalResult);
254
446
  } catch {
255
447
  }
256
- if (info.status === "waiting_permission" && state.pendingPermissions.size === 0) {
448
+ if (info.status === "waiting_permission" && state.pendingPermissions.size === 0 && !this.isDraining(sessionId)) {
257
449
  info.status = "running";
258
450
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
259
451
  }
260
452
  return true;
261
453
  }
262
- finishAllPending(sessionId, result, source) {
454
+ finishAllPending(sessionId, result, source, opts) {
263
455
  const state = this.runtime.get(sessionId);
264
456
  if (!state) return;
265
- for (const requestId of Array.from(state.pendingPermissions.keys())) {
266
- 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();
267
483
  }
268
484
  }
269
485
  /** Remove sessions that have been idle for too long, or stuck running too long */
@@ -275,29 +491,40 @@ var SessionManager = class _SessionManager {
275
491
  this.finishAllPending(
276
492
  id,
277
493
  { behavior: "deny", message: "Session expired", interrupt: true },
278
- "cleanup"
494
+ "cleanup",
495
+ { restoreRunning: false }
279
496
  );
497
+ this.drainingSessions.delete(id);
280
498
  this.sessions.delete(id);
281
499
  this.runtime.delete(id);
282
500
  } else if (info.status === "running" && now - lastActive > this.runningSessionMaxMs) {
283
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";
284
505
  info.status = "error";
285
506
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
286
507
  } else if (info.status === "waiting_permission" && now - lastActive > this.runningSessionMaxMs) {
287
508
  this.finishAllPending(
288
509
  id,
289
510
  { behavior: "deny", message: "Session timed out", interrupt: true },
290
- "cleanup"
511
+ "cleanup",
512
+ { restoreRunning: false }
291
513
  );
292
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";
293
518
  info.status = "error";
294
519
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
295
520
  } else if (info.status !== "running" && info.status !== "waiting_permission" && now - lastActive > this.sessionTtlMs) {
296
521
  this.finishAllPending(
297
522
  id,
298
523
  { behavior: "deny", message: "Session expired", interrupt: true },
299
- "cleanup"
524
+ "cleanup",
525
+ { restoreRunning: false }
300
526
  );
527
+ this.drainingSessions.delete(id);
301
528
  this.sessions.delete(id);
302
529
  this.runtime.delete(id);
303
530
  }
@@ -346,7 +573,8 @@ var SessionManager = class _SessionManager {
346
573
  this.finishAllPending(
347
574
  info.sessionId,
348
575
  { behavior: "deny", message: "Server shutting down", interrupt: true },
349
- "destroy"
576
+ "destroy",
577
+ { restoreRunning: false }
350
578
  );
351
579
  if ((info.status === "running" || info.status === "waiting_permission") && info.abortController) {
352
580
  info.abortController.abort();
@@ -416,6 +644,20 @@ var SessionManager = class _SessionManager {
416
644
  static clearTerminalEvents(buffer) {
417
645
  buffer.events = buffer.events.filter((e) => e.type !== "result" && e.type !== "error");
418
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
+ }
419
661
  };
420
662
 
421
663
  // src/types.ts
@@ -432,23 +674,98 @@ import { AbortError, query } from "@anthropic-ai/claude-agent-sdk";
432
674
  // src/utils/windows.ts
433
675
  import { existsSync } from "fs";
434
676
  import { execSync } from "child_process";
435
- import path from "path";
436
- 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\\";
437
681
  function isWindows() {
438
682
  return process.platform === "win32";
439
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
+ }
440
747
  function findGitBash() {
441
748
  const envPathRaw = process.env.CLAUDE_CODE_GIT_BASH_PATH;
442
749
  if (envPathRaw && envPathRaw.trim() !== "") {
443
- const envPath = normalize(envPathRaw.trim().replace(/^"|"$/g, ""));
444
- if (existsSync(envPath)) return envPath;
445
- return null;
446
- }
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;
447
766
  try {
448
- const output = execSync("where git", { encoding: "utf8" });
449
- const gitCandidates = output.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
450
- for (const gitPathRaw of gitCandidates) {
451
- const gitPath = normalize(gitPathRaw.replace(/^"|"$/g, ""));
767
+ const gitCandidates = [...tryFromPath(["git.exe", "git.cmd", "git.bat"]), ...tryWhere("git")];
768
+ for (const gitPath of gitCandidates) {
452
769
  if (!gitPath) continue;
453
770
  const gitDir = dirname(gitPath);
454
771
  const gitDirLower = gitDir.toLowerCase();
@@ -470,19 +787,38 @@ function findGitBash() {
470
787
  bashCandidates.push(join(root, "mingw64", "bin", "bash.exe"));
471
788
  }
472
789
  for (const bashPath of bashCandidates) {
473
- const normalized = normalize(bashPath);
474
- if (existsSync(normalized)) return normalized;
790
+ const normalizedPath = normalize(bashPath);
791
+ if (existsPath(normalizedPath)) return normalizedPath;
475
792
  }
476
793
  }
477
794
  } catch {
478
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;
479
802
  return null;
480
803
  }
481
804
  function checkWindowsBashAvailability() {
482
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));
483
809
  const bashPath = findGitBash();
484
810
  if (bashPath) {
485
- 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
+ }
486
822
  return;
487
823
  }
488
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.";
@@ -499,7 +835,9 @@ function checkWindowsBashAvailability() {
499
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.';
500
836
  function enhanceWindowsError(errorMessage) {
501
837
  if (!isWindows()) return errorMessage;
502
- 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")) {
503
841
  return errorMessage + WINDOWS_BASH_HINT;
504
842
  }
505
843
  return errorMessage;
@@ -508,6 +846,13 @@ function enhanceWindowsError(errorMessage) {
508
846
  // src/tools/query-consumer.ts
509
847
  var MAX_TRANSIENT_RETRIES = 3;
510
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
+ }
511
856
  function classifyError(err, abortSignal) {
512
857
  if (abortSignal.aborted) return "abort";
513
858
  if (err instanceof AbortError || err instanceof Error && err.name === "AbortError") {
@@ -532,12 +877,16 @@ function describeTool(toolName, toolCache) {
532
877
  return found?.description;
533
878
  }
534
879
  function sdkResultToAgentResult(result) {
880
+ const sessionTotalTurns = result.session_total_turns;
881
+ const sessionTotalCostUsd = result.session_total_cost_usd;
535
882
  const base = {
536
883
  sessionId: result.session_id,
537
884
  durationMs: result.duration_ms,
538
885
  durationApiMs: result.duration_api_ms,
539
886
  numTurns: result.num_turns,
540
887
  totalCostUsd: result.total_cost_usd,
888
+ sessionTotalTurns: typeof sessionTotalTurns === "number" ? sessionTotalTurns : void 0,
889
+ sessionTotalCostUsd: typeof sessionTotalCostUsd === "number" ? sessionTotalCostUsd : void 0,
541
890
  stopReason: result.stop_reason,
542
891
  usage: result.usage,
543
892
  modelUsage: result.modelUsage,
@@ -649,30 +998,42 @@ function consumeQuery(params) {
649
998
  return activeSessionId;
650
999
  };
651
1000
  let initTimeoutId;
1001
+ const permissionRequestTimeoutMs = clampPermissionRequestTimeoutMs(
1002
+ params.permissionRequestTimeoutMs
1003
+ );
652
1004
  const canUseTool = async (toolName, input, options2) => {
653
1005
  const sessionId = await getSessionId();
1006
+ const normalizedInput = normalizeToolInput(toolName, input, params.platform);
654
1007
  const sessionInfo = params.sessionManager.get(sessionId);
655
1008
  if (sessionInfo) {
656
1009
  if (Array.isArray(sessionInfo.disallowedTools) && sessionInfo.disallowedTools.includes(toolName)) {
657
1010
  return { behavior: "deny", message: `Tool '${toolName}' is disallowed by session policy.` };
658
1011
  }
659
1012
  if (!options2.blockedPath && Array.isArray(sessionInfo.allowedTools) && sessionInfo.allowedTools.includes(toolName)) {
660
- return { behavior: "allow" };
1013
+ return {
1014
+ behavior: "allow",
1015
+ updatedInput: normalizePermissionUpdatedInput(normalizedInput)
1016
+ };
661
1017
  }
662
1018
  }
663
1019
  const requestId = `${options2.toolUseID}:${toolName}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
1020
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
1021
+ const timeoutMs = permissionRequestTimeoutMs;
1022
+ const expiresAt = new Date(Date.now() + timeoutMs).toISOString();
664
1023
  const record = {
665
1024
  requestId,
666
1025
  toolName,
667
- input,
668
- summary: summarizePermission(toolName, input),
1026
+ input: normalizedInput,
1027
+ summary: summarizePermission(toolName, normalizedInput),
669
1028
  description: describeTool(toolName, params.toolCache),
670
1029
  decisionReason: options2.decisionReason,
671
1030
  blockedPath: options2.blockedPath,
672
1031
  toolUseID: options2.toolUseID,
673
1032
  agentID: options2.agentID,
674
1033
  suggestions: options2.suggestions,
675
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
1034
+ createdAt,
1035
+ timeoutMs,
1036
+ expiresAt
676
1037
  };
677
1038
  return await new Promise((resolve) => {
678
1039
  let finished = false;
@@ -694,10 +1055,14 @@ function consumeQuery(params) {
694
1055
  sessionId,
695
1056
  record,
696
1057
  finish,
697
- params.permissionRequestTimeoutMs
1058
+ permissionRequestTimeoutMs
698
1059
  );
699
1060
  if (!registered) {
700
- 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
+ });
701
1066
  return;
702
1067
  }
703
1068
  options2.signal.addEventListener("abort", abortListener, { once: true });
@@ -732,6 +1097,7 @@ function consumeQuery(params) {
732
1097
  };
733
1098
  const done = (async () => {
734
1099
  const preInit = [];
1100
+ let preInitDropped = 0;
735
1101
  if (shouldWaitForInit) {
736
1102
  initTimeoutId = setTimeout(() => {
737
1103
  close();
@@ -756,6 +1122,13 @@ function consumeQuery(params) {
756
1122
  sessionIdResolved = true;
757
1123
  resolveSessionId(activeSessionId);
758
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
+ }
759
1132
  for (const buffered of preInit) {
760
1133
  const event2 = messageToEvent(buffered);
761
1134
  if (!event2) continue;
@@ -771,35 +1144,50 @@ function consumeQuery(params) {
771
1144
  }
772
1145
  if (shouldWaitForInit && !sessionIdResolved) {
773
1146
  preInit.push(message);
1147
+ if (preInit.length > MAX_PREINIT_BUFFER_MESSAGES) {
1148
+ preInit.shift();
1149
+ preInitDropped++;
1150
+ }
774
1151
  continue;
775
1152
  }
776
1153
  if (message.type === "result") {
777
1154
  const sessionId2 = message.session_id ?? await getSessionId();
778
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
+ };
779
1168
  const stored = {
780
1169
  type: agentResult.isError ? "error" : "result",
781
- result: agentResult,
1170
+ result: resultWithSessionTotals,
782
1171
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
783
1172
  };
784
1173
  params.sessionManager.setResult(sessionId2, stored);
785
1174
  params.sessionManager.clearTerminalEvents(sessionId2);
786
1175
  params.sessionManager.pushEvent(sessionId2, {
787
1176
  type: agentResult.isError ? "error" : "result",
788
- data: agentResult,
1177
+ data: resultWithSessionTotals,
789
1178
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
790
1179
  });
791
- const current = params.sessionManager.get(sessionId2);
792
1180
  if (current && current.status !== "cancelled") {
793
1181
  params.sessionManager.update(sessionId2, {
794
1182
  status: agentResult.isError ? "error" : "idle",
795
- totalTurns: agentResult.numTurns,
796
- totalCostUsd: agentResult.totalCostUsd,
1183
+ totalTurns: sessionTotalTurns,
1184
+ totalCostUsd: sessionTotalCostUsd,
797
1185
  abortController: void 0
798
1186
  });
799
1187
  } else if (current) {
800
1188
  params.sessionManager.update(sessionId2, {
801
- totalTurns: agentResult.numTurns,
802
- totalCostUsd: agentResult.totalCostUsd,
1189
+ totalTurns: sessionTotalTurns,
1190
+ totalCostUsd: sessionTotalCostUsd,
803
1191
  abortController: void 0
804
1192
  });
805
1193
  }
@@ -948,15 +1336,23 @@ function consumeQuery(params) {
948
1336
 
949
1337
  // src/utils/resume-token.ts
950
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
+ }
951
1344
  function getResumeSecret() {
952
- const secret = process.env.CLAUDE_CODE_MCP_RESUME_SECRET;
953
- if (typeof secret !== "string") return void 0;
954
- const trimmed = secret.trim();
955
- return trimmed.length > 0 ? trimmed : void 0;
1345
+ return getResumeSecrets()[0];
956
1346
  }
957
1347
  function computeResumeToken(sessionId, secret) {
958
1348
  return createHmac("sha256", secret).update(sessionId).digest("base64url");
959
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
+ }
960
1356
 
961
1357
  // src/utils/race-with-abort.ts
962
1358
  function raceWithAbort(promise, signal, onAbort) {
@@ -983,7 +1379,7 @@ function raceWithAbort(promise, signal, onAbort) {
983
1379
 
984
1380
  // src/utils/build-options.ts
985
1381
  function buildOptions(src) {
986
- const opts = { cwd: src.cwd };
1382
+ const opts = { cwd: normalizeWindowsPathLike(src.cwd) };
987
1383
  if (src.allowedTools !== void 0) opts.allowedTools = src.allowedTools;
988
1384
  if (src.disallowedTools !== void 0) opts.disallowedTools = src.disallowedTools;
989
1385
  if (src.tools !== void 0) opts.tools = src.tools;
@@ -995,13 +1391,13 @@ function buildOptions(src) {
995
1391
  if (src.effort !== void 0) opts.effort = src.effort;
996
1392
  if (src.betas !== void 0) opts.betas = src.betas;
997
1393
  if (src.additionalDirectories !== void 0)
998
- opts.additionalDirectories = src.additionalDirectories;
1394
+ opts.additionalDirectories = normalizeWindowsPathArray(src.additionalDirectories);
999
1395
  if (src.outputFormat !== void 0) opts.outputFormat = src.outputFormat;
1000
1396
  if (src.thinking !== void 0) opts.thinking = src.thinking;
1001
1397
  if (src.persistSession !== void 0) opts.persistSession = src.persistSession;
1002
1398
  if (src.resumeSessionAt !== void 0) opts.resumeSessionAt = src.resumeSessionAt;
1003
1399
  if (src.pathToClaudeCodeExecutable !== void 0)
1004
- opts.pathToClaudeCodeExecutable = src.pathToClaudeCodeExecutable;
1400
+ opts.pathToClaudeCodeExecutable = normalizeWindowsPathLike(src.pathToClaudeCodeExecutable);
1005
1401
  if (src.agent !== void 0) opts.agent = src.agent;
1006
1402
  if (src.mcpServers !== void 0) opts.mcpServers = src.mcpServers;
1007
1403
  if (src.sandbox !== void 0) opts.sandbox = src.sandbox;
@@ -1014,11 +1410,48 @@ function buildOptions(src) {
1014
1410
  if (src.settingSources !== void 0) opts.settingSources = src.settingSources;
1015
1411
  else opts.settingSources = DEFAULT_SETTING_SOURCES;
1016
1412
  if (src.debug !== void 0) opts.debug = src.debug;
1017
- if (src.debugFile !== void 0) opts.debugFile = src.debugFile;
1413
+ if (src.debugFile !== void 0) opts.debugFile = normalizeWindowsPathLike(src.debugFile);
1018
1414
  if (src.env !== void 0) opts.env = { ...process.env, ...src.env };
1019
1415
  return opts;
1020
1416
  }
1021
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
+
1022
1455
  // src/tools/claude-code.ts
1023
1456
  async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, requestSignal) {
1024
1457
  const cwd = input.cwd !== void 0 ? input.cwd : serverCwd;
@@ -1029,10 +1462,28 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1029
1462
  error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a non-empty string.`
1030
1463
  };
1031
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
+ }
1032
1472
  const abortController = new AbortController();
1033
1473
  const adv = input.advanced ?? {};
1034
1474
  const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
1035
- 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
+ }
1036
1487
  const flat = {
1037
1488
  cwd,
1038
1489
  allowedTools: input.allowedTools,
@@ -1040,52 +1491,37 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1040
1491
  maxTurns: input.maxTurns,
1041
1492
  model: input.model,
1042
1493
  systemPrompt: input.systemPrompt,
1043
- ...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
1044
1504
  };
1045
1505
  try {
1046
1506
  const handle = consumeQuery({
1047
1507
  mode: "start",
1048
1508
  prompt: input.prompt,
1049
1509
  abortController,
1050
- options: buildOptions(flat),
1510
+ options: buildOptions(normalizedFlat),
1051
1511
  permissionRequestTimeoutMs,
1052
1512
  sessionInitTimeoutMs,
1053
1513
  sessionManager,
1054
1514
  toolCache,
1055
1515
  onInit: (init) => {
1056
1516
  if (sessionManager.get(init.session_id)) return;
1057
- sessionManager.create({
1058
- sessionId: init.session_id,
1059
- cwd,
1060
- model: input.model,
1061
- permissionMode: "default",
1062
- allowedTools: input.allowedTools,
1063
- disallowedTools: input.disallowedTools,
1064
- tools: adv.tools,
1065
- maxTurns: input.maxTurns,
1066
- systemPrompt: input.systemPrompt,
1067
- agents: adv.agents,
1068
- maxBudgetUsd: adv.maxBudgetUsd,
1069
- effort: adv.effort,
1070
- betas: adv.betas,
1071
- additionalDirectories: adv.additionalDirectories,
1072
- outputFormat: adv.outputFormat,
1073
- thinking: adv.thinking,
1074
- persistSession: adv.persistSession,
1075
- pathToClaudeCodeExecutable: adv.pathToClaudeCodeExecutable,
1076
- agent: adv.agent,
1077
- mcpServers: adv.mcpServers,
1078
- sandbox: adv.sandbox,
1079
- fallbackModel: adv.fallbackModel,
1080
- enableFileCheckpointing: adv.enableFileCheckpointing,
1081
- includePartialMessages: adv.includePartialMessages,
1082
- strictMcpConfig: adv.strictMcpConfig,
1083
- settingSources: adv.settingSources ?? DEFAULT_SETTING_SOURCES,
1084
- debug: adv.debug,
1085
- debugFile: adv.debugFile,
1086
- env: adv.env,
1087
- abortController
1088
- });
1517
+ sessionManager.create(
1518
+ toSessionCreateParams({
1519
+ sessionId: init.session_id,
1520
+ source: normalizedFlat,
1521
+ permissionMode: "default",
1522
+ abortController
1523
+ })
1524
+ );
1089
1525
  }
1090
1526
  });
1091
1527
  const sessionId = await raceWithAbort(
@@ -1098,7 +1534,8 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1098
1534
  sessionId,
1099
1535
  status: "running",
1100
1536
  pollInterval: 3e3,
1101
- resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0
1537
+ resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0,
1538
+ compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0
1102
1539
  };
1103
1540
  } catch (err) {
1104
1541
  const message = err instanceof Error ? err.message : String(err);
@@ -1137,6 +1574,13 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1137
1574
  const sessionInitTimeoutMs = input.sessionInitTimeoutMs ?? 1e4;
1138
1575
  const existing = sessionManager.get(input.sessionId);
1139
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
+ }
1140
1584
  const allowDiskResume = process.env.CLAUDE_CODE_MCP_ALLOW_DISK_RESUME === "1";
1141
1585
  if (!allowDiskResume) {
1142
1586
  return {
@@ -1145,7 +1589,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1145
1589
  error: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`
1146
1590
  };
1147
1591
  }
1148
- const resumeSecret = getResumeSecret();
1592
+ const resumeSecrets = getResumeSecrets();
1593
+ const resumeSecret = resumeSecrets[0];
1149
1594
  if (!resumeSecret) {
1150
1595
  return {
1151
1596
  sessionId: input.sessionId,
@@ -1161,8 +1606,7 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1161
1606
  error: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: resumeToken is required for disk resume fallback.`
1162
1607
  };
1163
1608
  }
1164
- const expectedToken = computeResumeToken(input.sessionId, resumeSecret);
1165
- if (dr.resumeToken !== expectedToken) {
1609
+ if (!isValidResumeToken(input.sessionId, dr.resumeToken, resumeSecrets)) {
1166
1610
  return {
1167
1611
  sessionId: input.sessionId,
1168
1612
  status: "error",
@@ -1172,38 +1616,27 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1172
1616
  try {
1173
1617
  const abortController2 = new AbortController();
1174
1618
  const options2 = buildOptionsFromDiskResume(dr);
1175
- sessionManager.create({
1176
- 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,
1177
1625
  cwd: options2.cwd ?? dr.cwd ?? "",
1178
- model: dr.model,
1179
- permissionMode: "default",
1180
- allowedTools: dr.allowedTools,
1181
- disallowedTools: dr.disallowedTools,
1182
- tools: dr.tools,
1183
- maxTurns: dr.maxTurns,
1184
- systemPrompt: dr.systemPrompt,
1185
- agents: dr.agents,
1186
- maxBudgetUsd: dr.maxBudgetUsd,
1187
- effort: dr.effort,
1188
- betas: dr.betas,
1189
- additionalDirectories: dr.additionalDirectories,
1190
- outputFormat: dr.outputFormat,
1191
- thinking: dr.thinking,
1192
- persistSession: dr.persistSession,
1193
- pathToClaudeCodeExecutable: dr.pathToClaudeCodeExecutable,
1194
- agent: dr.agent,
1195
- mcpServers: dr.mcpServers,
1196
- sandbox: dr.sandbox,
1197
- fallbackModel: dr.fallbackModel,
1198
- enableFileCheckpointing: dr.enableFileCheckpointing,
1199
- includePartialMessages: dr.includePartialMessages,
1200
- strictMcpConfig: dr.strictMcpConfig,
1201
- settingSources: dr.settingSources ?? DEFAULT_SETTING_SOURCES,
1202
- debug: dr.debug,
1203
- debugFile: dr.debugFile,
1204
- env: dr.env,
1205
- abortController: abortController2
1206
- });
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
+ );
1207
1640
  try {
1208
1641
  consumeQuery({
1209
1642
  mode: "disk-resume",
@@ -1284,8 +1717,29 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1284
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.`
1285
1718
  };
1286
1719
  }
1287
- const options = buildOptions(existing);
1720
+ const session = acquired;
1721
+ const options = buildOptions(session);
1288
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
+ }
1289
1743
  try {
1290
1744
  const handle = consumeQuery({
1291
1745
  mode: "resume",
@@ -1301,44 +1755,20 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1301
1755
  onInit: (init) => {
1302
1756
  if (!input.forkSession) return;
1303
1757
  if (init.session_id === input.sessionId) return;
1304
- if (!sessionManager.get(init.session_id)) {
1305
- sessionManager.create({
1306
- sessionId: init.session_id,
1307
- cwd: existing.cwd,
1308
- model: existing.model,
1309
- permissionMode: "default",
1310
- allowedTools: existing.allowedTools,
1311
- disallowedTools: existing.disallowedTools,
1312
- tools: existing.tools,
1313
- maxTurns: existing.maxTurns,
1314
- systemPrompt: existing.systemPrompt,
1315
- agents: existing.agents,
1316
- maxBudgetUsd: existing.maxBudgetUsd,
1317
- effort: existing.effort,
1318
- betas: existing.betas,
1319
- additionalDirectories: existing.additionalDirectories,
1320
- outputFormat: existing.outputFormat,
1321
- thinking: existing.thinking,
1322
- persistSession: existing.persistSession,
1323
- pathToClaudeCodeExecutable: existing.pathToClaudeCodeExecutable,
1324
- agent: existing.agent,
1325
- mcpServers: existing.mcpServers,
1326
- sandbox: existing.sandbox,
1327
- fallbackModel: existing.fallbackModel,
1328
- enableFileCheckpointing: existing.enableFileCheckpointing,
1329
- includePartialMessages: existing.includePartialMessages,
1330
- strictMcpConfig: existing.strictMcpConfig,
1331
- settingSources: existing.settingSources ?? DEFAULT_SETTING_SOURCES,
1332
- debug: existing.debug,
1333
- debugFile: existing.debugFile,
1334
- env: existing.env,
1335
- abortController
1336
- });
1337
- }
1338
1758
  sessionManager.update(input.sessionId, {
1339
1759
  status: originalStatus,
1340
1760
  abortController: void 0
1341
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
+ }
1342
1772
  }
1343
1773
  });
1344
1774
  const sessionId = input.forkSession ? await raceWithAbort(
@@ -1391,48 +1821,55 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1391
1821
  // src/tools/tool-discovery.ts
1392
1822
  var TOOL_CATALOG = {
1393
1823
  Bash: {
1394
- description: "Run shell commands (e.g. npm install, git commit, ls) in the project directory.",
1824
+ description: "Run shell commands",
1395
1825
  category: "execute"
1396
1826
  },
1397
- Read: { description: "Read the contents of a file given its path.", category: "file_read" },
1827
+ Read: {
1828
+ description: "Read file contents (large files: use offset/limit or Grep)",
1829
+ category: "file_read"
1830
+ },
1398
1831
  Write: {
1399
- description: "Create a new file or completely replace an existing file's contents.",
1832
+ description: "Create or overwrite files",
1400
1833
  category: "file_write"
1401
1834
  },
1402
1835
  Edit: {
1403
- description: "Make targeted changes to specific parts of an existing file without rewriting the whole file.",
1836
+ description: "Targeted edits (replace_all is substring-based)",
1404
1837
  category: "file_write"
1405
1838
  },
1406
1839
  Glob: {
1407
- description: "Find files by name pattern (e.g. '**/*.ts' finds all TypeScript files).",
1840
+ description: "Find files by glob pattern",
1408
1841
  category: "file_read"
1409
1842
  },
1410
1843
  Grep: {
1411
- description: "Search inside files for text or regex patterns (like grep/ripgrep).",
1844
+ description: "Search file contents (regex)",
1412
1845
  category: "file_read"
1413
1846
  },
1414
1847
  NotebookEdit: {
1415
- description: "Edit individual cells in Jupyter notebooks (.ipynb files).",
1848
+ description: "Edit Jupyter notebook cells (Windows paths normalized)",
1416
1849
  category: "file_write"
1417
1850
  },
1418
1851
  WebFetch: {
1419
- description: "Download and read the content of a web page or API endpoint.",
1852
+ description: "Fetch web page or API content",
1420
1853
  category: "network"
1421
1854
  },
1422
- WebSearch: { description: "Search the web and return relevant results.", category: "network" },
1855
+ WebSearch: { description: "Web search", category: "network" },
1423
1856
  Task: {
1424
- description: "Spawn a subagent to handle a subtask independently (requires this tool to be in allowedTools).",
1857
+ description: "Spawn subagent (must be in allowedTools)",
1425
1858
  category: "agent"
1426
1859
  },
1427
- TaskOutput: { description: "Get the output from a background subagent task.", category: "agent" },
1428
- 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" },
1429
1862
  TodoWrite: {
1430
- description: "Create and update a structured task/todo checklist.",
1863
+ description: "Task/todo checklist",
1431
1864
  category: "agent"
1432
1865
  },
1433
1866
  AskUserQuestion: {
1434
- description: "Ask the user a question and wait for their answer before continuing.",
1867
+ description: "Ask user a question",
1435
1868
  category: "interaction"
1869
+ },
1870
+ TeamDelete: {
1871
+ description: "Delete team (may need shutdown_approved first)",
1872
+ category: "agent"
1436
1873
  }
1437
1874
  };
1438
1875
  function uniq(items) {
@@ -1451,8 +1888,10 @@ function defaultCatalogTools() {
1451
1888
  }
1452
1889
  var ToolDiscoveryCache = class {
1453
1890
  cached;
1454
- constructor(initial) {
1891
+ onUpdated;
1892
+ constructor(initial, onUpdated) {
1455
1893
  this.cached = initial ?? defaultCatalogTools();
1894
+ this.onUpdated = onUpdated;
1456
1895
  }
1457
1896
  getTools() {
1458
1897
  return this.cached;
@@ -1461,7 +1900,13 @@ var ToolDiscoveryCache = class {
1461
1900
  const discovered = discoverToolsFromInit(initTools);
1462
1901
  const next = mergeToolLists(discovered, defaultCatalogTools());
1463
1902
  const updated = JSON.stringify(next) !== JSON.stringify(this.cached);
1464
- if (updated) this.cached = next;
1903
+ if (updated) {
1904
+ this.cached = next;
1905
+ try {
1906
+ this.onUpdated?.(this.cached);
1907
+ } catch {
1908
+ }
1909
+ }
1465
1910
  return { updated, tools: this.cached };
1466
1911
  }
1467
1912
  };
@@ -1486,9 +1931,8 @@ function groupByCategory(tools) {
1486
1931
  function buildInternalToolsDescription(tools) {
1487
1932
  const grouped = groupByCategory(tools);
1488
1933
  const categories = Object.keys(grouped).sort((a, b) => a.localeCompare(b));
1489
- 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';
1490
- 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\n";
1491
- 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";
1492
1936
  for (const category of categories) {
1493
1937
  desc += `
1494
1938
  [${category}]
@@ -1498,7 +1942,7 @@ function buildInternalToolsDescription(tools) {
1498
1942
  `;
1499
1943
  }
1500
1944
  }
1501
- desc += '\nUse `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";
1502
1946
  return desc;
1503
1947
  }
1504
1948
 
@@ -1557,6 +2001,117 @@ function toEvents(events, opts) {
1557
2001
  return { id: e.id, type: e.type, data: e.data, timestamp: e.timestamp };
1558
2002
  });
1559
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
+ }
1560
2115
  function buildResult(sessionManager, toolCache, input) {
1561
2116
  const responseMode = input.responseMode ?? "minimal";
1562
2117
  const po = input.pollOptions ?? {};
@@ -1569,6 +2124,7 @@ function buildResult(sessionManager, toolCache, input) {
1569
2124
  const includeStructuredOutput = po.includeStructuredOutput ?? responseMode === "full";
1570
2125
  const includeTerminalEvents = po.includeTerminalEvents ?? responseMode === "full";
1571
2126
  const includeProgressEvents = po.includeProgressEvents ?? responseMode === "full";
2127
+ const maxBytes = po.maxBytes;
1572
2128
  const maxEvents = input.maxEvents ?? (responseMode === "minimal" ? 200 : void 0);
1573
2129
  const sessionId = input.sessionId;
1574
2130
  const session = sessionManager.get(sessionId);
@@ -1581,7 +2137,7 @@ function buildResult(sessionManager, toolCache, input) {
1581
2137
  let truncated = false;
1582
2138
  const truncatedFields = [];
1583
2139
  const windowEvents = maxEvents !== void 0 && rawEvents.length > maxEvents ? rawEvents.slice(0, maxEvents) : rawEvents;
1584
- 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;
1585
2141
  if (maxEvents !== void 0 && rawEvents.length > maxEvents) {
1586
2142
  truncated = true;
1587
2143
  truncatedFields.push("events");
@@ -1604,8 +2160,28 @@ function buildResult(sessionManager, toolCache, input) {
1604
2160
  })();
1605
2161
  const pending = status === "waiting_permission" ? sessionManager.listPendingPermissions(sessionId) : [];
1606
2162
  const stored = status === "idle" || status === "error" ? sessionManager.getResult(sessionId) : void 0;
1607
- const initTools = includeTools ? sessionManager.getInitTools(sessionId) : void 0;
2163
+ const initTools = sessionManager.getInitTools(sessionId);
1608
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
+ }
1609
2185
  return {
1610
2186
  sessionId,
1611
2187
  status,
@@ -1613,28 +2189,32 @@ function buildResult(sessionManager, toolCache, input) {
1613
2189
  cursorResetTo,
1614
2190
  truncated: truncated ? true : void 0,
1615
2191
  truncatedFields: truncatedFields.length > 0 ? truncatedFields : void 0,
1616
- events: toEvents(outputEvents, {
1617
- includeUsage,
1618
- includeModelUsage,
1619
- includeStructuredOutput,
1620
- slim: responseMode === "minimal"
1621
- }),
2192
+ events: cappedEvents.events,
1622
2193
  nextCursor,
1623
2194
  availableTools,
1624
- actions: includeActions && status === "waiting_permission" ? pending.map((req) => ({
1625
- type: "permission",
1626
- requestId: req.requestId,
1627
- toolName: req.toolName,
1628
- input: req.input,
1629
- summary: req.summary,
1630
- decisionReason: req.decisionReason,
1631
- blockedPath: req.blockedPath,
1632
- toolUseID: req.toolUseID,
1633
- agentID: req.agentID,
1634
- suggestions: req.suggestions,
1635
- description: req.description,
1636
- createdAt: req.createdAt
1637
- })) : void 0,
2195
+ toolValidation: toolValidation.summary,
2196
+ compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0,
2197
+ actions: includeActions && status === "waiting_permission" ? pending.map((req) => {
2198
+ const expiresMs = req.expiresAt ? Date.parse(req.expiresAt) : Number.NaN;
2199
+ const remainingMs = Number.isFinite(expiresMs) ? Math.max(0, expiresMs - Date.now()) : void 0;
2200
+ return {
2201
+ type: "permission",
2202
+ requestId: req.requestId,
2203
+ toolName: req.toolName,
2204
+ input: req.input,
2205
+ summary: req.summary,
2206
+ decisionReason: req.decisionReason,
2207
+ blockedPath: req.blockedPath,
2208
+ toolUseID: req.toolUseID,
2209
+ agentID: req.agentID,
2210
+ suggestions: req.suggestions,
2211
+ description: req.description,
2212
+ createdAt: req.createdAt,
2213
+ timeoutMs: req.timeoutMs,
2214
+ expiresAt: req.expiresAt,
2215
+ remainingMs
2216
+ };
2217
+ }) : void 0,
1638
2218
  result: includeResult && stored?.result ? redactAgentResult(stored.result, {
1639
2219
  includeUsage,
1640
2220
  includeModelUsage,
@@ -1673,7 +2253,14 @@ function redactAgentResult(result, opts) {
1673
2253
  structuredOutput: opts.includeStructuredOutput ? structuredOutput : void 0
1674
2254
  };
1675
2255
  }
1676
- 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
+ }
1677
2264
  if (typeof input.sessionId !== "string" || input.sessionId.trim() === "") {
1678
2265
  return {
1679
2266
  sessionId: "",
@@ -1729,7 +2316,14 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache) {
1729
2316
  }
1730
2317
 
1731
2318
  // src/tools/claude-code-session.ts
1732
- 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
+ }
1733
2327
  const toSessionJson = (s) => input.includeSensitive ? sessionManager.toSensitiveJSON(s) : sessionManager.toPublicJSON(s);
1734
2328
  switch (input.action) {
1735
2329
  case "list": {
@@ -1796,32 +2390,192 @@ function executeClaudeCodeSession(input, sessionManager) {
1796
2390
  }
1797
2391
  }
1798
2392
 
2393
+ // src/resources/register-resources.ts
2394
+ var RESOURCE_SCHEME = "claude-code-mcp";
2395
+ var RESOURCE_URIS = {
2396
+ serverInfo: `${RESOURCE_SCHEME}:///server-info`,
2397
+ internalTools: `${RESOURCE_SCHEME}:///internal-tools`,
2398
+ gotchas: `${RESOURCE_SCHEME}:///gotchas`,
2399
+ compatReport: `${RESOURCE_SCHEME}:///compat-report`
2400
+ };
2401
+ function asTextResource(uri, text, mimeType) {
2402
+ return {
2403
+ contents: [
2404
+ {
2405
+ uri: uri.toString(),
2406
+ text,
2407
+ mimeType
2408
+ }
2409
+ ]
2410
+ };
2411
+ }
2412
+ function registerResources(server, deps) {
2413
+ const serverInfoUri = new URL(RESOURCE_URIS.serverInfo);
2414
+ server.registerResource(
2415
+ "server_info",
2416
+ serverInfoUri.toString(),
2417
+ {
2418
+ title: "Server Info",
2419
+ description: "Server metadata (version/platform/runtime).",
2420
+ mimeType: "application/json"
2421
+ },
2422
+ () => asTextResource(
2423
+ serverInfoUri,
2424
+ JSON.stringify(
2425
+ {
2426
+ name: "claude-code-mcp",
2427
+ node: process.version,
2428
+ platform: process.platform,
2429
+ arch: process.arch,
2430
+ resources: Object.values(RESOURCE_URIS),
2431
+ toolCatalogCount: deps.toolCache.getTools().length
2432
+ },
2433
+ null,
2434
+ 2
2435
+ ),
2436
+ "application/json"
2437
+ )
2438
+ );
2439
+ const toolsUri = new URL(RESOURCE_URIS.internalTools);
2440
+ server.registerResource(
2441
+ "internal_tools",
2442
+ toolsUri.toString(),
2443
+ {
2444
+ title: "Internal Tools",
2445
+ description: "Claude Code internal tool catalog (runtime-aware).",
2446
+ mimeType: "application/json"
2447
+ },
2448
+ () => asTextResource(
2449
+ toolsUri,
2450
+ JSON.stringify({ tools: deps.toolCache.getTools() }, null, 2),
2451
+ "application/json"
2452
+ )
2453
+ );
2454
+ const gotchasUri = new URL(RESOURCE_URIS.gotchas);
2455
+ server.registerResource(
2456
+ "gotchas",
2457
+ gotchasUri.toString(),
2458
+ {
2459
+ title: "Gotchas",
2460
+ description: "Practical limits and gotchas when using Claude Code via this MCP server.",
2461
+ mimeType: "text/markdown"
2462
+ },
2463
+ () => asTextResource(
2464
+ gotchasUri,
2465
+ [
2466
+ "# claude-code-mcp: gotchas",
2467
+ "",
2468
+ "- Permission approvals have a timeout (default 60s, server-clamped to 5min) and auto-deny (`actions[].expiresAt`/`remainingMs`).",
2469
+ "- `Read` has a per-call size cap in practice (often ~256KB); for large files use `offset`/`limit` or chunk with `Grep`.",
2470
+ "- `Edit` with `replace_all=true` is substring replacement; if no match is found the tool returns an error.",
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`.",
2472
+ "- `TeamDelete` may require members to reach `shutdown_approved`; cleanup can be asynchronous during shutdown.",
2473
+ '- Skills may become available later in the same session (early calls may show "Unknown").',
2474
+ "- Some internal features (e.g. ToolSearch) may not appear in `availableTools` because it is derived from SDK `system/init.tools`.",
2475
+ ""
2476
+ ].join("\n"),
2477
+ "text/markdown"
2478
+ )
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
+ );
2529
+ }
2530
+
1799
2531
  // src/server.ts
1800
- var SERVER_VERSION = true ? "2.0.1" : "0.0.0-dev";
1801
- function createServer(serverCwd) {
2532
+ var SERVER_VERSION = true ? "2.2.0" : "0.0.0-dev";
2533
+ function createServerContext(serverCwd) {
1802
2534
  const sessionManager = new SessionManager();
1803
- const toolCache = new ToolDiscoveryCache();
1804
- const server = new McpServer({
1805
- name: "claude-code-mcp",
1806
- 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
+ }
1807
2561
  });
1808
2562
  const agentDefinitionSchema = z.object({
1809
2563
  description: z.string(),
1810
2564
  prompt: z.string(),
1811
- tools: z.array(z.string()).optional(),
1812
- disallowedTools: z.array(z.string()).optional(),
1813
- model: z.enum(AGENT_MODELS).optional(),
1814
- maxTurns: z.number().int().positive().optional(),
1815
- mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional(),
1816
- skills: z.array(z.string()).optional(),
1817
- 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")
1818
2572
  });
1819
2573
  const systemPromptSchema = z.union([
1820
2574
  z.string(),
1821
2575
  z.object({
1822
2576
  type: z.literal("preset"),
1823
2577
  preset: z.literal("claude_code"),
1824
- append: z.string().optional().describe("Appended to preset prompt")
2578
+ append: z.string().optional().describe("Default: none")
1825
2579
  })
1826
2580
  ]);
1827
2581
  const toolsConfigSchema = z.union([
@@ -1839,47 +2593,135 @@ function createServer(serverCwd) {
1839
2593
  }),
1840
2594
  z.object({ type: z.literal("disabled") })
1841
2595
  ]);
2596
+ const effortOptionSchema = z.enum(EFFORT_LEVELS).optional();
2597
+ const thinkingOptionSchema = thinkingSchema.optional();
1842
2598
  const outputFormatSchema = z.object({
1843
2599
  type: z.literal("json_schema"),
1844
2600
  schema: z.record(z.string(), z.unknown())
1845
2601
  });
1846
- const advancedOptionsSchema = z.object({
1847
- tools: toolsConfigSchema.optional().describe("Visible tool set. Default: SDK"),
2602
+ const sharedOptionFieldsSchemaShape = {
2603
+ tools: toolsConfigSchema.optional().describe("Tool set. Default: SDK"),
1848
2604
  persistSession: z.boolean().optional().describe("Default: true"),
1849
- sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000"),
1850
- agents: z.record(z.string(), agentDefinitionSchema).optional().describe("Sub-agent definitions. Default: none"),
1851
- 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"),
1852
2607
  maxBudgetUsd: z.number().positive().optional().describe("Default: none"),
1853
- effort: z.enum(EFFORT_LEVELS).optional().describe("Default: SDK"),
1854
2608
  betas: z.array(z.string()).optional().describe("Default: none"),
1855
2609
  additionalDirectories: z.array(z.string()).optional().describe("Default: none"),
1856
- outputFormat: outputFormatSchema.optional().describe("Default: none (plain text)"),
1857
- thinking: thinkingSchema.optional().describe("Default: SDK"),
2610
+ outputFormat: outputFormatSchema.optional().describe("Default: none"),
1858
2611
  pathToClaudeCodeExecutable: z.string().optional().describe("Default: SDK-bundled"),
1859
2612
  mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("Default: none"),
1860
2613
  sandbox: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
1861
2614
  fallbackModel: z.string().optional().describe("Default: none"),
1862
2615
  enableFileCheckpointing: z.boolean().optional().describe("Default: false"),
1863
- includePartialMessages: z.boolean().optional().describe("Stream events to claude_code_check. Default: false"),
2616
+ includePartialMessages: z.boolean().optional().describe("Default: false"),
1864
2617
  strictMcpConfig: z.boolean().optional().describe("Default: false"),
1865
- 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"),
1866
2619
  debug: z.boolean().optional().describe("Default: false"),
1867
- debugFile: z.string().optional().describe("Enables debug. Default: none"),
1868
- env: z.record(z.string(), z.string().optional()).optional().describe("Merged with process.env. Default: none")
1869
- }).optional().describe("Low-frequency SDK options (all optional)");
1870
- 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(
1871
2701
  "claude_code",
1872
- buildInternalToolsDescription(toolCache.getTools()),
1873
2702
  {
1874
- prompt: z.string().describe("Task or question"),
1875
- cwd: z.string().optional().describe("Working directory. Default: server cwd"),
1876
- allowedTools: z.array(z.string()).optional().describe("Auto-approved tools, e.g. ['Bash','Read','Write','Edit']. Default: []"),
1877
- disallowedTools: z.array(z.string()).optional().describe("Forbidden tools (priority over allowedTools). Default: []"),
1878
- maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
1879
- model: z.string().optional().describe("e.g. 'opus'. Default: SDK"),
1880
- systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
1881
- permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Auto-deny timeout (ms). Default: 60000"),
1882
- 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
+ }
1883
2725
  },
1884
2726
  async (args, extra) => {
1885
2727
  try {
@@ -1898,76 +2740,50 @@ function createServer(serverCwd) {
1898
2740
  text: JSON.stringify(result, null, 2)
1899
2741
  }
1900
2742
  ],
2743
+ structuredContent: result,
1901
2744
  isError
1902
2745
  };
1903
2746
  } catch (err) {
1904
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
+ };
1905
2753
  return {
1906
2754
  content: [
1907
2755
  {
1908
2756
  type: "text",
1909
- text: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
2757
+ text: JSON.stringify(errorResult, null, 2)
1910
2758
  }
1911
2759
  ],
2760
+ structuredContent: errorResult,
1912
2761
  isError: true
1913
2762
  };
1914
2763
  }
1915
2764
  }
1916
2765
  );
1917
- server.tool(
2766
+ server.registerTool(
1918
2767
  "claude_code_reply",
1919
- `Send a follow-up message to an existing Claude Code session.
1920
-
1921
- 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.
1922
-
1923
- Supports session forking (forkSession=true) to explore alternative approaches without modifying the original session.
1924
-
1925
- Defaults:
1926
- - forkSession: false
1927
- - sessionInitTimeoutMs: 10000 (only used when forkSession=true)
1928
- - permissionRequestTimeoutMs: 60000
1929
- - Disk resume: disabled unless CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1
1930
-
1931
- 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.`,
1932
2768
  {
1933
- sessionId: z.string().describe("Session ID from claude_code"),
1934
- prompt: z.string().describe("Follow-up message"),
1935
- forkSession: z.boolean().optional().describe("Branch into new session copy. Default: false"),
1936
- sessionInitTimeoutMs: z.number().int().positive().optional().describe("Fork init timeout (ms). Default: 10000"),
1937
- permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Auto-deny timeout (ms). Default: 60000"),
1938
- diskResumeConfig: z.object({
1939
- resumeToken: z.string().optional().describe("Required"),
1940
- cwd: z.string().optional().describe("Required"),
1941
- allowedTools: z.array(z.string()).optional().describe("Default: []"),
1942
- disallowedTools: z.array(z.string()).optional().describe("Default: []"),
1943
- tools: toolsConfigSchema.optional().describe("Default: SDK"),
1944
- persistSession: z.boolean().optional().describe("Default: true"),
1945
- maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
1946
- model: z.string().optional().describe("Default: SDK"),
1947
- systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
1948
- agents: z.record(z.string(), agentDefinitionSchema).optional().describe("Default: none"),
1949
- agent: z.string().optional().describe("Default: none"),
1950
- maxBudgetUsd: z.number().positive().optional().describe("Default: none"),
1951
- effort: z.enum(EFFORT_LEVELS).optional().describe("Default: SDK"),
1952
- betas: z.array(z.string()).optional().describe("Default: none"),
1953
- additionalDirectories: z.array(z.string()).optional().describe("Default: none"),
1954
- outputFormat: outputFormatSchema.optional().describe("Default: none"),
1955
- thinking: thinkingSchema.optional().describe("Default: SDK"),
1956
- resumeSessionAt: z.string().optional().describe("Resume to specific message UUID. Default: none"),
1957
- pathToClaudeCodeExecutable: z.string().optional().describe("Default: SDK-bundled"),
1958
- mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("Default: none"),
1959
- sandbox: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
1960
- fallbackModel: z.string().optional().describe("Default: none"),
1961
- enableFileCheckpointing: z.boolean().optional().describe("Default: false"),
1962
- includePartialMessages: z.boolean().optional().describe("Stream events to claude_code_check. Default: false"),
1963
- strictMcpConfig: z.boolean().optional().describe("Default: false"),
1964
- settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. [] = isolation mode"),
1965
- debug: z.boolean().optional().describe("Default: false"),
1966
- debugFile: z.string().optional().describe("Enables debug. Default: none"),
1967
- env: z.record(z.string(), z.string().optional()).optional().describe("Default: none")
1968
- }).optional().describe(
1969
- "Disk resume config (needs CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1). Requires resumeToken + cwd."
1970
- )
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
+ }
1971
2787
  },
1972
2788
  async (args, extra) => {
1973
2789
  try {
@@ -1980,136 +2796,339 @@ Disk resume: If the server restarted and the session is no longer in memory, set
1980
2796
  text: JSON.stringify(result, null, 2)
1981
2797
  }
1982
2798
  ],
2799
+ structuredContent: result,
1983
2800
  isError
1984
2801
  };
1985
2802
  } catch (err) {
1986
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
+ };
1987
2809
  return {
1988
2810
  content: [
1989
2811
  {
1990
2812
  type: "text",
1991
- text: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
2813
+ text: JSON.stringify(errorResult, null, 2)
1992
2814
  }
1993
2815
  ],
2816
+ structuredContent: errorResult,
1994
2817
  isError: true
1995
2818
  };
1996
2819
  }
1997
2820
  }
1998
2821
  );
1999
- server.tool(
2822
+ server.registerTool(
2000
2823
  "claude_code_session",
2001
- `List, inspect, or cancel Claude Code sessions.
2002
-
2003
- - action="list": Get all sessions with their status, cost, turn count, and settings.
2004
- - action="get": Get full details for one session (pass sessionId). Add includeSensitive=true to also see cwd, systemPrompt, agents, and additionalDirectories.
2005
- - action="cancel": Stop a running session immediately (pass sessionId).`,
2006
2824
  {
2007
- action: z.enum(SESSION_ACTIONS),
2008
- sessionId: z.string().optional().describe("Required for 'get' and 'cancel'"),
2009
- 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
+ }
2010
2838
  },
2011
- async (args) => {
2012
- const result = executeClaudeCodeSession(args, sessionManager);
2013
- return {
2014
- content: [
2015
- {
2016
- type: "text",
2017
- text: JSON.stringify(result, null, 2)
2018
- }
2019
- ],
2020
- isError: result.isError ?? false
2021
- };
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
+ }
2022
2870
  }
2023
2871
  );
2024
- server.tool(
2872
+ server.registerTool(
2025
2873
  "claude_code_check",
2026
- `Query a running session for new events, retrieve the final result, or respond to permission requests.
2027
-
2028
- Two actions:
2029
-
2030
- Defaults (poll):
2031
- - responseMode: "minimal"
2032
- - minimal mode strips verbose fields from assistant messages (usage, model, id, cache_control) and filters out noisy progress events (tool_progress, auth_status)
2033
- - maxEvents: 200 in minimal mode (unlimited in full mode unless maxEvents is set)
2034
-
2035
- action="poll" \u2014 Retrieve events since the last poll.
2036
- Returns events (agent output, progress updates, permission requests, errors, final result).
2037
- Pass the cursor from the previous poll's nextCursor for incremental updates. Omit cursor to get all buffered events.
2038
- If the agent is waiting for permission, the response includes an "actions" array with pending requests.
2039
-
2040
- action="respond_permission" \u2014 Approve or deny a pending permission request.
2041
- Pass the requestId from the actions array, plus decision="allow" or decision="deny".
2042
- Approving resumes agent execution. Denying (with optional interrupt=true) can halt the entire session.
2043
- The response also includes the latest poll state (events, status, etc.), so a separate poll call is not needed.`,
2044
2874
  {
2045
- action: z.enum(CHECK_ACTIONS),
2046
- sessionId: z.string().describe("Target session ID"),
2047
- cursor: z.number().int().nonnegative().optional().describe("Event offset for incremental poll. Default: 0"),
2048
- responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Default: 'minimal'"),
2049
- maxEvents: z.number().int().positive().optional().describe("Events per poll. Default: 200 (minimal)"),
2050
- requestId: z.string().optional().describe("Permission request ID (from actions[])"),
2051
- decision: z.enum(["allow", "deny"]).optional().describe("For respond_permission"),
2052
- denyMessage: z.string().optional().describe("Reason shown to agent on deny. Default: 'Permission denied by caller'"),
2053
- interrupt: z.boolean().optional().describe("Stop session on deny. Default: false"),
2054
- pollOptions: z.object({
2055
- includeTools: z.boolean().optional().describe("Default: false"),
2056
- includeEvents: z.boolean().optional().describe("Default: true"),
2057
- includeActions: z.boolean().optional().describe("Default: true"),
2058
- includeResult: z.boolean().optional().describe("Default: true"),
2059
- includeUsage: z.boolean().optional().describe("Default: full=true, minimal=false"),
2060
- includeModelUsage: z.boolean().optional().describe("Default: full=true, minimal=false"),
2061
- includeStructuredOutput: z.boolean().optional().describe("Default: full=true, minimal=false"),
2062
- includeTerminalEvents: z.boolean().optional().describe("Default: full=true, minimal=false"),
2063
- includeProgressEvents: z.boolean().optional().describe("Default: full=true, minimal=false")
2064
- }).optional().describe("Override responseMode defaults"),
2065
- permissionOptions: z.object({
2066
- updatedInput: z.record(z.string(), z.unknown()).optional().describe("Replace tool input on allow. Default: none"),
2067
- updatedPermissions: z.array(z.record(z.string(), z.unknown())).optional().describe("Update permission rules on allow. Default: none")
2068
- }).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
+ }
2069
2910
  },
2070
- async (args) => {
2071
- const result = executeClaudeCodeCheck(args, sessionManager, toolCache);
2072
- const isError = result.isError === true;
2073
- return {
2074
- content: [
2075
- {
2076
- type: "text",
2077
- text: JSON.stringify(result, null, 2)
2078
- }
2079
- ],
2080
- isError
2081
- };
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
+ }
2082
2945
  }
2083
2946
  );
2084
- const originalClose = server.close.bind(server);
2085
- server.close = async () => {
2086
- sessionManager.destroy();
2087
- await originalClose();
2088
- };
2089
- return server;
2947
+ registerResources(server, { toolCache });
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));
2090
2976
  }
2091
2977
 
2092
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
+ }
2093
2992
  async function main() {
2094
2993
  const serverCwd = process.cwd();
2095
- const server = createServer(serverCwd);
2994
+ const ctx = createServerContext(serverCwd);
2995
+ const server = ctx.server;
2996
+ const sessionManager = ctx.sessionManager;
2096
2997
  const transport = new StdioServerTransport();
2097
2998
  let closing = false;
2098
- 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") => {
2099
3012
  if (closing) return;
2100
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);
2101
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();
2102
3032
  await server.close();
2103
3033
  } catch {
2104
3034
  }
2105
- process.exitCode = 0;
2106
- 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();
3072
+ };
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");
2107
3092
  };
2108
- process.on("SIGINT", shutdown);
2109
- process.on("SIGTERM", shutdown);
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);
2110
3118
  await server.connect(transport);
3119
+ server.sendToolListChanged();
3120
+ server.sendResourceListChanged();
2111
3121
  checkWindowsBashAvailability();
2112
- 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})`);
2113
3132
  }
2114
3133
  main().catch((err) => {
2115
3134
  console.error("Fatal error:", err);