@leo000001/claude-code-mcp 2.0.3 → 2.4.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,45 +7,102 @@ 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 };
10
+ // src/utils/normalize-windows-path.ts
11
+ import path from "path";
12
+ import os from "os";
13
+ var TOOL_INPUT_PATH_FIELDS = [
14
+ "file_path",
15
+ "path",
16
+ "directory",
17
+ "folder",
18
+ "cwd",
19
+ "dest",
20
+ "destination",
21
+ "source",
22
+ "target"
23
+ ];
24
+ function maybeAddWindowsLongPathPrefix(value) {
25
+ if (value.startsWith("\\\\?\\") || value.startsWith("\\\\.\\") || value.length < 240)
26
+ return value;
27
+ return path.win32.toNamespacedPath(value);
16
28
  }
17
-
18
- // src/utils/normalize-tool-input.ts
19
- function normalizeMsysToWindowsPath(path2) {
20
- if (/^[a-zA-Z]:[\\/]/.test(path2) || path2.startsWith("\\\\")) return void 0;
21
- if (path2.startsWith("//")) {
22
- const m2 = path2.match(/^\/\/([^/]{2,})\/(.*)$/);
29
+ function expandTilde(value, platform) {
30
+ if (value === "~") return os.homedir();
31
+ if (!value.startsWith("~")) return value;
32
+ const next = value[1];
33
+ if (next !== "/" && next !== "\\") return value;
34
+ const rest = value.slice(2);
35
+ const join2 = platform === "win32" ? path.win32.join : path.posix.join;
36
+ return join2(os.homedir(), rest);
37
+ }
38
+ function convertMsysToWindowsPath(rawPath) {
39
+ if (/^[a-zA-Z]:[\\/]/.test(rawPath) || rawPath.startsWith("\\\\")) return void 0;
40
+ if (rawPath.startsWith("//")) {
41
+ const m2 = rawPath.match(/^\/\/([^/]{2,})\/(.*)$/);
23
42
  if (m2) {
24
43
  const host = m2[1];
25
44
  const rest2 = m2[2] ?? "";
26
45
  return `\\\\${host}\\${rest2.replace(/\//g, "\\")}`;
27
46
  }
28
47
  }
29
- const cyg = path2.match(/^\/cygdrive\/([a-zA-Z])\/(.*)$/);
48
+ const cyg = rawPath.match(/^\/cygdrive\/([a-zA-Z])\/(.*)$/);
30
49
  if (cyg) {
31
50
  const drive2 = cyg[1].toUpperCase();
32
51
  const rest2 = cyg[2] ?? "";
33
52
  return `${drive2}:\\${rest2.replace(/\//g, "\\")}`;
34
53
  }
35
- const m = path2.match(/^\/(?:mnt\/)?([a-zA-Z])\/(.*)$/);
54
+ const m = rawPath.match(/^\/(?:mnt\/)?([a-zA-Z])\/(.*)$/);
36
55
  if (!m) return void 0;
37
56
  const drive = m[1].toUpperCase();
38
57
  const rest = m[2] ?? "";
39
58
  return `${drive}:\\${rest.replace(/\//g, "\\")}`;
40
59
  }
41
- function normalizeToolInput(toolName, input, platform = process.platform) {
60
+ function normalizeMsysToWindowsPath(rawPath) {
61
+ const converted = convertMsysToWindowsPath(rawPath);
62
+ if (!converted) return void 0;
63
+ return path.win32.normalize(converted);
64
+ }
65
+ function isUnsupportedPosixAbsolutePath(value, platform = process.platform) {
66
+ if (platform !== "win32") return false;
67
+ if (!value.startsWith("/")) return false;
68
+ return convertMsysToWindowsPath(value) === void 0;
69
+ }
70
+ function extractPosixHomePathFromCommand(command) {
71
+ const match = command.match(/\/home\/[^/\s"'`]+(?:\/[^\s"'`]*)?/);
72
+ return match?.[0];
73
+ }
74
+ function findUnsupportedPosixPathInToolInput(input, platform = process.platform) {
75
+ if (platform !== "win32") return void 0;
76
+ for (const key of TOOL_INPUT_PATH_FIELDS) {
77
+ const value = input[key];
78
+ if (typeof value !== "string") continue;
79
+ if (isUnsupportedPosixAbsolutePath(value, platform)) return value;
80
+ }
81
+ const command = input.command;
82
+ if (typeof command !== "string") return void 0;
83
+ const embeddedHomePath = extractPosixHomePathFromCommand(command);
84
+ if (!embeddedHomePath) return void 0;
85
+ return isUnsupportedPosixAbsolutePath(embeddedHomePath, platform) ? embeddedHomePath : void 0;
86
+ }
87
+ function normalizeWindowsPathLike(value, platform = process.platform) {
88
+ const expanded = expandTilde(value, platform);
89
+ if (platform !== "win32") return expanded;
90
+ const converted = normalizeMsysToWindowsPath(expanded);
91
+ if (!converted && expanded.startsWith("/")) return expanded;
92
+ const normalized = path.win32.normalize(converted ?? expanded);
93
+ return maybeAddWindowsLongPathPrefix(normalized);
94
+ }
95
+ function normalizeWindowsPathArray(values, platform = process.platform) {
96
+ return values.map((v) => normalizeWindowsPathLike(v, platform));
97
+ }
98
+
99
+ // src/utils/normalize-tool-input.ts
100
+ function normalizeToolInput(_toolName, input, platform = process.platform) {
42
101
  if (platform !== "win32") return input;
43
- if (toolName !== "NotebookEdit") return input;
44
102
  const filePath = input.file_path;
45
103
  if (typeof filePath !== "string") return input;
46
- const normalized = normalizeMsysToWindowsPath(filePath);
47
- if (!normalized) return input;
48
- return { ...input, file_path: normalized };
104
+ const normalized = normalizeWindowsPathLike(filePath, platform);
105
+ return normalized === filePath ? input : { ...input, file_path: normalized };
49
106
  }
50
107
 
51
108
  // src/session/manager.ts
@@ -54,21 +111,91 @@ var DEFAULT_RUNNING_SESSION_MAX_MS = 4 * 60 * 60 * 1e3;
54
111
  var DEFAULT_CLEANUP_INTERVAL_MS = 6e4;
55
112
  var DEFAULT_EVENT_BUFFER_MAX_SIZE = 1e3;
56
113
  var DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE = 2e3;
114
+ var DEFAULT_MAX_SESSIONS = 128;
115
+ var DEFAULT_MAX_PENDING_PERMISSIONS_PER_SESSION = 64;
116
+ function parsePositiveInt(raw) {
117
+ if (typeof raw !== "string" || raw.trim() === "") return void 0;
118
+ const parsed = Number.parseInt(raw, 10);
119
+ if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
120
+ return parsed;
121
+ }
122
+ function normalizePositiveNumber(value) {
123
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return void 0;
124
+ return Math.trunc(value);
125
+ }
126
+ function normalizeToolPolicyNames(values) {
127
+ if (!Array.isArray(values) || values.length === 0) return [];
128
+ return values.filter((value) => typeof value === "string").map((value) => value.trim()).filter((value) => value !== "");
129
+ }
57
130
  var SessionManager = class _SessionManager {
58
131
  sessions = /* @__PURE__ */ new Map();
59
132
  runtime = /* @__PURE__ */ new Map();
133
+ drainingSessions = /* @__PURE__ */ new Map();
60
134
  cleanupTimer;
61
135
  sessionTtlMs = DEFAULT_SESSION_TTL_MS;
62
136
  runningSessionMaxMs = DEFAULT_RUNNING_SESSION_MAX_MS;
63
137
  platform;
138
+ maxSessions;
139
+ maxPendingPermissionsPerSession;
140
+ eventBufferMaxSize;
141
+ eventBufferHardMaxSize;
142
+ destroyed = false;
64
143
  constructor(opts) {
65
144
  this.platform = opts?.platform ?? process.platform;
145
+ const envRaw = process.env.CLAUDE_CODE_MCP_MAX_SESSIONS;
146
+ const envParsed = typeof envRaw === "string" && envRaw.trim() !== "" ? Number.parseInt(envRaw, 10) : NaN;
147
+ const configured = opts?.maxSessions ?? (Number.isFinite(envParsed) ? envParsed : void 0);
148
+ this.maxSessions = typeof configured === "number" ? configured <= 0 ? Number.POSITIVE_INFINITY : configured : DEFAULT_MAX_SESSIONS;
149
+ const maxPendingEnvRaw = process.env.CLAUDE_CODE_MCP_MAX_PENDING_PERMISSIONS;
150
+ const maxPendingEnvParsed = typeof maxPendingEnvRaw === "string" && maxPendingEnvRaw.trim() !== "" ? Number.parseInt(maxPendingEnvRaw, 10) : NaN;
151
+ const maxPendingConfigured = opts?.maxPendingPermissionsPerSession ?? (Number.isFinite(maxPendingEnvParsed) ? maxPendingEnvParsed : void 0);
152
+ this.maxPendingPermissionsPerSession = typeof maxPendingConfigured === "number" ? maxPendingConfigured <= 0 ? Number.POSITIVE_INFINITY : maxPendingConfigured : DEFAULT_MAX_PENDING_PERMISSIONS_PER_SESSION;
153
+ const configuredEventBufferMaxSize = normalizePositiveNumber(opts?.eventBufferMaxSize) ?? parsePositiveInt(process.env.CLAUDE_CODE_MCP_EVENT_BUFFER_MAX_SIZE);
154
+ const configuredEventBufferHardMaxSize = normalizePositiveNumber(opts?.eventBufferHardMaxSize) ?? parsePositiveInt(process.env.CLAUDE_CODE_MCP_EVENT_BUFFER_HARD_MAX_SIZE);
155
+ this.eventBufferMaxSize = configuredEventBufferMaxSize ?? DEFAULT_EVENT_BUFFER_MAX_SIZE;
156
+ const initialHardMaxSize = configuredEventBufferHardMaxSize ?? DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE;
157
+ this.eventBufferHardMaxSize = Math.max(this.eventBufferMaxSize, initialHardMaxSize);
158
+ if (initialHardMaxSize < this.eventBufferMaxSize) {
159
+ console.error(
160
+ `[config] CLAUDE_CODE_MCP_EVENT_BUFFER_HARD_MAX_SIZE (${initialHardMaxSize}) is smaller than maxSize (${this.eventBufferMaxSize}); clamped to ${this.eventBufferHardMaxSize}.`
161
+ );
162
+ }
66
163
  this.cleanupTimer = setInterval(() => this.cleanup(), DEFAULT_CLEANUP_INTERVAL_MS);
67
164
  if (this.cleanupTimer.unref) {
68
165
  this.cleanupTimer.unref();
69
166
  }
70
167
  }
168
+ getSessionCount() {
169
+ if (this.destroyed) return 0;
170
+ return this.sessions.size;
171
+ }
172
+ getMaxSessions() {
173
+ return this.maxSessions;
174
+ }
175
+ getMaxPendingPermissionsPerSession() {
176
+ return this.maxPendingPermissionsPerSession;
177
+ }
178
+ getEventBufferConfig() {
179
+ return {
180
+ maxSize: this.eventBufferMaxSize,
181
+ hardMaxSize: this.eventBufferHardMaxSize
182
+ };
183
+ }
184
+ getSessionTtlMs() {
185
+ return this.sessionTtlMs;
186
+ }
187
+ getRunningSessionMaxMs() {
188
+ return this.runningSessionMaxMs;
189
+ }
190
+ hasCapacityFor(additionalSessions) {
191
+ if (this.destroyed) return false;
192
+ if (additionalSessions <= 0) return true;
193
+ return this.sessions.size + additionalSessions <= this.maxSessions;
194
+ }
71
195
  create(params) {
196
+ if (this.destroyed) {
197
+ throw new Error("SessionManager is destroyed and no longer accepts new sessions.");
198
+ }
72
199
  const now = (/* @__PURE__ */ new Date()).toISOString();
73
200
  const existing = this.sessions.get(params.sessionId);
74
201
  if (existing) {
@@ -86,6 +213,7 @@ var SessionManager = class _SessionManager {
86
213
  permissionMode: params.permissionMode ?? "default",
87
214
  allowedTools: params.allowedTools,
88
215
  disallowedTools: params.disallowedTools,
216
+ strictAllowedTools: params.strictAllowedTools,
89
217
  tools: params.tools,
90
218
  maxTurns: params.maxTurns,
91
219
  systemPrompt: params.systemPrompt,
@@ -109,14 +237,15 @@ var SessionManager = class _SessionManager {
109
237
  debug: params.debug,
110
238
  debugFile: params.debugFile,
111
239
  env: params.env,
112
- abortController: params.abortController
240
+ abortController: params.abortController,
241
+ queryInterrupt: params.queryInterrupt
113
242
  };
114
243
  this.sessions.set(params.sessionId, info);
115
244
  this.runtime.set(params.sessionId, {
116
245
  buffer: {
117
246
  events: [],
118
- maxSize: DEFAULT_EVENT_BUFFER_MAX_SIZE,
119
- hardMaxSize: DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE,
247
+ maxSize: this.eventBufferMaxSize,
248
+ hardMaxSize: this.eventBufferHardMaxSize,
120
249
  nextId: 0
121
250
  },
122
251
  pendingPermissions: /* @__PURE__ */ new Map()
@@ -124,46 +253,62 @@ var SessionManager = class _SessionManager {
124
253
  return info;
125
254
  }
126
255
  get(sessionId) {
256
+ if (this.destroyed) return void 0;
127
257
  return this.sessions.get(sessionId);
128
258
  }
129
259
  updateStatus(sessionId, status) {
130
260
  return this.update(sessionId, { status });
131
261
  }
132
262
  list() {
263
+ if (this.destroyed) return [];
133
264
  return Array.from(this.sessions.values());
134
265
  }
135
266
  update(sessionId, patch) {
267
+ if (this.destroyed) return void 0;
136
268
  const info = this.sessions.get(sessionId);
137
269
  if (!info) return void 0;
138
270
  Object.assign(info, patch, { lastActiveAt: (/* @__PURE__ */ new Date()).toISOString() });
139
271
  return info;
140
272
  }
141
273
  /**
142
- * Atomically transition a session from an expected status to "running".
274
+ * Atomically transition a session's status from expectedStatus to "running".
275
+ * This only covers synchronous state mutation within SessionManager.
143
276
  * Returns the session if successful, undefined if the session doesn't exist
144
277
  * or its current status doesn't match `expectedStatus`.
145
278
  */
146
279
  tryAcquire(sessionId, expectedStatus, abortController) {
280
+ if (this.destroyed) return void 0;
147
281
  if (expectedStatus !== "idle" && expectedStatus !== "error") return void 0;
148
282
  const info = this.sessions.get(sessionId);
149
283
  if (!info || info.status !== expectedStatus) return void 0;
150
284
  info.status = "running";
151
285
  info.abortController = abortController;
286
+ info.queryInterrupt = void 0;
152
287
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
153
288
  this.clearTerminalEvents(sessionId);
154
289
  return info;
155
290
  }
156
291
  cancel(sessionId, opts) {
292
+ if (this.destroyed) return false;
157
293
  const info = this.sessions.get(sessionId);
158
294
  if (!info) return false;
159
295
  if (info.status !== "running" && info.status !== "waiting_permission") return false;
160
296
  if (info.status === "waiting_permission") {
161
297
  this.finishAllPending(
162
298
  sessionId,
163
- { behavior: "deny", message: "Session cancelled", interrupt: true },
299
+ // `cancel()` already aborts the running query below. Avoid sending an additional
300
+ // interrupt=true control response while the stream is being torn down, which can race
301
+ // into SDK-level "Operation aborted" write errors on stdio clients.
302
+ { behavior: "deny", message: "Session cancelled", interrupt: false },
164
303
  "cancel"
165
304
  );
166
305
  }
306
+ if (info.queryInterrupt) {
307
+ try {
308
+ info.queryInterrupt();
309
+ } catch {
310
+ }
311
+ }
167
312
  if (info.abortController) {
168
313
  info.abortController.abort();
169
314
  }
@@ -171,35 +316,77 @@ var SessionManager = class _SessionManager {
171
316
  info.cancelledAt = (/* @__PURE__ */ new Date()).toISOString();
172
317
  info.cancelledReason = opts?.reason ?? "Session cancelled";
173
318
  info.cancelledSource = opts?.source ?? "cancel";
319
+ info.queryInterrupt = void 0;
320
+ info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
321
+ return true;
322
+ }
323
+ interrupt(sessionId, opts) {
324
+ if (this.destroyed) return false;
325
+ const info = this.sessions.get(sessionId);
326
+ if (!info) return false;
327
+ if (info.status !== "running" && info.status !== "waiting_permission") return false;
328
+ if (info.status === "waiting_permission") {
329
+ this.finishAllPending(
330
+ sessionId,
331
+ { behavior: "deny", message: opts?.reason ?? "Session interrupted", interrupt: true },
332
+ "interrupt"
333
+ );
334
+ }
335
+ if (info.queryInterrupt) {
336
+ try {
337
+ info.queryInterrupt();
338
+ } catch {
339
+ }
340
+ }
341
+ if (info.abortController) {
342
+ info.abortController.abort();
343
+ }
344
+ this.pushEvent(sessionId, {
345
+ type: "progress",
346
+ data: {
347
+ type: "interrupted",
348
+ reason: opts?.reason ?? "Session interrupted",
349
+ source: opts?.source ?? "interrupt"
350
+ },
351
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
352
+ });
174
353
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
175
354
  return true;
176
355
  }
177
356
  delete(sessionId) {
357
+ if (this.destroyed) return false;
178
358
  this.finishAllPending(
179
359
  sessionId,
180
360
  { behavior: "deny", message: "Session deleted", interrupt: true },
181
- "cleanup"
361
+ "cleanup",
362
+ { restoreRunning: false }
182
363
  );
364
+ this.drainingSessions.delete(sessionId);
183
365
  this.runtime.delete(sessionId);
184
366
  return this.sessions.delete(sessionId);
185
367
  }
186
368
  setResult(sessionId, result) {
369
+ if (this.destroyed) return;
187
370
  const state = this.runtime.get(sessionId);
188
371
  if (!state) return;
189
372
  state.storedResult = result;
190
373
  }
191
374
  getResult(sessionId) {
375
+ if (this.destroyed) return void 0;
192
376
  return this.runtime.get(sessionId)?.storedResult;
193
377
  }
194
378
  setInitTools(sessionId, tools) {
379
+ if (this.destroyed) return;
195
380
  const state = this.runtime.get(sessionId);
196
381
  if (!state) return;
197
382
  state.initTools = tools;
198
383
  }
199
384
  getInitTools(sessionId) {
385
+ if (this.destroyed) return void 0;
200
386
  return this.runtime.get(sessionId)?.initTools;
201
387
  }
202
388
  pushEvent(sessionId, event) {
389
+ if (this.destroyed) return void 0;
203
390
  const state = this.runtime.get(sessionId);
204
391
  if (!state) return void 0;
205
392
  const full = _SessionManager.pushEvent(
@@ -217,25 +404,75 @@ var SessionManager = class _SessionManager {
217
404
  return full;
218
405
  }
219
406
  getLastEventId(sessionId) {
407
+ if (this.destroyed) return void 0;
220
408
  const state = this.runtime.get(sessionId);
221
409
  if (!state) return void 0;
222
410
  return state.buffer.nextId > 0 ? state.buffer.nextId - 1 : void 0;
223
411
  }
224
412
  readEvents(sessionId, cursor) {
413
+ if (this.destroyed) return { events: [], nextCursor: cursor ?? 0 };
225
414
  const state = this.runtime.get(sessionId);
226
415
  if (!state) return { events: [], nextCursor: cursor ?? 0 };
227
416
  return _SessionManager.readEvents(state.buffer, cursor);
228
417
  }
229
418
  clearTerminalEvents(sessionId) {
419
+ if (this.destroyed) return;
230
420
  const state = this.runtime.get(sessionId);
231
421
  if (!state) return;
232
422
  _SessionManager.clearTerminalEvents(state.buffer);
233
423
  }
234
424
  setPendingPermission(sessionId, req, finish, timeoutMs) {
425
+ if (this.destroyed) return false;
235
426
  const state = this.runtime.get(sessionId);
236
427
  const info = this.sessions.get(sessionId);
237
428
  if (!state || !info) return false;
429
+ if (info.status !== "running" && info.status !== "waiting_permission") {
430
+ try {
431
+ finish({
432
+ behavior: "deny",
433
+ message: `Session is not accepting permission requests (status: ${info.status}).`,
434
+ interrupt: true
435
+ });
436
+ } catch {
437
+ }
438
+ return false;
439
+ }
440
+ if (this.isDraining(sessionId)) {
441
+ try {
442
+ finish({
443
+ behavior: "deny",
444
+ message: "Session is finishing pending permission requests.",
445
+ interrupt: true
446
+ });
447
+ } catch {
448
+ }
449
+ return false;
450
+ }
238
451
  if (!state.pendingPermissions.has(req.requestId)) {
452
+ if (state.pendingPermissions.size >= this.maxPendingPermissionsPerSession) {
453
+ const deny = {
454
+ behavior: "deny",
455
+ message: "Too many pending permission requests for this session.",
456
+ interrupt: false
457
+ };
458
+ this.pushEvent(sessionId, {
459
+ type: "permission_result",
460
+ data: {
461
+ requestId: req.requestId,
462
+ toolName: req.toolName,
463
+ behavior: "deny",
464
+ source: "policy",
465
+ message: deny.message,
466
+ interrupt: deny.interrupt
467
+ },
468
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
469
+ });
470
+ try {
471
+ finish(deny);
472
+ } catch {
473
+ }
474
+ return false;
475
+ }
239
476
  const inferredExpiresAt = new Date(Date.now() + timeoutMs).toISOString();
240
477
  const record = {
241
478
  ...req,
@@ -267,14 +504,79 @@ var SessionManager = class _SessionManager {
267
504
  return false;
268
505
  }
269
506
  getPendingPermissionCount(sessionId) {
507
+ if (this.destroyed) return 0;
270
508
  return this.runtime.get(sessionId)?.pendingPermissions.size ?? 0;
271
509
  }
510
+ getEventCount(sessionId) {
511
+ if (this.destroyed) return 0;
512
+ return this.runtime.get(sessionId)?.buffer.events.length ?? 0;
513
+ }
514
+ getCurrentCursor(sessionId) {
515
+ if (this.destroyed) return void 0;
516
+ const state = this.runtime.get(sessionId);
517
+ if (!state) return void 0;
518
+ return state.buffer.nextId;
519
+ }
520
+ getRemainingTtlMs(sessionId) {
521
+ if (this.destroyed) return void 0;
522
+ const session = this.sessions.get(sessionId);
523
+ if (!session) return void 0;
524
+ const lastActiveMs = Date.parse(session.lastActiveAt);
525
+ if (!Number.isFinite(lastActiveMs)) return void 0;
526
+ const limitMs = session.status === "running" || session.status === "waiting_permission" ? this.runningSessionMaxMs : this.sessionTtlMs;
527
+ return Math.max(0, limitMs - (Date.now() - lastActiveMs));
528
+ }
529
+ getRuntimeToolStats() {
530
+ if (this.destroyed) {
531
+ return { sessionsWithInitTools: 0, runtimeDiscoveredUniqueCount: 0 };
532
+ }
533
+ const unique = /* @__PURE__ */ new Set();
534
+ let sessionsWithInitTools = 0;
535
+ for (const state of this.runtime.values()) {
536
+ if (!Array.isArray(state.initTools) || state.initTools.length === 0) continue;
537
+ sessionsWithInitTools += 1;
538
+ for (const tool of state.initTools) {
539
+ if (typeof tool === "string" && tool.trim() !== "") {
540
+ unique.add(tool);
541
+ }
542
+ }
543
+ }
544
+ return {
545
+ sessionsWithInitTools,
546
+ runtimeDiscoveredUniqueCount: unique.size
547
+ };
548
+ }
272
549
  listPendingPermissions(sessionId) {
550
+ if (this.destroyed) return [];
273
551
  const state = this.runtime.get(sessionId);
274
552
  if (!state) return [];
275
553
  return Array.from(state.pendingPermissions.values()).map((p) => p.record).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
276
554
  }
555
+ getPendingPermission(sessionId, requestId) {
556
+ if (this.destroyed) return void 0;
557
+ const state = this.runtime.get(sessionId);
558
+ return state?.pendingPermissions.get(requestId)?.record;
559
+ }
560
+ allowToolForSession(sessionId, toolName) {
561
+ if (this.destroyed) return false;
562
+ const info = this.sessions.get(sessionId);
563
+ if (!info) return false;
564
+ const normalized = toolName.trim();
565
+ if (normalized === "") return false;
566
+ const disallowed = normalizeToolPolicyNames(info.disallowedTools);
567
+ if (disallowed.includes(normalized)) {
568
+ return false;
569
+ }
570
+ const allowed = Array.isArray(info.allowedTools) ? [...info.allowedTools] : [];
571
+ if (!allowed.includes(normalized)) {
572
+ allowed.push(normalized);
573
+ info.allowedTools = allowed;
574
+ info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
575
+ }
576
+ return true;
577
+ }
277
578
  finishRequest(sessionId, requestId, result, source) {
579
+ if (this.destroyed) return false;
278
580
  const state = this.runtime.get(sessionId);
279
581
  const info = this.sessions.get(sessionId);
280
582
  if (!state || !info) return false;
@@ -282,11 +584,12 @@ var SessionManager = class _SessionManager {
282
584
  if (!pending) return false;
283
585
  let finalResult = result;
284
586
  if (finalResult.behavior === "allow") {
285
- const disallowed = info.disallowedTools;
286
- if (Array.isArray(disallowed) && disallowed.includes(pending.record.toolName) && pending.record.toolName.trim() !== "") {
587
+ const disallowed = normalizeToolPolicyNames(info.disallowedTools);
588
+ const pendingToolName = pending.record.toolName.trim();
589
+ if (pendingToolName !== "" && disallowed.includes(pendingToolName)) {
287
590
  finalResult = {
288
591
  behavior: "deny",
289
- message: `Tool '${pending.record.toolName}' is disallowed by session policy.`,
592
+ message: `Tool '${pendingToolName}' is disallowed by session policy.`,
290
593
  interrupt: false
291
594
  };
292
595
  }
@@ -294,19 +597,25 @@ var SessionManager = class _SessionManager {
294
597
  if (finalResult.behavior === "allow") {
295
598
  const updatedInput = finalResult.updatedInput;
296
599
  const validRecord = updatedInput !== null && updatedInput !== void 0 && typeof updatedInput === "object" && !Array.isArray(updatedInput);
297
- if (!validRecord) {
600
+ const normalizedUpdatedInput = validRecord ? normalizeToolInput(
601
+ pending.record.toolName,
602
+ updatedInput,
603
+ this.platform
604
+ ) : normalizeToolInput(pending.record.toolName, pending.record.input, this.platform);
605
+ const unsupportedPath = findUnsupportedPosixPathInToolInput(
606
+ normalizedUpdatedInput,
607
+ this.platform
608
+ );
609
+ if (unsupportedPath) {
298
610
  finalResult = {
299
- ...finalResult,
300
- updatedInput: normalizePermissionUpdatedInput(pending.record.input)
611
+ behavior: "deny",
612
+ message: `Tool '${pending.record.toolName}' attempted to allow an unsupported POSIX path '${unsupportedPath}' on Windows.`,
613
+ interrupt: false
301
614
  };
302
615
  } else {
303
616
  finalResult = {
304
617
  ...finalResult,
305
- updatedInput: normalizeToolInput(
306
- pending.record.toolName,
307
- updatedInput,
308
- this.platform
309
- )
618
+ updatedInput: normalizedUpdatedInput
310
619
  };
311
620
  }
312
621
  }
@@ -321,6 +630,11 @@ var SessionManager = class _SessionManager {
321
630
  if (finalResult.behavior === "deny") {
322
631
  eventData.message = finalResult.message;
323
632
  eventData.interrupt = finalResult.interrupt;
633
+ } else {
634
+ const allow = finalResult;
635
+ if (allow.updatedInput !== void 0) eventData.updatedInput = allow.updatedInput;
636
+ if (allow.updatedPermissions !== void 0)
637
+ eventData.updatedPermissions = allow.updatedPermissions;
324
638
  }
325
639
  this.pushEvent(sessionId, {
326
640
  type: "permission_result",
@@ -331,21 +645,47 @@ var SessionManager = class _SessionManager {
331
645
  pending.finish(finalResult);
332
646
  } catch {
333
647
  }
334
- if (info.status === "waiting_permission" && state.pendingPermissions.size === 0) {
648
+ if (info.status === "waiting_permission" && state.pendingPermissions.size === 0 && !this.isDraining(sessionId)) {
335
649
  info.status = "running";
336
650
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
337
651
  }
338
652
  return true;
339
653
  }
340
- finishAllPending(sessionId, result, source) {
654
+ finishAllPending(sessionId, result, source, opts) {
655
+ if (this.destroyed) return;
341
656
  const state = this.runtime.get(sessionId);
342
657
  if (!state) return;
343
- for (const requestId of Array.from(state.pendingPermissions.keys())) {
344
- this.finishRequest(sessionId, requestId, result, source);
658
+ if (state.pendingPermissions.size === 0) return;
659
+ this.beginDraining(sessionId);
660
+ try {
661
+ while (state.pendingPermissions.size > 0) {
662
+ const next = state.pendingPermissions.keys().next();
663
+ if (next.done || typeof next.value !== "string") break;
664
+ const requestId = next.value;
665
+ const handled = this.finishRequest(sessionId, requestId, result, source);
666
+ if (!handled) {
667
+ const pending = state.pendingPermissions.get(requestId);
668
+ if (pending?.timeoutId) clearTimeout(pending.timeoutId);
669
+ state.pendingPermissions.delete(requestId);
670
+ try {
671
+ pending?.finish(result);
672
+ } catch {
673
+ }
674
+ }
675
+ }
676
+ } finally {
677
+ this.endDraining(sessionId);
678
+ }
679
+ const restoreRunning = opts?.restoreRunning ?? true;
680
+ const info = this.sessions.get(sessionId);
681
+ if (restoreRunning && info && info.status === "waiting_permission" && state.pendingPermissions.size === 0) {
682
+ info.status = "running";
683
+ info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
345
684
  }
346
685
  }
347
686
  /** Remove sessions that have been idle for too long, or stuck running too long */
348
687
  cleanup() {
688
+ if (this.destroyed) return;
349
689
  const now = Date.now();
350
690
  for (const [id, info] of this.sessions) {
351
691
  const lastActive = new Date(info.lastActiveAt).getTime();
@@ -353,29 +693,42 @@ var SessionManager = class _SessionManager {
353
693
  this.finishAllPending(
354
694
  id,
355
695
  { behavior: "deny", message: "Session expired", interrupt: true },
356
- "cleanup"
696
+ "cleanup",
697
+ { restoreRunning: false }
357
698
  );
699
+ this.drainingSessions.delete(id);
358
700
  this.sessions.delete(id);
359
701
  this.runtime.delete(id);
360
702
  } else if (info.status === "running" && now - lastActive > this.runningSessionMaxMs) {
361
703
  if (info.abortController) info.abortController.abort();
362
- info.status = "error";
704
+ info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
705
+ info.cancelledReason = info.cancelledReason ?? "Session timed out";
706
+ info.cancelledSource = info.cancelledSource ?? "cleanup";
707
+ info.queryInterrupt = void 0;
708
+ info.status = "cancelled";
363
709
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
364
710
  } else if (info.status === "waiting_permission" && now - lastActive > this.runningSessionMaxMs) {
365
711
  this.finishAllPending(
366
712
  id,
367
713
  { behavior: "deny", message: "Session timed out", interrupt: true },
368
- "cleanup"
714
+ "cleanup",
715
+ { restoreRunning: false }
369
716
  );
370
717
  if (info.abortController) info.abortController.abort();
371
- info.status = "error";
718
+ info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
719
+ info.cancelledReason = info.cancelledReason ?? "Session timed out";
720
+ info.cancelledSource = info.cancelledSource ?? "cleanup";
721
+ info.queryInterrupt = void 0;
722
+ info.status = "cancelled";
372
723
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
373
724
  } else if (info.status !== "running" && info.status !== "waiting_permission" && now - lastActive > this.sessionTtlMs) {
374
725
  this.finishAllPending(
375
726
  id,
376
727
  { behavior: "deny", message: "Session expired", interrupt: true },
377
- "cleanup"
728
+ "cleanup",
729
+ { restoreRunning: false }
378
730
  );
731
+ this.drainingSessions.delete(id);
379
732
  this.sessions.delete(id);
380
733
  this.runtime.delete(id);
381
734
  }
@@ -404,6 +757,7 @@ var SessionManager = class _SessionManager {
404
757
  toPublicJSON(info) {
405
758
  const {
406
759
  abortController: _abortController,
760
+ queryInterrupt: _queryInterrupt,
407
761
  cwd: _cwd,
408
762
  systemPrompt: _systemPrompt,
409
763
  agents: _agents,
@@ -419,22 +773,29 @@ var SessionManager = class _SessionManager {
419
773
  return rest;
420
774
  }
421
775
  destroy() {
776
+ if (this.destroyed) return;
422
777
  clearInterval(this.cleanupTimer);
423
778
  for (const info of this.sessions.values()) {
424
779
  this.finishAllPending(
425
780
  info.sessionId,
426
781
  { behavior: "deny", message: "Server shutting down", interrupt: true },
427
- "destroy"
782
+ "destroy",
783
+ { restoreRunning: false }
428
784
  );
429
785
  if ((info.status === "running" || info.status === "waiting_permission") && info.abortController) {
430
786
  info.abortController.abort();
431
787
  }
788
+ info.queryInterrupt = void 0;
432
789
  info.status = "cancelled";
433
790
  info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
434
791
  info.cancelledReason = info.cancelledReason ?? "Server shutting down";
435
792
  info.cancelledSource = info.cancelledSource ?? "destroy";
436
793
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
437
794
  }
795
+ this.drainingSessions.clear();
796
+ this.runtime.clear();
797
+ this.sessions.clear();
798
+ this.destroyed = true;
438
799
  }
439
800
  static pushEvent(buffer, event, isActivePermissionRequest) {
440
801
  const pinned = event.pinned ?? (event.type === "permission_request" || event.type === "permission_result" || event.type === "result" || event.type === "error");
@@ -446,38 +807,61 @@ var SessionManager = class _SessionManager {
446
807
  pinned
447
808
  };
448
809
  buffer.events.push(full);
449
- while (buffer.events.length > buffer.maxSize) {
450
- const idx = buffer.events.findIndex((e) => !e.pinned);
451
- if (idx !== -1) {
452
- buffer.events.splice(idx, 1);
453
- continue;
810
+ _SessionManager.evictEventsToLimits(buffer, isActivePermissionRequest);
811
+ return full;
812
+ }
813
+ static evictEventsToLimits(buffer, isActivePermissionRequest) {
814
+ if (buffer.events.length <= buffer.maxSize && buffer.events.length <= buffer.hardMaxSize) {
815
+ return;
816
+ }
817
+ const droppedIds = /* @__PURE__ */ new Set();
818
+ let remaining = buffer.events.length;
819
+ const dropOldestMatching = (count, predicate) => {
820
+ if (count <= 0) return 0;
821
+ let dropped = 0;
822
+ for (const event of buffer.events) {
823
+ if (dropped >= count) break;
824
+ if (droppedIds.has(event.id)) continue;
825
+ if (!predicate(event)) continue;
826
+ droppedIds.add(event.id);
827
+ dropped += 1;
454
828
  }
455
- const pinnedDropIdx = buffer.events.findIndex((e) => {
456
- if (e.type === "permission_result") return true;
457
- if (e.type === "permission_request") {
458
- const requestId = e.data?.requestId;
459
- if (typeof requestId !== "string") return true;
460
- return isActivePermissionRequest ? !isActivePermissionRequest(requestId) : true;
461
- }
462
- return false;
463
- });
464
- if (pinnedDropIdx === -1) break;
465
- buffer.events.splice(pinnedDropIdx, 1);
466
- }
467
- while (buffer.events.length > buffer.hardMaxSize) {
468
- const idx = buffer.events.findIndex((e) => {
469
- if (e.type === "permission_request") {
470
- const requestId = e.data?.requestId;
471
- if (typeof requestId !== "string") return true;
472
- return isActivePermissionRequest ? !isActivePermissionRequest(requestId) : true;
473
- }
474
- if (e.type === "permission_result") return true;
475
- return false;
476
- });
477
- if (idx === -1) break;
478
- buffer.events.splice(idx, 1);
829
+ if (dropped > 0) {
830
+ remaining -= dropped;
831
+ }
832
+ return dropped;
833
+ };
834
+ const isDroppablePermissionEvent = (event) => {
835
+ if (event.type === "permission_result") return true;
836
+ if (event.type !== "permission_request") return false;
837
+ const requestId = event.data?.requestId;
838
+ if (typeof requestId !== "string") return true;
839
+ return isActivePermissionRequest ? !isActivePermissionRequest(requestId) : true;
840
+ };
841
+ let toDropForSoftLimit = remaining - buffer.maxSize;
842
+ if (toDropForSoftLimit > 0) {
843
+ toDropForSoftLimit -= dropOldestMatching(toDropForSoftLimit, (event) => !event.pinned);
844
+ }
845
+ if (toDropForSoftLimit > 0) {
846
+ toDropForSoftLimit -= dropOldestMatching(toDropForSoftLimit, isDroppablePermissionEvent);
847
+ }
848
+ let toDropForHardLimit = remaining - buffer.hardMaxSize;
849
+ if (toDropForHardLimit > 0) {
850
+ toDropForHardLimit -= dropOldestMatching(toDropForHardLimit, isDroppablePermissionEvent);
851
+ }
852
+ if (droppedIds.size > 0) {
853
+ buffer.events = buffer.events.filter((event) => !droppedIds.has(event.id));
479
854
  }
480
- return full;
855
+ }
856
+ static lowerBoundByEventId(events, startFrom) {
857
+ let left = 0;
858
+ let right = events.length;
859
+ while (left < right) {
860
+ const mid = Math.floor((left + right) / 2);
861
+ if (events[mid].id < startFrom) left = mid + 1;
862
+ else right = mid;
863
+ }
864
+ return left;
481
865
  }
482
866
  static readEvents(buffer, cursor) {
483
867
  let cursorResetTo;
@@ -487,22 +871,52 @@ var SessionManager = class _SessionManager {
487
871
  if (earliest == null && buffer.nextId > cursor) cursorResetTo = buffer.nextId;
488
872
  }
489
873
  const startFrom = cursorResetTo ?? cursor ?? 0;
490
- const filtered = buffer.events.filter((e) => e.id >= startFrom);
874
+ const startIndex = _SessionManager.lowerBoundByEventId(buffer.events, startFrom);
875
+ const filtered = buffer.events.slice(startIndex);
491
876
  const nextCursor = filtered.length > 0 ? filtered[filtered.length - 1].id + 1 : startFrom;
492
877
  return { events: filtered, nextCursor, cursorResetTo };
493
878
  }
494
879
  static clearTerminalEvents(buffer) {
495
880
  buffer.events = buffer.events.filter((e) => e.type !== "result" && e.type !== "error");
496
881
  }
882
+ isDraining(sessionId) {
883
+ return (this.drainingSessions.get(sessionId) ?? 0) > 0;
884
+ }
885
+ beginDraining(sessionId) {
886
+ this.drainingSessions.set(sessionId, (this.drainingSessions.get(sessionId) ?? 0) + 1);
887
+ }
888
+ endDraining(sessionId) {
889
+ const current = this.drainingSessions.get(sessionId) ?? 0;
890
+ if (current <= 1) {
891
+ this.drainingSessions.delete(sessionId);
892
+ return;
893
+ }
894
+ this.drainingSessions.set(sessionId, current - 1);
895
+ }
497
896
  };
498
897
 
898
+ // src/tools/claude-code.ts
899
+ import { existsSync as existsSync2, statSync } from "fs";
900
+
499
901
  // src/types.ts
500
902
  var EFFORT_LEVELS = ["low", "medium", "high", "max"];
501
903
  var AGENT_MODELS = ["sonnet", "opus", "haiku", "inherit"];
502
- var SESSION_ACTIONS = ["list", "get", "cancel"];
904
+ var SESSION_ACTIONS = ["list", "get", "cancel", "interrupt"];
503
905
  var DEFAULT_SETTING_SOURCES = ["user", "project", "local"];
504
906
  var CHECK_ACTIONS = ["poll", "respond_permission"];
505
- var CHECK_RESPONSE_MODES = ["minimal", "full"];
907
+ var CHECK_RESPONSE_MODES = ["minimal", "full", "delta_compact"];
908
+ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
909
+ ErrorCode2["INVALID_ARGUMENT"] = "INVALID_ARGUMENT";
910
+ ErrorCode2["SESSION_NOT_FOUND"] = "SESSION_NOT_FOUND";
911
+ ErrorCode2["SESSION_BUSY"] = "SESSION_BUSY";
912
+ ErrorCode2["PERMISSION_REQUEST_NOT_FOUND"] = "PERMISSION_REQUEST_NOT_FOUND";
913
+ ErrorCode2["PERMISSION_DENIED"] = "PERMISSION_DENIED";
914
+ ErrorCode2["RESOURCE_EXHAUSTED"] = "RESOURCE_EXHAUSTED";
915
+ ErrorCode2["TIMEOUT"] = "TIMEOUT";
916
+ ErrorCode2["CANCELLED"] = "CANCELLED";
917
+ ErrorCode2["INTERNAL"] = "INTERNAL";
918
+ return ErrorCode2;
919
+ })(ErrorCode || {});
506
920
 
507
921
  // src/tools/query-consumer.ts
508
922
  import { AbortError, query } from "@anthropic-ai/claude-agent-sdk";
@@ -510,23 +924,98 @@ import { AbortError, query } from "@anthropic-ai/claude-agent-sdk";
510
924
  // src/utils/windows.ts
511
925
  import { existsSync } from "fs";
512
926
  import { execSync } from "child_process";
513
- import path from "path";
514
- var { join, dirname, normalize } = path.win32;
927
+ import path2 from "path";
928
+ var { join, dirname, normalize, isAbsolute } = path2.win32;
929
+ var LONG_PATH_PREFIX = "\\\\?\\";
930
+ var UNC_LONG_PATH_PREFIX = "\\\\?\\UNC\\";
515
931
  function isWindows() {
516
932
  return process.platform === "win32";
517
933
  }
934
+ function normalizeMaybeQuotedPath(raw) {
935
+ return normalize(raw.trim().replace(/^"|"$/g, ""));
936
+ }
937
+ function existsSyncSafe(candidate) {
938
+ try {
939
+ return existsSync(candidate);
940
+ } catch {
941
+ return false;
942
+ }
943
+ }
944
+ function ensureLongPathPrefix(normalized) {
945
+ if (!normalized || normalized.startsWith(LONG_PATH_PREFIX)) return normalized;
946
+ if (!isAbsolute(normalized)) return normalized;
947
+ if (normalized.startsWith("\\\\")) {
948
+ return `${UNC_LONG_PATH_PREFIX}${normalized.slice(2)}`;
949
+ }
950
+ return `${LONG_PATH_PREFIX}${normalized}`;
951
+ }
952
+ function existsPath(raw) {
953
+ if (!raw) return false;
954
+ const normalized = normalizeMaybeQuotedPath(raw);
955
+ if (existsSyncSafe(normalized)) return true;
956
+ const longPath = ensureLongPathPrefix(normalized);
957
+ if (longPath === normalized) return false;
958
+ return existsSyncSafe(longPath);
959
+ }
960
+ function tryWhere(exe) {
961
+ try {
962
+ const output = execSync(`where ${exe}`, {
963
+ encoding: "utf8",
964
+ timeout: 2e3,
965
+ windowsHide: true
966
+ });
967
+ return output.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((p) => normalizeMaybeQuotedPath(p));
968
+ } catch {
969
+ return [];
970
+ }
971
+ }
972
+ function pathEntries() {
973
+ const raw = process.env.PATH;
974
+ if (typeof raw !== "string" || raw.trim() === "") return [];
975
+ return raw.split(path2.delimiter).map((p) => p.trim().replace(/^"|"$/g, "")).filter(Boolean).map((p) => normalizeMaybeQuotedPath(p));
976
+ }
977
+ function tryFromPath(exeNames) {
978
+ const results = [];
979
+ for (const dir of pathEntries()) {
980
+ for (const exe of exeNames) {
981
+ const full = normalize(path2.win32.join(dir, exe));
982
+ if (existsPath(full)) results.push(full);
983
+ }
984
+ }
985
+ return results;
986
+ }
987
+ function firstExisting(candidates) {
988
+ for (const p of candidates) {
989
+ if (p && existsPath(p)) return p;
990
+ }
991
+ return null;
992
+ }
993
+ function isWindowsSystemBash(pathLike) {
994
+ const p = normalizeMaybeQuotedPath(pathLike).toLowerCase();
995
+ 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");
996
+ }
518
997
  function findGitBash() {
519
998
  const envPathRaw = process.env.CLAUDE_CODE_GIT_BASH_PATH;
520
999
  if (envPathRaw && envPathRaw.trim() !== "") {
521
- const envPath = normalize(envPathRaw.trim().replace(/^"|"$/g, ""));
522
- if (existsSync(envPath)) return envPath;
523
- return null;
1000
+ const envPath = normalizeMaybeQuotedPath(envPathRaw);
1001
+ if (existsPath(envPath)) return envPath;
1002
+ }
1003
+ const programFilesRoots = [
1004
+ process.env.ProgramW6432,
1005
+ process.env.ProgramFiles,
1006
+ process.env["ProgramFiles(x86)"]
1007
+ ].filter((v) => typeof v === "string" && v.trim() !== "");
1008
+ const defaultCandidates = [];
1009
+ for (const root of programFilesRoots) {
1010
+ const base = join(normalizeMaybeQuotedPath(root), "Git");
1011
+ defaultCandidates.push(join(base, "bin", "bash.exe"));
1012
+ defaultCandidates.push(join(base, "usr", "bin", "bash.exe"));
524
1013
  }
1014
+ const fromDefault = firstExisting(defaultCandidates);
1015
+ if (fromDefault) return fromDefault;
525
1016
  try {
526
- const output = execSync("where git", { encoding: "utf8" });
527
- const gitCandidates = output.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
528
- for (const gitPathRaw of gitCandidates) {
529
- const gitPath = normalize(gitPathRaw.replace(/^"|"$/g, ""));
1017
+ const gitCandidates = [...tryFromPath(["git.exe", "git.cmd", "git.bat"]), ...tryWhere("git")];
1018
+ for (const gitPath of gitCandidates) {
530
1019
  if (!gitPath) continue;
531
1020
  const gitDir = dirname(gitPath);
532
1021
  const gitDirLower = gitDir.toLowerCase();
@@ -548,19 +1037,38 @@ function findGitBash() {
548
1037
  bashCandidates.push(join(root, "mingw64", "bin", "bash.exe"));
549
1038
  }
550
1039
  for (const bashPath of bashCandidates) {
551
- const normalized = normalize(bashPath);
552
- if (existsSync(normalized)) return normalized;
1040
+ const normalizedPath = normalize(bashPath);
1041
+ if (existsPath(normalizedPath)) return normalizedPath;
553
1042
  }
554
1043
  }
555
1044
  } catch {
556
1045
  }
1046
+ const fromWhereBash = firstExisting(
1047
+ [...tryFromPath(["bash.exe"]), ...tryWhere("bash")].filter(
1048
+ (p) => p.toLowerCase().endsWith("\\bash.exe") && !isWindowsSystemBash(p)
1049
+ )
1050
+ );
1051
+ if (fromWhereBash) return fromWhereBash;
557
1052
  return null;
558
1053
  }
559
1054
  function checkWindowsBashAvailability() {
560
1055
  if (!isWindows()) return;
1056
+ const envPathRaw = process.env.CLAUDE_CODE_GIT_BASH_PATH;
1057
+ const envPath = envPathRaw && envPathRaw.trim() !== "" ? normalizeMaybeQuotedPath(envPathRaw) : null;
1058
+ const envValid = !!(envPath && existsPath(envPath));
561
1059
  const bashPath = findGitBash();
562
1060
  if (bashPath) {
563
- console.error(`[windows] Git Bash detected: ${bashPath}`);
1061
+ if (!envValid) {
1062
+ process.env.CLAUDE_CODE_GIT_BASH_PATH = bashPath;
1063
+ if (envPathRaw && envPathRaw.trim() !== "") {
1064
+ console.error(
1065
+ `[windows] WARNING: CLAUDE_CODE_GIT_BASH_PATH is set to "${envPathRaw}" but the file does not exist.`
1066
+ );
1067
+ }
1068
+ console.error(`[windows] Git Bash detected: ${bashPath} (set CLAUDE_CODE_GIT_BASH_PATH)`);
1069
+ } else {
1070
+ console.error(`[windows] Git Bash detected: ${bashPath}`);
1071
+ }
564
1072
  return;
565
1073
  }
566
1074
  const hint = process.env.CLAUDE_CODE_GIT_BASH_PATH ? `CLAUDE_CODE_GIT_BASH_PATH is set to "${process.env.CLAUDE_CODE_GIT_BASH_PATH}" but the file does not exist.` : "CLAUDE_CODE_GIT_BASH_PATH is not set and git was not found in PATH.";
@@ -577,15 +1085,32 @@ function checkWindowsBashAvailability() {
577
1085
  var WINDOWS_BASH_HINT = '\n\n[Windows] The Claude Code CLI requires Git Bash. Set CLAUDE_CODE_GIT_BASH_PATH in your MCP server config or system environment. See README.md "Windows Support" section for details.';
578
1086
  function enhanceWindowsError(errorMessage) {
579
1087
  if (!isWindows()) return errorMessage;
580
- if (errorMessage.includes("git-bash") || errorMessage.includes("bash.exe") || errorMessage.includes("CLAUDE_CODE_GIT_BASH_PATH")) {
1088
+ const lower = errorMessage.toLowerCase();
1089
+ const looksLikeMissingBash = lower.includes("enoent") && (lower.includes("bash") || lower.includes("git-bash")) || lower.includes("claude code on windows requires git-bash");
1090
+ if (looksLikeMissingBash || lower.includes("claude_code_git_bash_path")) {
581
1091
  return errorMessage + WINDOWS_BASH_HINT;
582
1092
  }
583
1093
  return errorMessage;
584
1094
  }
585
1095
 
1096
+ // src/utils/permission-updated-input.ts
1097
+ function normalizePermissionUpdatedInput(input) {
1098
+ if (input && typeof input === "object" && !Array.isArray(input)) {
1099
+ return input;
1100
+ }
1101
+ return { input };
1102
+ }
1103
+
586
1104
  // src/tools/query-consumer.ts
587
1105
  var MAX_TRANSIENT_RETRIES = 3;
588
1106
  var INITIAL_RETRY_DELAY_MS = 1e3;
1107
+ var DEFAULT_PERMISSION_REQUEST_TIMEOUT_MS = 6e4;
1108
+ var MAX_PERMISSION_REQUEST_TIMEOUT_MS = 5 * 6e4;
1109
+ var MAX_PREINIT_BUFFER_MESSAGES = 200;
1110
+ function clampPermissionRequestTimeoutMs(ms) {
1111
+ if (!Number.isFinite(ms) || ms <= 0) return DEFAULT_PERMISSION_REQUEST_TIMEOUT_MS;
1112
+ return Math.min(ms, MAX_PERMISSION_REQUEST_TIMEOUT_MS);
1113
+ }
589
1114
  function classifyError(err, abortSignal) {
590
1115
  if (abortSignal.aborted) return "abort";
591
1116
  if (err instanceof AbortError || err instanceof Error && err.name === "AbortError") {
@@ -609,13 +1134,21 @@ function describeTool(toolName, toolCache) {
609
1134
  const found = tools?.find((t) => t.name === toolName);
610
1135
  return found?.description;
611
1136
  }
1137
+ function normalizePolicyToolNames(tools) {
1138
+ if (!Array.isArray(tools) || tools.length === 0) return [];
1139
+ return tools.filter((tool) => typeof tool === "string").map((tool) => tool.trim()).filter((tool) => tool !== "");
1140
+ }
612
1141
  function sdkResultToAgentResult(result) {
1142
+ const sessionTotalTurns = result.session_total_turns;
1143
+ const sessionTotalCostUsd = result.session_total_cost_usd;
613
1144
  const base = {
614
1145
  sessionId: result.session_id,
615
1146
  durationMs: result.duration_ms,
616
1147
  durationApiMs: result.duration_api_ms,
617
1148
  numTurns: result.num_turns,
618
1149
  totalCostUsd: result.total_cost_usd,
1150
+ sessionTotalTurns: typeof sessionTotalTurns === "number" ? sessionTotalTurns : void 0,
1151
+ sessionTotalCostUsd: typeof sessionTotalCostUsd === "number" ? sessionTotalCostUsd : void 0,
619
1152
  stopReason: result.stop_reason,
620
1153
  usage: result.usage,
621
1154
  modelUsage: result.modelUsage,
@@ -727,15 +1260,46 @@ function consumeQuery(params) {
727
1260
  return activeSessionId;
728
1261
  };
729
1262
  let initTimeoutId;
1263
+ const permissionRequestTimeoutMs = clampPermissionRequestTimeoutMs(
1264
+ params.permissionRequestTimeoutMs
1265
+ );
730
1266
  const canUseTool = async (toolName, input, options2) => {
731
1267
  const sessionId = await getSessionId();
1268
+ const normalizedToolName = toolName.trim();
732
1269
  const normalizedInput = normalizeToolInput(toolName, input, params.platform);
1270
+ const unsupportedPath = findUnsupportedPosixPathInToolInput(normalizedInput, params.platform);
1271
+ if (unsupportedPath) {
1272
+ return {
1273
+ behavior: "deny",
1274
+ message: `Tool '${normalizedToolName || toolName}' requested unsupported POSIX path '${unsupportedPath}' on Windows.`,
1275
+ interrupt: false
1276
+ };
1277
+ }
1278
+ if (typeof options2.blockedPath === "string" && isUnsupportedPosixAbsolutePath(options2.blockedPath, params.platform)) {
1279
+ return {
1280
+ behavior: "deny",
1281
+ message: `Tool '${normalizedToolName || toolName}' requested blocked path '${options2.blockedPath}' that is unsupported on Windows.`,
1282
+ interrupt: false
1283
+ };
1284
+ }
733
1285
  const sessionInfo = params.sessionManager.get(sessionId);
734
1286
  if (sessionInfo) {
735
- if (Array.isArray(sessionInfo.disallowedTools) && sessionInfo.disallowedTools.includes(toolName)) {
736
- return { behavior: "deny", message: `Tool '${toolName}' is disallowed by session policy.` };
1287
+ const disallowedTools = normalizePolicyToolNames(sessionInfo.disallowedTools);
1288
+ const allowedTools = normalizePolicyToolNames(sessionInfo.allowedTools);
1289
+ if (normalizedToolName !== "" && disallowedTools.includes(normalizedToolName)) {
1290
+ return {
1291
+ behavior: "deny",
1292
+ message: `Tool '${normalizedToolName}' is disallowed by session policy.`
1293
+ };
1294
+ }
1295
+ if (sessionInfo.strictAllowedTools === true && normalizedToolName !== "" && allowedTools.length > 0 && !allowedTools.includes(normalizedToolName)) {
1296
+ return {
1297
+ behavior: "deny",
1298
+ message: `Tool '${normalizedToolName}' is not in allowedTools under strictAllowedTools policy.`,
1299
+ interrupt: false
1300
+ };
737
1301
  }
738
- if (!options2.blockedPath && Array.isArray(sessionInfo.allowedTools) && sessionInfo.allowedTools.includes(toolName)) {
1302
+ if (!options2.blockedPath && normalizedToolName !== "" && allowedTools.includes(normalizedToolName)) {
739
1303
  return {
740
1304
  behavior: "allow",
741
1305
  updatedInput: normalizePermissionUpdatedInput(normalizedInput)
@@ -744,7 +1308,7 @@ function consumeQuery(params) {
744
1308
  }
745
1309
  const requestId = `${options2.toolUseID}:${toolName}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
746
1310
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
747
- const timeoutMs = params.permissionRequestTimeoutMs;
1311
+ const timeoutMs = permissionRequestTimeoutMs;
748
1312
  const expiresAt = new Date(Date.now() + timeoutMs).toISOString();
749
1313
  const record = {
750
1314
  requestId,
@@ -781,10 +1345,14 @@ function consumeQuery(params) {
781
1345
  sessionId,
782
1346
  record,
783
1347
  finish,
784
- params.permissionRequestTimeoutMs
1348
+ permissionRequestTimeoutMs
785
1349
  );
786
1350
  if (!registered) {
787
- finish({ behavior: "deny", message: "Session no longer exists.", interrupt: true });
1351
+ finish({
1352
+ behavior: "deny",
1353
+ message: "Session no longer exists.",
1354
+ interrupt: true
1355
+ });
788
1356
  return;
789
1357
  }
790
1358
  options2.signal.addEventListener("abort", abortListener, { once: true });
@@ -819,6 +1387,7 @@ function consumeQuery(params) {
819
1387
  };
820
1388
  const done = (async () => {
821
1389
  const preInit = [];
1390
+ let preInitDropped = 0;
822
1391
  if (shouldWaitForInit) {
823
1392
  initTimeoutId = setTimeout(() => {
824
1393
  close();
@@ -843,6 +1412,13 @@ function consumeQuery(params) {
843
1412
  sessionIdResolved = true;
844
1413
  resolveSessionId(activeSessionId);
845
1414
  if (initTimeoutId) clearTimeout(initTimeoutId);
1415
+ if (preInitDropped > 0) {
1416
+ params.sessionManager.pushEvent(activeSessionId, {
1417
+ type: "progress",
1418
+ data: { type: "pre_init_dropped", dropped: preInitDropped },
1419
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1420
+ });
1421
+ }
846
1422
  for (const buffered of preInit) {
847
1423
  const event2 = messageToEvent(buffered);
848
1424
  if (!event2) continue;
@@ -858,36 +1434,53 @@ function consumeQuery(params) {
858
1434
  }
859
1435
  if (shouldWaitForInit && !sessionIdResolved) {
860
1436
  preInit.push(message);
1437
+ if (preInit.length > MAX_PREINIT_BUFFER_MESSAGES) {
1438
+ preInit.shift();
1439
+ preInitDropped++;
1440
+ }
861
1441
  continue;
862
1442
  }
863
1443
  if (message.type === "result") {
864
1444
  const sessionId2 = message.session_id ?? await getSessionId();
865
1445
  const agentResult = sdkResultToAgentResult(message);
1446
+ const current = params.sessionManager.get(sessionId2);
1447
+ const previousTotalTurns = current?.totalTurns ?? 0;
1448
+ const previousTotalCostUsd = current?.totalCostUsd ?? 0;
1449
+ const computedTotalTurns = previousTotalTurns + agentResult.numTurns;
1450
+ const computedTotalCostUsd = previousTotalCostUsd + agentResult.totalCostUsd;
1451
+ const sessionTotalTurns = typeof agentResult.sessionTotalTurns === "number" ? Math.max(agentResult.sessionTotalTurns, computedTotalTurns) : computedTotalTurns;
1452
+ const sessionTotalCostUsd = typeof agentResult.sessionTotalCostUsd === "number" ? Math.max(agentResult.sessionTotalCostUsd, computedTotalCostUsd) : computedTotalCostUsd;
1453
+ const resultWithSessionTotals = {
1454
+ ...agentResult,
1455
+ sessionTotalTurns,
1456
+ sessionTotalCostUsd
1457
+ };
866
1458
  const stored = {
867
1459
  type: agentResult.isError ? "error" : "result",
868
- result: agentResult,
1460
+ result: resultWithSessionTotals,
869
1461
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
870
1462
  };
871
1463
  params.sessionManager.setResult(sessionId2, stored);
872
1464
  params.sessionManager.clearTerminalEvents(sessionId2);
873
1465
  params.sessionManager.pushEvent(sessionId2, {
874
1466
  type: agentResult.isError ? "error" : "result",
875
- data: agentResult,
1467
+ data: resultWithSessionTotals,
876
1468
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
877
1469
  });
878
- const current = params.sessionManager.get(sessionId2);
879
1470
  if (current && current.status !== "cancelled") {
880
1471
  params.sessionManager.update(sessionId2, {
881
1472
  status: agentResult.isError ? "error" : "idle",
882
- totalTurns: agentResult.numTurns,
883
- totalCostUsd: agentResult.totalCostUsd,
884
- abortController: void 0
1473
+ totalTurns: sessionTotalTurns,
1474
+ totalCostUsd: sessionTotalCostUsd,
1475
+ abortController: void 0,
1476
+ queryInterrupt: void 0
885
1477
  });
886
1478
  } else if (current) {
887
1479
  params.sessionManager.update(sessionId2, {
888
- totalTurns: agentResult.numTurns,
889
- totalCostUsd: agentResult.totalCostUsd,
890
- abortController: void 0
1480
+ totalTurns: sessionTotalTurns,
1481
+ totalCostUsd: sessionTotalCostUsd,
1482
+ abortController: void 0,
1483
+ queryInterrupt: void 0
891
1484
  });
892
1485
  }
893
1486
  return;
@@ -939,7 +1532,8 @@ function consumeQuery(params) {
939
1532
  });
940
1533
  params.sessionManager.update(sessionId, {
941
1534
  status: "error",
942
- abortController: void 0
1535
+ abortController: void 0,
1536
+ queryInterrupt: void 0
943
1537
  });
944
1538
  }
945
1539
  }
@@ -1022,7 +1616,11 @@ function consumeQuery(params) {
1022
1616
  data: agentResult,
1023
1617
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1024
1618
  });
1025
- params.sessionManager.update(sessionId, { status: "error", abortController: void 0 });
1619
+ params.sessionManager.update(sessionId, {
1620
+ status: "error",
1621
+ abortController: void 0,
1622
+ queryInterrupt: void 0
1623
+ });
1026
1624
  }
1027
1625
  return;
1028
1626
  } finally {
@@ -1034,16 +1632,33 @@ function consumeQuery(params) {
1034
1632
  }
1035
1633
 
1036
1634
  // src/utils/resume-token.ts
1037
- import { createHmac } from "crypto";
1635
+ import { createHmac, timingSafeEqual } from "crypto";
1636
+ function getResumeSecrets() {
1637
+ const raw = process.env.CLAUDE_CODE_MCP_RESUME_SECRET;
1638
+ if (typeof raw !== "string") return [];
1639
+ return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
1640
+ }
1038
1641
  function getResumeSecret() {
1039
- const secret = process.env.CLAUDE_CODE_MCP_RESUME_SECRET;
1040
- if (typeof secret !== "string") return void 0;
1041
- const trimmed = secret.trim();
1042
- return trimmed.length > 0 ? trimmed : void 0;
1642
+ return getResumeSecrets()[0];
1043
1643
  }
1044
1644
  function computeResumeToken(sessionId, secret) {
1045
1645
  return createHmac("sha256", secret).update(sessionId).digest("base64url");
1046
1646
  }
1647
+ function timingSafeEqualText(a, b) {
1648
+ const left = Buffer.from(a, "utf8");
1649
+ const rightRaw = Buffer.from(b, "utf8");
1650
+ const right = Buffer.alloc(left.length);
1651
+ rightRaw.copy(right, 0, 0, left.length);
1652
+ const sameLength = rightRaw.length === left.length;
1653
+ return timingSafeEqual(left, right) && sameLength;
1654
+ }
1655
+ function isValidResumeToken(sessionId, token, secrets) {
1656
+ for (const secret of secrets) {
1657
+ const computed = computeResumeToken(sessionId, secret);
1658
+ if (timingSafeEqualText(computed, token)) return true;
1659
+ }
1660
+ return false;
1661
+ }
1047
1662
 
1048
1663
  // src/utils/race-with-abort.ts
1049
1664
  function raceWithAbort(promise, signal, onAbort) {
@@ -1070,7 +1685,7 @@ function raceWithAbort(promise, signal, onAbort) {
1070
1685
 
1071
1686
  // src/utils/build-options.ts
1072
1687
  function buildOptions(src) {
1073
- const opts = { cwd: src.cwd };
1688
+ const opts = { cwd: normalizeWindowsPathLike(src.cwd) };
1074
1689
  if (src.allowedTools !== void 0) opts.allowedTools = src.allowedTools;
1075
1690
  if (src.disallowedTools !== void 0) opts.disallowedTools = src.disallowedTools;
1076
1691
  if (src.tools !== void 0) opts.tools = src.tools;
@@ -1082,13 +1697,13 @@ function buildOptions(src) {
1082
1697
  if (src.effort !== void 0) opts.effort = src.effort;
1083
1698
  if (src.betas !== void 0) opts.betas = src.betas;
1084
1699
  if (src.additionalDirectories !== void 0)
1085
- opts.additionalDirectories = src.additionalDirectories;
1700
+ opts.additionalDirectories = normalizeWindowsPathArray(src.additionalDirectories);
1086
1701
  if (src.outputFormat !== void 0) opts.outputFormat = src.outputFormat;
1087
1702
  if (src.thinking !== void 0) opts.thinking = src.thinking;
1088
1703
  if (src.persistSession !== void 0) opts.persistSession = src.persistSession;
1089
1704
  if (src.resumeSessionAt !== void 0) opts.resumeSessionAt = src.resumeSessionAt;
1090
1705
  if (src.pathToClaudeCodeExecutable !== void 0)
1091
- opts.pathToClaudeCodeExecutable = src.pathToClaudeCodeExecutable;
1706
+ opts.pathToClaudeCodeExecutable = normalizeWindowsPathLike(src.pathToClaudeCodeExecutable);
1092
1707
  if (src.agent !== void 0) opts.agent = src.agent;
1093
1708
  if (src.mcpServers !== void 0) opts.mcpServers = src.mcpServers;
1094
1709
  if (src.sandbox !== void 0) opts.sandbox = src.sandbox;
@@ -1101,14 +1716,54 @@ function buildOptions(src) {
1101
1716
  if (src.settingSources !== void 0) opts.settingSources = src.settingSources;
1102
1717
  else opts.settingSources = DEFAULT_SETTING_SOURCES;
1103
1718
  if (src.debug !== void 0) opts.debug = src.debug;
1104
- if (src.debugFile !== void 0) opts.debugFile = src.debugFile;
1719
+ if (src.debugFile !== void 0) opts.debugFile = normalizeWindowsPathLike(src.debugFile);
1105
1720
  if (src.env !== void 0) opts.env = { ...process.env, ...src.env };
1106
1721
  return opts;
1107
1722
  }
1108
1723
 
1724
+ // src/utils/session-create.ts
1725
+ function toSessionCreateParams(input) {
1726
+ const src = input.source;
1727
+ return {
1728
+ sessionId: input.sessionId,
1729
+ cwd: src.cwd,
1730
+ model: src.model,
1731
+ permissionMode: input.permissionMode,
1732
+ allowedTools: src.allowedTools,
1733
+ disallowedTools: src.disallowedTools,
1734
+ strictAllowedTools: src.strictAllowedTools,
1735
+ tools: src.tools,
1736
+ maxTurns: src.maxTurns,
1737
+ systemPrompt: src.systemPrompt,
1738
+ agents: src.agents,
1739
+ maxBudgetUsd: src.maxBudgetUsd,
1740
+ effort: src.effort,
1741
+ betas: src.betas,
1742
+ additionalDirectories: src.additionalDirectories,
1743
+ outputFormat: src.outputFormat,
1744
+ thinking: src.thinking,
1745
+ persistSession: src.persistSession,
1746
+ pathToClaudeCodeExecutable: src.pathToClaudeCodeExecutable,
1747
+ agent: src.agent,
1748
+ mcpServers: src.mcpServers,
1749
+ sandbox: src.sandbox,
1750
+ fallbackModel: src.fallbackModel,
1751
+ enableFileCheckpointing: src.enableFileCheckpointing,
1752
+ includePartialMessages: src.includePartialMessages,
1753
+ strictMcpConfig: src.strictMcpConfig,
1754
+ settingSources: src.settingSources ?? DEFAULT_SETTING_SOURCES,
1755
+ debug: src.debug,
1756
+ debugFile: src.debugFile,
1757
+ env: src.env,
1758
+ abortController: input.abortController,
1759
+ queryInterrupt: input.queryInterrupt
1760
+ };
1761
+ }
1762
+
1109
1763
  // src/tools/claude-code.ts
1110
1764
  async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, requestSignal) {
1111
- const cwd = input.cwd !== void 0 ? input.cwd : serverCwd;
1765
+ const cwdProvided = input.cwd !== void 0;
1766
+ const cwd = cwdProvided ? input.cwd : serverCwd;
1112
1767
  if (typeof cwd !== "string" || cwd.trim() === "") {
1113
1768
  return {
1114
1769
  sessionId: "",
@@ -1116,63 +1771,96 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1116
1771
  error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a non-empty string.`
1117
1772
  };
1118
1773
  }
1774
+ const normalizedCwd = normalizeWindowsPathLike(cwd);
1775
+ if (cwdProvided && !existsSync2(normalizedCwd)) {
1776
+ return {
1777
+ sessionId: "",
1778
+ status: "error",
1779
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd path does not exist: ${normalizedCwd}`
1780
+ };
1781
+ }
1782
+ if (cwdProvided) {
1783
+ try {
1784
+ if (!statSync(normalizedCwd).isDirectory()) {
1785
+ return {
1786
+ sessionId: "",
1787
+ status: "error",
1788
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a directory: ${normalizedCwd}`
1789
+ };
1790
+ }
1791
+ } catch (err) {
1792
+ const detail = err instanceof Error ? ` (${err.message})` : "";
1793
+ return {
1794
+ sessionId: "",
1795
+ status: "error",
1796
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd is not accessible: ${normalizedCwd}${detail}`
1797
+ };
1798
+ }
1799
+ }
1800
+ if (!sessionManager.hasCapacityFor(1)) {
1801
+ return {
1802
+ sessionId: "",
1803
+ status: "error",
1804
+ error: `Error [${"RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */}]: Too many sessions (limit: ${sessionManager.getMaxSessions()}).`
1805
+ };
1806
+ }
1119
1807
  const abortController = new AbortController();
1120
1808
  const adv = input.advanced ?? {};
1121
1809
  const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
1122
- const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ?? 1e4;
1810
+ const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ?? input.sessionInitTimeoutMs ?? 1e4;
1811
+ const compatWarnings = [];
1812
+ if (input.sessionInitTimeoutMs !== void 0) {
1813
+ compatWarnings.push(
1814
+ "Top-level sessionInitTimeoutMs for claude_code is a compatibility alias; prefer advanced.sessionInitTimeoutMs."
1815
+ );
1816
+ }
1817
+ if (input.sessionInitTimeoutMs !== void 0 && adv.sessionInitTimeoutMs !== void 0 && input.sessionInitTimeoutMs !== adv.sessionInitTimeoutMs) {
1818
+ compatWarnings.push(
1819
+ `Both advanced.sessionInitTimeoutMs (${adv.sessionInitTimeoutMs}) and top-level sessionInitTimeoutMs (${input.sessionInitTimeoutMs}) were provided; using advanced.sessionInitTimeoutMs.`
1820
+ );
1821
+ }
1123
1822
  const flat = {
1124
- cwd,
1823
+ cwd: normalizedCwd,
1125
1824
  allowedTools: input.allowedTools,
1126
1825
  disallowedTools: input.disallowedTools,
1826
+ strictAllowedTools: input.strictAllowedTools ?? adv.strictAllowedTools,
1127
1827
  maxTurns: input.maxTurns,
1128
1828
  model: input.model,
1129
1829
  systemPrompt: input.systemPrompt,
1130
- ...adv
1830
+ ...adv,
1831
+ effort: input.effort ?? adv.effort,
1832
+ thinking: input.thinking ?? adv.thinking
1833
+ };
1834
+ const normalizedFlat = {
1835
+ ...flat,
1836
+ cwd: normalizeWindowsPathLike(flat.cwd),
1837
+ additionalDirectories: flat.additionalDirectories !== void 0 ? normalizeWindowsPathArray(flat.additionalDirectories) : void 0,
1838
+ debugFile: flat.debugFile !== void 0 ? normalizeWindowsPathLike(flat.debugFile) : void 0,
1839
+ pathToClaudeCodeExecutable: flat.pathToClaudeCodeExecutable !== void 0 ? normalizeWindowsPathLike(flat.pathToClaudeCodeExecutable) : void 0
1131
1840
  };
1132
1841
  try {
1133
1842
  const handle = consumeQuery({
1134
1843
  mode: "start",
1135
1844
  prompt: input.prompt,
1136
1845
  abortController,
1137
- options: buildOptions(flat),
1846
+ options: buildOptions(normalizedFlat),
1138
1847
  permissionRequestTimeoutMs,
1139
1848
  sessionInitTimeoutMs,
1140
1849
  sessionManager,
1141
1850
  toolCache,
1142
1851
  onInit: (init) => {
1143
1852
  if (sessionManager.get(init.session_id)) return;
1144
- sessionManager.create({
1145
- sessionId: init.session_id,
1146
- cwd,
1147
- model: input.model,
1148
- permissionMode: "default",
1149
- allowedTools: input.allowedTools,
1150
- disallowedTools: input.disallowedTools,
1151
- tools: adv.tools,
1152
- maxTurns: input.maxTurns,
1153
- systemPrompt: input.systemPrompt,
1154
- agents: adv.agents,
1155
- maxBudgetUsd: adv.maxBudgetUsd,
1156
- effort: adv.effort,
1157
- betas: adv.betas,
1158
- additionalDirectories: adv.additionalDirectories,
1159
- outputFormat: adv.outputFormat,
1160
- thinking: adv.thinking,
1161
- persistSession: adv.persistSession,
1162
- pathToClaudeCodeExecutable: adv.pathToClaudeCodeExecutable,
1163
- agent: adv.agent,
1164
- mcpServers: adv.mcpServers,
1165
- sandbox: adv.sandbox,
1166
- fallbackModel: adv.fallbackModel,
1167
- enableFileCheckpointing: adv.enableFileCheckpointing,
1168
- includePartialMessages: adv.includePartialMessages,
1169
- strictMcpConfig: adv.strictMcpConfig,
1170
- settingSources: adv.settingSources ?? DEFAULT_SETTING_SOURCES,
1171
- debug: adv.debug,
1172
- debugFile: adv.debugFile,
1173
- env: adv.env,
1174
- abortController
1175
- });
1853
+ sessionManager.create(
1854
+ toSessionCreateParams({
1855
+ sessionId: init.session_id,
1856
+ source: normalizedFlat,
1857
+ permissionMode: "default",
1858
+ abortController,
1859
+ queryInterrupt: () => {
1860
+ handle.interrupt();
1861
+ }
1862
+ })
1863
+ );
1176
1864
  }
1177
1865
  });
1178
1866
  const sessionId = await raceWithAbort(
@@ -1185,7 +1873,8 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1185
1873
  sessionId,
1186
1874
  status: "running",
1187
1875
  pollInterval: 3e3,
1188
- resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0
1876
+ resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0,
1877
+ compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0
1189
1878
  };
1190
1879
  } catch (err) {
1191
1880
  const message = err instanceof Error ? err.message : String(err);
@@ -1198,9 +1887,45 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1198
1887
  }
1199
1888
 
1200
1889
  // src/tools/claude-code-reply.ts
1201
- function toStartError(sessionId, err) {
1202
- const message = err instanceof Error ? err.message : String(err);
1203
- const errorText = message.includes("Error [") ? message : `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`;
1890
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
1891
+ import os2 from "os";
1892
+ import path3 from "path";
1893
+ function normalizeAndAssertCwd(cwd, contextLabel) {
1894
+ const normalizedCwd = normalizeWindowsPathLike(cwd);
1895
+ const resolvedCwd = resolvePortableTmpAlias(normalizedCwd);
1896
+ if (!existsSync3(resolvedCwd)) {
1897
+ throw new Error(
1898
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} path does not exist: ${resolvedCwd}`
1899
+ );
1900
+ }
1901
+ try {
1902
+ const stat = statSync2(resolvedCwd);
1903
+ if (!stat.isDirectory()) {
1904
+ throw new Error(
1905
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} must be a directory: ${resolvedCwd}`
1906
+ );
1907
+ }
1908
+ } catch (err) {
1909
+ if (err instanceof Error && err.message.includes("Error [")) throw err;
1910
+ const detail = err instanceof Error ? ` (${err.message})` : "";
1911
+ throw new Error(
1912
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} is not accessible: ${resolvedCwd}${detail}`
1913
+ );
1914
+ }
1915
+ return resolvedCwd;
1916
+ }
1917
+ function resolvePortableTmpAlias(cwd) {
1918
+ if (process.platform !== "win32") return cwd;
1919
+ const normalized = cwd.replace(/\\/g, "/");
1920
+ if (normalized === "/tmp") return os2.tmpdir();
1921
+ if (normalized.startsWith("/tmp/")) {
1922
+ return path3.join(os2.tmpdir(), normalized.slice("/tmp/".length));
1923
+ }
1924
+ return cwd;
1925
+ }
1926
+ function toStartError(sessionId, err) {
1927
+ const message = err instanceof Error ? err.message : String(err);
1928
+ const errorText = message.includes("Error [") ? message : `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`;
1204
1929
  return {
1205
1930
  agentResult: {
1206
1931
  sessionId,
@@ -1217,13 +1942,24 @@ function buildOptionsFromDiskResume(dr) {
1217
1942
  if (dr.cwd === void 0 || typeof dr.cwd !== "string" || dr.cwd.trim() === "") {
1218
1943
  throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be provided for disk resume.`);
1219
1944
  }
1220
- return buildOptions(dr);
1945
+ const normalizedCwd = normalizeAndAssertCwd(dr.cwd, "disk resume cwd");
1946
+ return buildOptions({
1947
+ ...dr,
1948
+ cwd: normalizedCwd
1949
+ });
1221
1950
  }
1222
1951
  async function executeClaudeCodeReply(input, sessionManager, toolCache, requestSignal) {
1223
1952
  const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
1224
1953
  const sessionInitTimeoutMs = input.sessionInitTimeoutMs ?? 1e4;
1225
1954
  const existing = sessionManager.get(input.sessionId);
1226
1955
  if (!existing) {
1956
+ if (!sessionManager.hasCapacityFor(1)) {
1957
+ return {
1958
+ sessionId: input.sessionId,
1959
+ status: "error",
1960
+ error: `Error [${"RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */}]: Too many sessions (limit: ${sessionManager.getMaxSessions()}).`
1961
+ };
1962
+ }
1227
1963
  const allowDiskResume = process.env.CLAUDE_CODE_MCP_ALLOW_DISK_RESUME === "1";
1228
1964
  if (!allowDiskResume) {
1229
1965
  return {
@@ -1232,7 +1968,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1232
1968
  error: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`
1233
1969
  };
1234
1970
  }
1235
- const resumeSecret = getResumeSecret();
1971
+ const resumeSecrets = getResumeSecrets();
1972
+ const resumeSecret = resumeSecrets[0];
1236
1973
  if (!resumeSecret) {
1237
1974
  return {
1238
1975
  sessionId: input.sessionId,
@@ -1248,8 +1985,7 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1248
1985
  error: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: resumeToken is required for disk resume fallback.`
1249
1986
  };
1250
1987
  }
1251
- const expectedToken = computeResumeToken(input.sessionId, resumeSecret);
1252
- if (dr.resumeToken !== expectedToken) {
1988
+ if (!isValidResumeToken(input.sessionId, dr.resumeToken, resumeSecrets)) {
1253
1989
  return {
1254
1990
  sessionId: input.sessionId,
1255
1991
  status: "error",
@@ -1259,40 +1995,29 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1259
1995
  try {
1260
1996
  const abortController2 = new AbortController();
1261
1997
  const options2 = buildOptionsFromDiskResume(dr);
1262
- sessionManager.create({
1263
- sessionId: input.sessionId,
1998
+ if (input.effort !== void 0) options2.effort = input.effort;
1999
+ if (input.thinking !== void 0) options2.thinking = input.thinking;
2000
+ const { resumeToken: _resumeToken, ...rest } = dr;
2001
+ void _resumeToken;
2002
+ const source = {
2003
+ ...rest,
1264
2004
  cwd: options2.cwd ?? dr.cwd ?? "",
1265
- model: dr.model,
1266
- permissionMode: "default",
1267
- allowedTools: dr.allowedTools,
1268
- disallowedTools: dr.disallowedTools,
1269
- tools: dr.tools,
1270
- maxTurns: dr.maxTurns,
1271
- systemPrompt: dr.systemPrompt,
1272
- agents: dr.agents,
1273
- maxBudgetUsd: dr.maxBudgetUsd,
1274
- effort: dr.effort,
1275
- betas: dr.betas,
1276
- additionalDirectories: dr.additionalDirectories,
1277
- outputFormat: dr.outputFormat,
1278
- thinking: dr.thinking,
1279
- persistSession: dr.persistSession,
1280
- pathToClaudeCodeExecutable: dr.pathToClaudeCodeExecutable,
1281
- agent: dr.agent,
1282
- mcpServers: dr.mcpServers,
1283
- sandbox: dr.sandbox,
1284
- fallbackModel: dr.fallbackModel,
1285
- enableFileCheckpointing: dr.enableFileCheckpointing,
1286
- includePartialMessages: dr.includePartialMessages,
1287
- strictMcpConfig: dr.strictMcpConfig,
1288
- settingSources: dr.settingSources ?? DEFAULT_SETTING_SOURCES,
1289
- debug: dr.debug,
1290
- debugFile: dr.debugFile,
1291
- env: dr.env,
1292
- abortController: abortController2
1293
- });
2005
+ additionalDirectories: options2.additionalDirectories ?? rest.additionalDirectories,
2006
+ debugFile: options2.debugFile ?? rest.debugFile,
2007
+ pathToClaudeCodeExecutable: options2.pathToClaudeCodeExecutable ?? rest.pathToClaudeCodeExecutable,
2008
+ effort: input.effort ?? rest.effort,
2009
+ thinking: input.thinking ?? rest.thinking
2010
+ };
2011
+ sessionManager.create(
2012
+ toSessionCreateParams({
2013
+ sessionId: input.sessionId,
2014
+ source,
2015
+ permissionMode: "default",
2016
+ abortController: abortController2
2017
+ })
2018
+ );
1294
2019
  try {
1295
- consumeQuery({
2020
+ const handle = consumeQuery({
1296
2021
  mode: "disk-resume",
1297
2022
  sessionId: input.sessionId,
1298
2023
  prompt: input.prompt,
@@ -1303,6 +2028,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1303
2028
  sessionManager,
1304
2029
  toolCache
1305
2030
  });
2031
+ sessionManager.update(input.sessionId, {
2032
+ queryInterrupt: () => {
2033
+ handle.interrupt();
2034
+ }
2035
+ });
1306
2036
  } catch (err) {
1307
2037
  const { agentResult, errorText } = toStartError(input.sessionId, err);
1308
2038
  sessionManager.setResult(input.sessionId, {
@@ -1315,7 +2045,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1315
2045
  data: agentResult,
1316
2046
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1317
2047
  });
1318
- sessionManager.update(input.sessionId, { status: "error", abortController: void 0 });
2048
+ sessionManager.update(input.sessionId, {
2049
+ status: "error",
2050
+ abortController: void 0,
2051
+ queryInterrupt: void 0
2052
+ });
1319
2053
  return { sessionId: input.sessionId, status: "error", error: errorText };
1320
2054
  }
1321
2055
  return {
@@ -1337,7 +2071,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1337
2071
  data: agentResult,
1338
2072
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1339
2073
  });
1340
- sessionManager.update(input.sessionId, { status: "error", abortController: void 0 });
2074
+ sessionManager.update(input.sessionId, {
2075
+ status: "error",
2076
+ abortController: void 0,
2077
+ queryInterrupt: void 0
2078
+ });
1341
2079
  }
1342
2080
  return {
1343
2081
  sessionId: input.sessionId,
@@ -1371,8 +2109,31 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1371
2109
  error: current ? `Error [${"SESSION_BUSY" /* SESSION_BUSY */}]: Session is not available (status: ${current.status}).` : `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`
1372
2110
  };
1373
2111
  }
1374
- const options = buildOptions(existing);
2112
+ const session = acquired;
2113
+ const normalizedCwd = normalizeAndAssertCwd(session.cwd, "session cwd");
2114
+ const options = buildOptions(session);
2115
+ options.cwd = normalizedCwd;
1375
2116
  if (input.forkSession) options.forkSession = true;
2117
+ if (input.forkSession && !sessionManager.hasCapacityFor(1)) {
2118
+ sessionManager.update(input.sessionId, { status: originalStatus, abortController: void 0 });
2119
+ return {
2120
+ sessionId: input.sessionId,
2121
+ status: "error",
2122
+ error: `Error [${"RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */}]: Too many sessions (limit: ${sessionManager.getMaxSessions()}).`
2123
+ };
2124
+ }
2125
+ const sourceOverrides = {
2126
+ effort: input.effort ?? session.effort,
2127
+ thinking: input.thinking ?? session.thinking
2128
+ };
2129
+ if (input.effort !== void 0) options.effort = input.effort;
2130
+ if (input.thinking !== void 0) options.thinking = input.thinking;
2131
+ if (!input.forkSession && (input.effort !== void 0 || input.thinking !== void 0)) {
2132
+ const patch = {};
2133
+ if (input.effort !== void 0) patch.effort = input.effort;
2134
+ if (input.thinking !== void 0) patch.thinking = input.thinking;
2135
+ sessionManager.update(input.sessionId, patch);
2136
+ }
1376
2137
  try {
1377
2138
  const handle = consumeQuery({
1378
2139
  mode: "resume",
@@ -1388,46 +2149,33 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1388
2149
  onInit: (init) => {
1389
2150
  if (!input.forkSession) return;
1390
2151
  if (init.session_id === input.sessionId) return;
1391
- if (!sessionManager.get(init.session_id)) {
1392
- sessionManager.create({
1393
- sessionId: init.session_id,
1394
- cwd: existing.cwd,
1395
- model: existing.model,
1396
- permissionMode: "default",
1397
- allowedTools: existing.allowedTools,
1398
- disallowedTools: existing.disallowedTools,
1399
- tools: existing.tools,
1400
- maxTurns: existing.maxTurns,
1401
- systemPrompt: existing.systemPrompt,
1402
- agents: existing.agents,
1403
- maxBudgetUsd: existing.maxBudgetUsd,
1404
- effort: existing.effort,
1405
- betas: existing.betas,
1406
- additionalDirectories: existing.additionalDirectories,
1407
- outputFormat: existing.outputFormat,
1408
- thinking: existing.thinking,
1409
- persistSession: existing.persistSession,
1410
- pathToClaudeCodeExecutable: existing.pathToClaudeCodeExecutable,
1411
- agent: existing.agent,
1412
- mcpServers: existing.mcpServers,
1413
- sandbox: existing.sandbox,
1414
- fallbackModel: existing.fallbackModel,
1415
- enableFileCheckpointing: existing.enableFileCheckpointing,
1416
- includePartialMessages: existing.includePartialMessages,
1417
- strictMcpConfig: existing.strictMcpConfig,
1418
- settingSources: existing.settingSources ?? DEFAULT_SETTING_SOURCES,
1419
- debug: existing.debug,
1420
- debugFile: existing.debugFile,
1421
- env: existing.env,
1422
- abortController
1423
- });
1424
- }
1425
2152
  sessionManager.update(input.sessionId, {
1426
2153
  status: originalStatus,
1427
- abortController: void 0
2154
+ abortController: void 0,
2155
+ queryInterrupt: void 0
1428
2156
  });
2157
+ if (!sessionManager.get(init.session_id)) {
2158
+ sessionManager.create(
2159
+ toSessionCreateParams({
2160
+ sessionId: init.session_id,
2161
+ source: { ...session, ...sourceOverrides },
2162
+ permissionMode: "default",
2163
+ abortController,
2164
+ queryInterrupt: () => {
2165
+ handle.interrupt();
2166
+ }
2167
+ })
2168
+ );
2169
+ }
1429
2170
  }
1430
2171
  });
2172
+ if (!input.forkSession) {
2173
+ sessionManager.update(input.sessionId, {
2174
+ queryInterrupt: () => {
2175
+ handle.interrupt();
2176
+ }
2177
+ });
2178
+ }
1431
2179
  const sessionId = input.forkSession ? await raceWithAbort(
1432
2180
  handle.sdkSessionIdPromise,
1433
2181
  requestSignal,
@@ -1452,7 +2200,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1452
2200
  if (input.forkSession) {
1453
2201
  sessionManager.update(input.sessionId, {
1454
2202
  status: originalStatus,
1455
- abortController: void 0
2203
+ abortController: void 0,
2204
+ queryInterrupt: void 0
1456
2205
  });
1457
2206
  } else {
1458
2207
  sessionManager.setResult(input.sessionId, {
@@ -1465,7 +2214,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1465
2214
  data: agentResult,
1466
2215
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1467
2216
  });
1468
- sessionManager.update(input.sessionId, { status: "error", abortController: void 0 });
2217
+ sessionManager.update(input.sessionId, {
2218
+ status: "error",
2219
+ abortController: void 0,
2220
+ queryInterrupt: void 0
2221
+ });
1469
2222
  }
1470
2223
  return {
1471
2224
  sessionId: input.sessionId,
@@ -1476,77 +2229,97 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1476
2229
  }
1477
2230
 
1478
2231
  // src/tools/tool-discovery.ts
2232
+ var DEFAULT_PERMISSION_MODEL = "policy_controlled";
2233
+ var DEFAULT_SCHEMA_AVAILABILITY = "none";
1479
2234
  var TOOL_CATALOG = {
1480
2235
  Bash: {
1481
- description: "Run shell commands (e.g. npm install, git commit, ls) in the project directory.",
1482
- category: "execute"
2236
+ description: "Run shell commands",
2237
+ category: "execute",
2238
+ notes: ["Execution may require permission approval depending on session policy."]
1483
2239
  },
1484
2240
  Read: {
1485
- description: "Read the contents of a file given its path (large files may hit per-call size caps; use offset/limit or Grep chunking).",
1486
- category: "file_read"
2241
+ description: "Read file contents (large files: use offset/limit or Grep)",
2242
+ category: "file_read",
2243
+ notes: ["Large reads are often capped by the backend; use offset/limit when needed."]
1487
2244
  },
1488
2245
  Write: {
1489
- description: "Create a new file or completely replace an existing file's contents.",
2246
+ description: "Create or overwrite files",
1490
2247
  category: "file_write"
1491
2248
  },
1492
2249
  Edit: {
1493
- description: "Make targeted changes to specific parts of an existing file without rewriting the whole file (replace_all is substring-based).",
2250
+ description: "Targeted edits (replace_all is substring-based)",
1494
2251
  category: "file_write"
1495
2252
  },
1496
2253
  Glob: {
1497
- description: "Find files by name pattern (e.g. '**/*.ts' finds all TypeScript files).",
2254
+ description: "Find files by glob pattern",
1498
2255
  category: "file_read"
1499
2256
  },
1500
2257
  Grep: {
1501
- description: "Search inside files for text or regex patterns (like grep/ripgrep).",
2258
+ description: "Search file contents (regex)",
1502
2259
  category: "file_read"
1503
2260
  },
1504
2261
  NotebookEdit: {
1505
- description: "Edit individual cells in Jupyter notebooks (.ipynb files) (expects native Windows paths; this server normalizes /d/... when possible).",
2262
+ description: "Edit Jupyter notebook cells (Windows paths normalized)",
1506
2263
  category: "file_write"
1507
2264
  },
1508
2265
  WebFetch: {
1509
- description: "Download and read the content of a web page or API endpoint.",
2266
+ description: "Fetch web page or API content",
1510
2267
  category: "network"
1511
2268
  },
1512
- WebSearch: { description: "Search the web and return relevant results.", category: "network" },
2269
+ WebSearch: { description: "Web search", category: "network" },
1513
2270
  Task: {
1514
- description: "Spawn a subagent to handle a subtask independently (requires this tool to be in allowedTools).",
1515
- category: "agent"
2271
+ description: "Spawn subagent (must be in allowedTools)",
2272
+ category: "agent",
2273
+ availabilityConditions: ["Requires Task to be visible and approved by session policy."]
1516
2274
  },
1517
- TaskOutput: { description: "Get the output from a background subagent task.", category: "agent" },
1518
- TaskStop: { description: "Cancel a running background subagent task.", category: "agent" },
2275
+ TaskOutput: { description: "Get subagent output", category: "agent" },
2276
+ TaskStop: { description: "Cancel subagent", category: "agent" },
1519
2277
  TodoWrite: {
1520
- description: "Create and update a structured task/todo checklist.",
2278
+ description: "Task/todo checklist",
1521
2279
  category: "agent"
1522
2280
  },
1523
2281
  AskUserQuestion: {
1524
- description: "Ask the user a question and wait for their answer before continuing.",
2282
+ description: "Ask user a question",
1525
2283
  category: "interaction"
1526
2284
  },
1527
2285
  TeamDelete: {
1528
- description: "Delete a team and its resources (may require all active members to shutdown_approved; cleanup may complete asynchronously).",
1529
- category: "agent"
2286
+ description: "Delete team (may need shutdown_approved first)",
2287
+ category: "agent",
2288
+ notes: ["Team cleanup can be asynchronous during shutdown."]
1530
2289
  }
1531
2290
  };
1532
2291
  function uniq(items) {
1533
2292
  return Array.from(new Set(items));
1534
2293
  }
2294
+ function withToolDefaults(tool) {
2295
+ return {
2296
+ ...tool,
2297
+ permissionModel: tool.permissionModel ?? DEFAULT_PERMISSION_MODEL,
2298
+ schemaAvailability: tool.schemaAvailability ?? DEFAULT_SCHEMA_AVAILABILITY
2299
+ };
2300
+ }
1535
2301
  function discoverToolsFromInit(initTools) {
1536
2302
  const names = uniq(initTools.filter((t) => typeof t === "string" && t.trim() !== ""));
1537
- return names.map((name) => ({
1538
- name,
1539
- description: TOOL_CATALOG[name]?.description ?? name,
1540
- category: TOOL_CATALOG[name]?.category
1541
- }));
2303
+ return names.map(
2304
+ (name) => withToolDefaults({
2305
+ name,
2306
+ description: TOOL_CATALOG[name]?.description ?? name,
2307
+ category: TOOL_CATALOG[name]?.category,
2308
+ availabilityConditions: TOOL_CATALOG[name]?.availabilityConditions,
2309
+ platformConstraints: TOOL_CATALOG[name]?.platformConstraints,
2310
+ notes: TOOL_CATALOG[name]?.notes
2311
+ })
2312
+ );
1542
2313
  }
1543
2314
  function defaultCatalogTools() {
1544
- return Object.keys(TOOL_CATALOG).sort((a, b) => a.localeCompare(b)).map((name) => ({ name, ...TOOL_CATALOG[name] }));
2315
+ return Object.keys(TOOL_CATALOG).sort((a, b) => a.localeCompare(b)).map((name) => withToolDefaults({ name, ...TOOL_CATALOG[name] }));
1545
2316
  }
1546
2317
  var ToolDiscoveryCache = class {
1547
2318
  cached;
1548
- constructor(initial) {
2319
+ onUpdated;
2320
+ constructor(initial, onUpdated) {
1549
2321
  this.cached = initial ?? defaultCatalogTools();
2322
+ this.onUpdated = onUpdated;
1550
2323
  }
1551
2324
  getTools() {
1552
2325
  return this.cached;
@@ -1555,7 +2328,13 @@ var ToolDiscoveryCache = class {
1555
2328
  const discovered = discoverToolsFromInit(initTools);
1556
2329
  const next = mergeToolLists(discovered, defaultCatalogTools());
1557
2330
  const updated = JSON.stringify(next) !== JSON.stringify(this.cached);
1558
- if (updated) this.cached = next;
2331
+ if (updated) {
2332
+ this.cached = next;
2333
+ try {
2334
+ this.onUpdated?.(this.cached);
2335
+ } catch {
2336
+ }
2337
+ }
1559
2338
  return { updated, tools: this.cached };
1560
2339
  }
1561
2340
  };
@@ -1580,9 +2359,8 @@ function groupByCategory(tools) {
1580
2359
  function buildInternalToolsDescription(tools) {
1581
2360
  const grouped = groupByCategory(tools);
1582
2361
  const categories = Object.keys(grouped).sort((a, b) => a.localeCompare(b));
1583
- let desc = 'Start a new Claude Code agent session.\n\nLaunches an autonomous coding agent that can read/write files, run shell commands, search code, manage git, access the web, and more. Returns immediately with a sessionId \u2014 the agent runs asynchronously in the background.\n\nWorkflow:\n1. Call claude_code with a prompt \u2192 returns { sessionId, status: "running", pollInterval }\n2. Poll with claude_code_check (action="poll") to receive progress events and the final result\n3. If the agent needs permission for a tool call, approve or deny via claude_code_check (action="respond_permission")\n\n';
1584
- desc += "Defaults:\n- settingSources: ['user', 'project', 'local'] (loads ~/.claude/settings.json, .claude/settings.json, .claude/settings.local.json, and CLAUDE.md)\n- persistSession: true\n- sessionInitTimeoutMs: 10000\n- permissionRequestTimeoutMs: 60000\n- allowedTools/disallowedTools: [] (none)\n- resumeToken: omitted unless CLAUDE_CODE_MCP_RESUME_SECRET is set on the server\n- Permission prompts auto-deny on timeout; use claude_code_check actions[].expiresAt/remainingMs\n\n";
1585
- desc += "Internal tools available to the agent (use allowedTools/disallowedTools to control approval policy; authoritative list returned by claude_code_check with includeTools=true):\n";
2362
+ let desc = "Start a Claude Code session and return sessionId.\nUse claude_code_check to poll events/results and handle permissions.\n\n";
2363
+ desc += "Internal tools (authoritative list: includeTools=true in claude_code_check):\n";
1586
2364
  for (const category of categories) {
1587
2365
  desc += `
1588
2366
  [${category}]
@@ -1592,8 +2370,7 @@ function buildInternalToolsDescription(tools) {
1592
2370
  `;
1593
2371
  }
1594
2372
  }
1595
- desc += "\nSecurity: You MUST configure allowedTools/disallowedTools based on your own permission scope. Only allow tools that you yourself are authorized to perform \u2014 do not grant the agent broader permissions than you have. For example, if you lack write access to a directory, do not include Write/Edit in allowedTools. When in doubt, leave both lists empty and review each permission request individually via claude_code_check.\n\n";
1596
- desc += 'Use `allowedTools` to pre-approve tools (no permission prompts). Use `disallowedTools` to permanently block specific tools. Any tool not in either list will pause the session (status: "waiting_permission") until approved or denied via claude_code_check.\n';
2373
+ desc += "\nPermission control: allowedTools auto-approves; disallowedTools always denies; others require approval.\n";
1597
2374
  return desc;
1598
2375
  }
1599
2376
 
@@ -1604,7 +2381,7 @@ function pollIntervalForStatus(status) {
1604
2381
  return void 0;
1605
2382
  }
1606
2383
  function toPermissionResult(params) {
1607
- if (params.decision === "allow") {
2384
+ if (params.decision === "allow" || params.decision === "allow_for_session") {
1608
2385
  return {
1609
2386
  behavior: "allow",
1610
2387
  updatedInput: params.updatedInput,
@@ -1617,6 +2394,19 @@ function toPermissionResult(params) {
1617
2394
  interrupt: params.interrupt
1618
2395
  };
1619
2396
  }
2397
+ function appendAllowForSessionUpdate(updates, toolName) {
2398
+ const normalizedToolName = toolName?.trim();
2399
+ if (!normalizedToolName) return updates;
2400
+ return [
2401
+ ...updates ?? [],
2402
+ {
2403
+ type: "addRules",
2404
+ behavior: "allow",
2405
+ destination: "session",
2406
+ rules: [{ toolName: normalizedToolName }]
2407
+ }
2408
+ ];
2409
+ }
1620
2410
  function slimAssistantData(data) {
1621
2411
  if (!data || typeof data !== "object") return data;
1622
2412
  const d = data;
@@ -1652,18 +2442,169 @@ function toEvents(events, opts) {
1652
2442
  return { id: e.id, type: e.type, data: e.data, timestamp: e.timestamp };
1653
2443
  });
1654
2444
  }
2445
+ function uniqSorted(values) {
2446
+ if (!Array.isArray(values) || values.length === 0) return [];
2447
+ const filtered = values.filter((v) => typeof v === "string").map((v) => v.trim()).filter(Boolean);
2448
+ return Array.from(new Set(filtered)).sort((a, b) => a.localeCompare(b));
2449
+ }
2450
+ function detectPathCompatibilityWarnings(session) {
2451
+ if (!session) return [];
2452
+ const cwd = session.cwd;
2453
+ if (typeof cwd !== "string" || cwd.trim() === "") return [];
2454
+ const warnings = [];
2455
+ if (process.platform === "win32" && cwd.startsWith("/") && !cwd.startsWith("//")) {
2456
+ warnings.push(
2457
+ `cwd '${cwd}' looks POSIX-style on Windows. Consider using a Windows path (e.g. C:\\\\repo) to avoid path compatibility issues.`
2458
+ );
2459
+ }
2460
+ if (process.platform !== "win32" && /^[a-zA-Z]:[\\/]/.test(cwd)) {
2461
+ warnings.push(
2462
+ `cwd '${cwd}' looks Windows-style on ${process.platform}. This may indicate cross-platform path mismatch (for example WSL boundary).`
2463
+ );
2464
+ }
2465
+ if (process.platform !== "win32" && cwd.startsWith("\\\\")) {
2466
+ warnings.push(
2467
+ `cwd '${cwd}' looks like a Windows UNC path on ${process.platform}. Confirm MCP client/server run on the same platform.`
2468
+ );
2469
+ }
2470
+ if (process.platform === "win32" && Array.isArray(session.additionalDirectories)) {
2471
+ for (const dir of session.additionalDirectories) {
2472
+ if (typeof dir === "string" && dir.startsWith("/") && !dir.startsWith("//")) {
2473
+ warnings.push(
2474
+ `additionalDirectories contains POSIX-style path '${dir}' on Windows. Consider using a Windows path to avoid path compatibility issues.`
2475
+ );
2476
+ }
2477
+ }
2478
+ }
2479
+ return warnings;
2480
+ }
2481
+ function isPosixHomePath(value) {
2482
+ return /^\/home\/[^/\s]+(?:\/|$)/.test(value);
2483
+ }
2484
+ function extractPosixHomePath(value) {
2485
+ const match = value.match(/\/home\/[^/\s"'`]+(?:\/[^\s"'`]*)?/);
2486
+ return match?.[0];
2487
+ }
2488
+ function detectPendingPermissionPathWarnings(pending, session) {
2489
+ if (process.platform !== "win32" || pending.length === 0) return [];
2490
+ const cwd = session?.cwd;
2491
+ const cwdHint = typeof cwd === "string" && cwd.trim() !== "" ? ` under cwd '${cwd}'` : " under the current cwd";
2492
+ const warnings = [];
2493
+ for (const req of pending) {
2494
+ const candidates = [];
2495
+ if (typeof req.blockedPath === "string") candidates.push(req.blockedPath);
2496
+ const filePath = req.input.file_path;
2497
+ if (typeof filePath === "string") candidates.push(filePath);
2498
+ const pathFields = [
2499
+ "path",
2500
+ "directory",
2501
+ "folder",
2502
+ "cwd",
2503
+ "dest",
2504
+ "destination",
2505
+ "source",
2506
+ "target"
2507
+ ];
2508
+ for (const field of pathFields) {
2509
+ const val = req.input[field];
2510
+ if (typeof val === "string") candidates.push(val);
2511
+ }
2512
+ const command = req.input.command;
2513
+ if (typeof command === "string") {
2514
+ const embeddedPath = extractPosixHomePath(command);
2515
+ if (embeddedPath) candidates.push(embeddedPath);
2516
+ }
2517
+ const badPath = candidates.find((p) => isPosixHomePath(p));
2518
+ if (!badPath) continue;
2519
+ warnings.push(
2520
+ `Permission request '${req.requestId}' uses POSIX home path '${badPath}' on Windows. Prefer an absolute Windows path${cwdHint} to avoid out-of-bounds permission prompts.`
2521
+ );
2522
+ }
2523
+ return warnings;
2524
+ }
2525
+ function computeToolValidation(session, initTools) {
2526
+ if (!session) return { summary: void 0, warnings: [] };
2527
+ const allowedTools = uniqSorted(session.allowedTools);
2528
+ const disallowedTools = uniqSorted(session.disallowedTools);
2529
+ const warnings = [];
2530
+ if (allowedTools.length > 0 && session.strictAllowedTools !== true) {
2531
+ warnings.push(
2532
+ "allowedTools currently acts as pre-approval only. Set strictAllowedTools=true to enforce a strict allowlist."
2533
+ );
2534
+ }
2535
+ if (allowedTools.length === 0 && disallowedTools.length === 0) {
2536
+ return { summary: void 0, warnings };
2537
+ }
2538
+ if (!Array.isArray(initTools) || initTools.length === 0) {
2539
+ return {
2540
+ summary: {
2541
+ runtimeToolsKnown: false,
2542
+ unknownAllowedTools: [],
2543
+ unknownDisallowedTools: []
2544
+ },
2545
+ warnings: [
2546
+ ...warnings,
2547
+ "Runtime tool list is not available yet; unknown allowedTools/disallowedTools names cannot be validated until system/init tools arrive."
2548
+ ]
2549
+ };
2550
+ }
2551
+ const runtime = new Set(
2552
+ initTools.filter((name) => typeof name === "string").map((name) => name.trim()).filter(Boolean)
2553
+ );
2554
+ const unknownAllowedTools = allowedTools.filter((name) => !runtime.has(name));
2555
+ const unknownDisallowedTools = disallowedTools.filter((name) => !runtime.has(name));
2556
+ if (unknownAllowedTools.length > 0) {
2557
+ warnings.push(
2558
+ `Unknown allowedTools (not present in runtime tools): ${unknownAllowedTools.join(", ")}.`
2559
+ );
2560
+ }
2561
+ if (unknownDisallowedTools.length > 0) {
2562
+ warnings.push(
2563
+ `Unknown disallowedTools (not present in runtime tools): ${unknownDisallowedTools.join(", ")}.`
2564
+ );
2565
+ }
2566
+ return {
2567
+ summary: {
2568
+ runtimeToolsKnown: true,
2569
+ unknownAllowedTools,
2570
+ unknownDisallowedTools
2571
+ },
2572
+ warnings
2573
+ };
2574
+ }
2575
+ function byteLength(value) {
2576
+ return new TextEncoder().encode(JSON.stringify(value)).length;
2577
+ }
2578
+ function capEventsByBytes(events, maxBytes) {
2579
+ if (!Number.isFinite(maxBytes) || typeof maxBytes !== "number" || maxBytes <= 0) {
2580
+ return { events, truncated: false };
2581
+ }
2582
+ const budget = Math.floor(maxBytes);
2583
+ const kept = [];
2584
+ let bytes = 2;
2585
+ for (const evt of events) {
2586
+ const evtBytes = byteLength(evt);
2587
+ const separatorBytes = kept.length === 0 ? 0 : 1;
2588
+ if (bytes + separatorBytes + evtBytes > budget) break;
2589
+ kept.push(evt);
2590
+ bytes += separatorBytes + evtBytes;
2591
+ }
2592
+ return { events: kept, truncated: kept.length < events.length };
2593
+ }
1655
2594
  function buildResult(sessionManager, toolCache, input) {
1656
2595
  const responseMode = input.responseMode ?? "minimal";
2596
+ const compactMode = responseMode === "delta_compact";
1657
2597
  const po = input.pollOptions ?? {};
1658
2598
  const includeTools = po.includeTools;
1659
- const includeEvents = po.includeEvents ?? true;
2599
+ const includeEvents = po.includeEvents ?? !compactMode;
1660
2600
  const includeActions = po.includeActions ?? true;
1661
- const includeResult = po.includeResult ?? true;
2601
+ const includeResult = po.includeResult ?? !compactMode;
1662
2602
  const includeUsage = po.includeUsage ?? responseMode === "full";
1663
2603
  const includeModelUsage = po.includeModelUsage ?? responseMode === "full";
1664
2604
  const includeStructuredOutput = po.includeStructuredOutput ?? responseMode === "full";
1665
2605
  const includeTerminalEvents = po.includeTerminalEvents ?? responseMode === "full";
1666
2606
  const includeProgressEvents = po.includeProgressEvents ?? responseMode === "full";
2607
+ const maxBytes = po.maxBytes;
1667
2608
  const maxEvents = input.maxEvents ?? (responseMode === "minimal" ? 200 : void 0);
1668
2609
  const sessionId = input.sessionId;
1669
2610
  const session = sessionManager.get(sessionId);
@@ -1676,7 +2617,7 @@ function buildResult(sessionManager, toolCache, input) {
1676
2617
  let truncated = false;
1677
2618
  const truncatedFields = [];
1678
2619
  const windowEvents = maxEvents !== void 0 && rawEvents.length > maxEvents ? rawEvents.slice(0, maxEvents) : rawEvents;
1679
- const nextCursor = maxEvents !== void 0 && rawEvents.length > maxEvents ? windowEvents.length > 0 ? windowEvents[windowEvents.length - 1].id + 1 : rawNextCursor : rawNextCursor;
2620
+ let nextCursor = maxEvents !== void 0 && rawEvents.length > maxEvents ? windowEvents.length > 0 ? windowEvents[windowEvents.length - 1].id + 1 : rawNextCursor : rawNextCursor;
1680
2621
  if (maxEvents !== void 0 && rawEvents.length > maxEvents) {
1681
2622
  truncated = true;
1682
2623
  truncatedFields.push("events");
@@ -1699,8 +2640,28 @@ function buildResult(sessionManager, toolCache, input) {
1699
2640
  })();
1700
2641
  const pending = status === "waiting_permission" ? sessionManager.listPendingPermissions(sessionId) : [];
1701
2642
  const stored = status === "idle" || status === "error" ? sessionManager.getResult(sessionId) : void 0;
1702
- const initTools = includeTools ? sessionManager.getInitTools(sessionId) : void 0;
2643
+ const initTools = sessionManager.getInitTools(sessionId);
1703
2644
  const availableTools = includeTools && initTools ? discoverToolsFromInit(initTools) : void 0;
2645
+ const toolValidation = computeToolValidation(session, initTools);
2646
+ const compatWarnings = Array.from(
2647
+ /* @__PURE__ */ new Set([
2648
+ ...toolValidation.warnings,
2649
+ ...detectPathCompatibilityWarnings(session),
2650
+ ...detectPendingPermissionPathWarnings(pending, session)
2651
+ ])
2652
+ );
2653
+ const shapedEvents = toEvents(outputEvents, {
2654
+ includeUsage,
2655
+ includeModelUsage,
2656
+ includeStructuredOutput,
2657
+ slim: responseMode === "minimal" || responseMode === "delta_compact"
2658
+ });
2659
+ const cappedEvents = capEventsByBytes(shapedEvents, maxBytes);
2660
+ if (cappedEvents.truncated) {
2661
+ truncated = true;
2662
+ truncatedFields.push("events_bytes");
2663
+ nextCursor = cappedEvents.events.length > 0 ? cappedEvents.events[cappedEvents.events.length - 1].id + 1 : cursorResetTo ?? input.cursor ?? 0;
2664
+ }
1704
2665
  return {
1705
2666
  sessionId,
1706
2667
  status,
@@ -1708,14 +2669,11 @@ function buildResult(sessionManager, toolCache, input) {
1708
2669
  cursorResetTo,
1709
2670
  truncated: truncated ? true : void 0,
1710
2671
  truncatedFields: truncatedFields.length > 0 ? truncatedFields : void 0,
1711
- events: toEvents(outputEvents, {
1712
- includeUsage,
1713
- includeModelUsage,
1714
- includeStructuredOutput,
1715
- slim: responseMode === "minimal"
1716
- }),
2672
+ events: cappedEvents.events,
1717
2673
  nextCursor,
1718
2674
  availableTools,
2675
+ toolValidation: toolValidation.summary,
2676
+ compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0,
1719
2677
  actions: includeActions && status === "waiting_permission" ? pending.map((req) => {
1720
2678
  const expiresMs = req.expiresAt ? Date.parse(req.expiresAt) : Number.NaN;
1721
2679
  const remainingMs = Number.isFinite(expiresMs) ? Math.max(0, expiresMs - Date.now()) : void 0;
@@ -1741,7 +2699,7 @@ function buildResult(sessionManager, toolCache, input) {
1741
2699
  includeUsage,
1742
2700
  includeModelUsage,
1743
2701
  includeStructuredOutput,
1744
- slim: responseMode === "minimal"
2702
+ slim: responseMode === "minimal" || responseMode === "delta_compact"
1745
2703
  }) : void 0,
1746
2704
  cancelledAt: session?.cancelledAt,
1747
2705
  cancelledReason: session?.cancelledReason,
@@ -1775,7 +2733,14 @@ function redactAgentResult(result, opts) {
1775
2733
  structuredOutput: opts.includeStructuredOutput ? structuredOutput : void 0
1776
2734
  };
1777
2735
  }
1778
- function executeClaudeCodeCheck(input, sessionManager, toolCache) {
2736
+ function executeClaudeCodeCheck(input, sessionManager, toolCache, requestSignal) {
2737
+ if (requestSignal?.aborted) {
2738
+ return {
2739
+ sessionId: input.sessionId ?? "",
2740
+ error: `Error [${"CANCELLED" /* CANCELLED */}]: request was cancelled.`,
2741
+ isError: true
2742
+ };
2743
+ }
1779
2744
  if (typeof input.sessionId !== "string" || input.sessionId.trim() === "") {
1780
2745
  return {
1781
2746
  sessionId: "",
@@ -1801,20 +2766,25 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache) {
1801
2766
  isError: true
1802
2767
  };
1803
2768
  }
1804
- if (input.decision !== "allow" && input.decision !== "deny") {
2769
+ if (input.decision !== "allow" && input.decision !== "deny" && input.decision !== "allow_for_session") {
1805
2770
  return {
1806
2771
  sessionId: input.sessionId,
1807
- error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision must be 'allow' or 'deny'.`,
2772
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision must be 'allow', 'deny', or 'allow_for_session'.`,
1808
2773
  isError: true
1809
2774
  };
1810
2775
  }
2776
+ const pendingRequest = input.decision === "allow_for_session" ? sessionManager.getPendingPermission(input.sessionId, input.requestId) : void 0;
2777
+ const updatedPermissions = input.decision === "allow_for_session" ? appendAllowForSessionUpdate(
2778
+ input.permissionOptions?.updatedPermissions,
2779
+ pendingRequest?.toolName
2780
+ ) : input.permissionOptions?.updatedPermissions;
1811
2781
  const ok = sessionManager.finishRequest(
1812
2782
  input.sessionId,
1813
2783
  input.requestId,
1814
2784
  toPermissionResult({
1815
2785
  decision: input.decision,
1816
2786
  updatedInput: input.permissionOptions?.updatedInput,
1817
- updatedPermissions: input.permissionOptions?.updatedPermissions,
2787
+ updatedPermissions,
1818
2788
  denyMessage: input.denyMessage,
1819
2789
  interrupt: input.interrupt
1820
2790
  }),
@@ -1827,12 +2797,63 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache) {
1827
2797
  isError: true
1828
2798
  };
1829
2799
  }
2800
+ if (input.decision === "allow_for_session" && pendingRequest?.toolName) {
2801
+ sessionManager.allowToolForSession(input.sessionId, pendingRequest.toolName);
2802
+ }
1830
2803
  return buildResult(sessionManager, toolCache, input);
1831
2804
  }
1832
2805
 
1833
2806
  // src/tools/claude-code-session.ts
1834
- function executeClaudeCodeSession(input, sessionManager) {
1835
- const toSessionJson = (s) => input.includeSensitive ? sessionManager.toSensitiveJSON(s) : sessionManager.toPublicJSON(s);
2807
+ var ALWAYS_REDACTED_FIELDS = [
2808
+ "env",
2809
+ "mcpServers",
2810
+ "sandbox",
2811
+ "debugFile",
2812
+ "pathToClaudeCodeExecutable"
2813
+ ];
2814
+ var CONDITIONAL_REDACTED_FIELDS = [
2815
+ "cwd",
2816
+ "systemPrompt",
2817
+ "agents",
2818
+ "additionalDirectories"
2819
+ ];
2820
+ function buildRedactions(includeSensitive) {
2821
+ const redactions = [];
2822
+ for (const field of ALWAYS_REDACTED_FIELDS) {
2823
+ redactions?.push({ field, reason: "secret_or_internal" });
2824
+ }
2825
+ if (!includeSensitive) {
2826
+ for (const field of CONDITIONAL_REDACTED_FIELDS) {
2827
+ redactions?.push({ field, reason: "sensitive_by_default" });
2828
+ }
2829
+ }
2830
+ return redactions;
2831
+ }
2832
+ function executeClaudeCodeSession(input, sessionManager, requestSignal) {
2833
+ if (requestSignal?.aborted) {
2834
+ return {
2835
+ sessions: [],
2836
+ message: `Error [${"CANCELLED" /* CANCELLED */}]: request was cancelled.`,
2837
+ isError: true
2838
+ };
2839
+ }
2840
+ const toSessionJson = (s) => {
2841
+ const base = input.includeSensitive ? sessionManager.toSensitiveJSON(s) : sessionManager.toPublicJSON(s);
2842
+ const stored = sessionManager.getResult(s.sessionId);
2843
+ const lastError = stored?.type === "error" ? stored.result.result : void 0;
2844
+ const lastErrorAt = stored?.type === "error" ? stored.createdAt : void 0;
2845
+ return {
2846
+ ...base,
2847
+ pendingPermissionCount: sessionManager.getPendingPermissionCount(s.sessionId),
2848
+ eventCount: sessionManager.getEventCount(s.sessionId),
2849
+ currentCursor: sessionManager.getCurrentCursor(s.sessionId),
2850
+ lastEventId: sessionManager.getLastEventId(s.sessionId),
2851
+ ttlMs: sessionManager.getRemainingTtlMs(s.sessionId),
2852
+ lastError,
2853
+ lastErrorAt,
2854
+ redactions: buildRedactions(input.includeSensitive)
2855
+ };
2856
+ };
1836
2857
  switch (input.action) {
1837
2858
  case "list": {
1838
2859
  const sessions = sessionManager.list().map((s) => toSessionJson(s));
@@ -1889,21 +2910,64 @@ function executeClaudeCodeSession(input, sessionManager) {
1889
2910
  message: `Session '${input.sessionId}' cancelled.`
1890
2911
  };
1891
2912
  }
2913
+ case "interrupt": {
2914
+ if (!input.sessionId) {
2915
+ return {
2916
+ sessions: [],
2917
+ message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: sessionId is required for 'interrupt' action.`,
2918
+ isError: true
2919
+ };
2920
+ }
2921
+ const interrupted = sessionManager.interrupt(input.sessionId, {
2922
+ reason: "Interrupted by caller",
2923
+ source: "claude_code_session"
2924
+ });
2925
+ if (!interrupted) {
2926
+ const session = sessionManager.get(input.sessionId);
2927
+ if (!session) {
2928
+ return {
2929
+ sessions: [],
2930
+ message: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found.`,
2931
+ isError: true
2932
+ };
2933
+ }
2934
+ return {
2935
+ sessions: [toSessionJson(session)],
2936
+ message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Session '${input.sessionId}' is not running (status: ${session.status}).`,
2937
+ isError: true
2938
+ };
2939
+ }
2940
+ const updated = sessionManager.get(input.sessionId);
2941
+ return {
2942
+ sessions: updated ? [toSessionJson(updated)] : [],
2943
+ message: `Session '${input.sessionId}' interrupted.`
2944
+ };
2945
+ }
1892
2946
  default:
1893
2947
  return {
1894
2948
  sessions: [],
1895
- message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown action '${input.action}'. Use 'list', 'get', or 'cancel'.`,
2949
+ message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown action '${input.action}'. Use 'list', 'get', 'cancel', or 'interrupt'.`,
1896
2950
  isError: true
1897
2951
  };
1898
2952
  }
1899
2953
  }
1900
2954
 
1901
2955
  // src/resources/register-resources.ts
2956
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2957
+ import { createHash } from "crypto";
1902
2958
  var RESOURCE_SCHEME = "claude-code-mcp";
1903
2959
  var RESOURCE_URIS = {
1904
2960
  serverInfo: `${RESOURCE_SCHEME}:///server-info`,
1905
2961
  internalTools: `${RESOURCE_SCHEME}:///internal-tools`,
1906
- gotchas: `${RESOURCE_SCHEME}:///gotchas`
2962
+ gotchas: `${RESOURCE_SCHEME}:///gotchas`,
2963
+ quickstart: `${RESOURCE_SCHEME}:///quickstart`,
2964
+ errors: `${RESOURCE_SCHEME}:///errors`,
2965
+ compatReport: `${RESOURCE_SCHEME}:///compat-report`
2966
+ };
2967
+ var RESOURCE_TEMPLATES = {
2968
+ sessionById: `${RESOURCE_SCHEME}:///session/{sessionId}`,
2969
+ runtimeTools: `${RESOURCE_SCHEME}:///tools/runtime{?sessionId}`,
2970
+ compatDiff: `${RESOURCE_SCHEME}:///compat/diff{?client}`
1907
2971
  };
1908
2972
  function asTextResource(uri, text, mimeType) {
1909
2973
  return {
@@ -1916,31 +2980,189 @@ function asTextResource(uri, text, mimeType) {
1916
2980
  ]
1917
2981
  };
1918
2982
  }
2983
+ function asJsonResource(uri, value) {
2984
+ return asTextResource(uri, JSON.stringify(value, null, 2), "application/json");
2985
+ }
2986
+ function serializeLimit(limit) {
2987
+ return Number.isFinite(limit) ? limit : "unlimited";
2988
+ }
2989
+ function computeEtag(value) {
2990
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
2991
+ }
2992
+ function extractSingleVariable(value) {
2993
+ if (typeof value === "string" && value.trim() !== "") return value;
2994
+ if (Array.isArray(value) && typeof value[0] === "string" && value[0].trim() !== "")
2995
+ return value[0];
2996
+ return void 0;
2997
+ }
2998
+ function buildSessionRedactions(includeSensitive) {
2999
+ const redactions = [
3000
+ { field: "env", reason: "secret_or_internal" },
3001
+ { field: "mcpServers", reason: "secret_or_internal" },
3002
+ { field: "sandbox", reason: "secret_or_internal" },
3003
+ { field: "debugFile", reason: "secret_or_internal" },
3004
+ { field: "pathToClaudeCodeExecutable", reason: "secret_or_internal" }
3005
+ ];
3006
+ if (!includeSensitive) {
3007
+ redactions.push(
3008
+ { field: "cwd", reason: "sensitive_by_default" },
3009
+ { field: "systemPrompt", reason: "sensitive_by_default" },
3010
+ { field: "agents", reason: "sensitive_by_default" },
3011
+ { field: "additionalDirectories", reason: "sensitive_by_default" }
3012
+ );
3013
+ }
3014
+ return redactions;
3015
+ }
3016
+ function buildGotchasEntries() {
3017
+ return [
3018
+ {
3019
+ id: "permission-timeout",
3020
+ title: "Permission requests auto-deny on timeout",
3021
+ severity: "high",
3022
+ appliesTo: ["claude_code_check.actions", "permission workflow"],
3023
+ symptom: "Session waits for approval, then tool call is denied without explicit caller decision.",
3024
+ detection: "Observe actions[].expiresAt/remainingMs and permission_result with source=timeout.",
3025
+ remedy: "Poll more frequently and respond before timeout; increase permissionRequestTimeoutMs if needed.",
3026
+ example: "Default timeout is 60000ms and server-clamped to 300000ms."
3027
+ },
3028
+ {
3029
+ id: "read-size-cap",
3030
+ title: "Read may cap response size",
3031
+ severity: "medium",
3032
+ appliesTo: ["Read tool"],
3033
+ symptom: "Large file reads are truncated or fail.",
3034
+ detection: "Read responses are incomplete for large files.",
3035
+ remedy: "Use offset/limit paging or chunk with Grep."
3036
+ },
3037
+ {
3038
+ id: "edit-replace-all",
3039
+ title: "Edit replace_all uses substring matching",
3040
+ severity: "medium",
3041
+ appliesTo: ["Edit tool"],
3042
+ symptom: "replace_all=true fails unexpectedly.",
3043
+ detection: "Tool returns error when no exact substring match is found.",
3044
+ remedy: "Validate exact match text first; prefer smaller targeted replacements."
3045
+ },
3046
+ {
3047
+ id: "windows-path-normalization",
3048
+ title: "Windows path normalization applies to common MSYS-style paths",
3049
+ severity: "medium",
3050
+ appliesTo: ["Windows", "cwd", "additionalDirectories", "file_path"],
3051
+ symptom: "Permission/path behavior differs from raw input path text.",
3052
+ detection: "Compare submitted path with effective path in logs/permission prompts.",
3053
+ remedy: "Use absolute native Windows paths when possible; avoid relying on implicit conversion behavior.",
3054
+ example: "/d/... /mnt/c/... /cygdrive/c/... //server/share/... are normalized on Windows."
3055
+ },
3056
+ {
3057
+ id: "team-delete-async-cleanup",
3058
+ title: "TeamDelete cleanup can be asynchronous",
3059
+ severity: "low",
3060
+ appliesTo: ["TeamDelete"],
3061
+ symptom: "Immediate follow-up calls still see active members.",
3062
+ detection: "TeamDelete reports shutdown_approved or active member transitions.",
3063
+ remedy: "Retry after short delay; wait for shutdown state to settle."
3064
+ },
3065
+ {
3066
+ id: "skills-late-availability",
3067
+ title: "Skills may become available later in a session",
3068
+ severity: "low",
3069
+ appliesTo: ["Skills"],
3070
+ symptom: "Early calls show unknown skill/tool errors.",
3071
+ detection: "Later calls in same session succeed without config changes.",
3072
+ remedy: "Retry after initialization events are complete."
3073
+ },
3074
+ {
3075
+ id: "tool-count-sources-differ",
3076
+ title: "toolCatalogCount and availableTools have different sources",
3077
+ severity: "medium",
3078
+ appliesTo: ["internal-tools", "claude_code_check.availableTools", "compat-report"],
3079
+ symptom: "Tool counts appear inconsistent (for example 15 vs 28).",
3080
+ detection: "Compare compat-report.toolCounts and session-level availableTools.",
3081
+ remedy: "Treat catalog count as server-known baseline and availableTools as session runtime view from system/init.tools."
3082
+ },
3083
+ {
3084
+ id: "available-tools-not-exhaustive",
3085
+ title: "availableTools may omit internal features",
3086
+ severity: "low",
3087
+ appliesTo: ["claude_code_check.availableTools"],
3088
+ symptom: "A known feature is callable but not listed in availableTools.",
3089
+ detection: "Feature works while missing from availableTools list.",
3090
+ remedy: "Use availableTools as runtime hint, not exhaustive capability proof.",
3091
+ example: "Some internal features (e.g. ToolSearch) may not appear."
3092
+ }
3093
+ ];
3094
+ }
3095
+ function asVersionedPayload(params) {
3096
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3097
+ return {
3098
+ ...params.payload,
3099
+ schemaVersion: params.schemaVersion,
3100
+ updatedAt,
3101
+ etag: computeEtag(params.payload),
3102
+ stability: params.stability
3103
+ };
3104
+ }
1919
3105
  function registerResources(server, deps) {
3106
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3107
+ const resourceSchemaVersion = "1.3";
3108
+ const mcpProtocolVersion = "2025-03-26";
3109
+ const gotchasEntries = buildGotchasEntries();
3110
+ const catalogToolNames = new Set(defaultCatalogTools().map((tool) => tool.name));
3111
+ const staticResourceUris = Object.values(RESOURCE_URIS);
3112
+ const templateUris = Object.values(RESOURCE_TEMPLATES);
1920
3113
  const serverInfoUri = new URL(RESOURCE_URIS.serverInfo);
1921
3114
  server.registerResource(
1922
3115
  "server_info",
1923
3116
  serverInfoUri.toString(),
1924
3117
  {
1925
3118
  title: "Server Info",
1926
- description: "Static server metadata (version/platform/runtime).",
3119
+ description: "Server metadata (version/platform/runtime).",
1927
3120
  mimeType: "application/json"
1928
3121
  },
1929
- () => asTextResource(
3122
+ () => asJsonResource(
1930
3123
  serverInfoUri,
1931
- JSON.stringify(
1932
- {
3124
+ (() => {
3125
+ const base = {
1933
3126
  name: "claude-code-mcp",
3127
+ version: deps.version,
1934
3128
  node: process.version,
1935
3129
  platform: process.platform,
1936
3130
  arch: process.arch,
1937
- resources: Object.values(RESOURCE_URIS),
1938
- toolCatalogCount: deps.toolCache.getTools().length
1939
- },
1940
- null,
1941
- 2
1942
- ),
1943
- "application/json"
3131
+ mcpProtocolVersion,
3132
+ startedAt,
3133
+ uptimeSec: Math.floor(process.uptime()),
3134
+ resources: staticResourceUris,
3135
+ resourceTemplates: templateUris,
3136
+ toolCatalogCount: deps.toolCache.getTools().length,
3137
+ capabilities: {
3138
+ resources: true,
3139
+ toolsListChanged: true,
3140
+ resourcesListChanged: true,
3141
+ prompts: false,
3142
+ completions: false
3143
+ },
3144
+ limits: {
3145
+ maxSessions: serializeLimit(deps.sessionManager.getMaxSessions()),
3146
+ maxPendingPermissionsPerSession: serializeLimit(
3147
+ deps.sessionManager.getMaxPendingPermissionsPerSession()
3148
+ ),
3149
+ eventBuffer: deps.sessionManager.getEventBufferConfig(),
3150
+ pollDefaults: {
3151
+ runningMs: 3e3,
3152
+ waitingPermissionMs: 1e3
3153
+ }
3154
+ }
3155
+ };
3156
+ if (typeof process.env.CLAUDE_CODE_MCP_BUILD_COMMIT === "string") {
3157
+ const commit = process.env.CLAUDE_CODE_MCP_BUILD_COMMIT.trim();
3158
+ if (commit !== "") base.buildCommit = commit;
3159
+ }
3160
+ return asVersionedPayload({
3161
+ schemaVersion: resourceSchemaVersion,
3162
+ stability: "stable",
3163
+ payload: base
3164
+ });
3165
+ })()
1944
3166
  )
1945
3167
  );
1946
3168
  const toolsUri = new URL(RESOURCE_URIS.internalTools);
@@ -1949,13 +3171,22 @@ function registerResources(server, deps) {
1949
3171
  toolsUri.toString(),
1950
3172
  {
1951
3173
  title: "Internal Tools",
1952
- description: "Claude Code internal tool catalog (static + runtime-discovered).",
3174
+ description: "Claude Code internal tool catalog (runtime-aware).",
1953
3175
  mimeType: "application/json"
1954
3176
  },
1955
- () => asTextResource(
3177
+ () => asJsonResource(
1956
3178
  toolsUri,
1957
- JSON.stringify({ tools: deps.toolCache.getTools() }, null, 2),
1958
- "application/json"
3179
+ (() => {
3180
+ const base = {
3181
+ tools: deps.toolCache.getTools(),
3182
+ toolCatalogCount: deps.toolCache.getTools().length
3183
+ };
3184
+ return asVersionedPayload({
3185
+ schemaVersion: resourceSchemaVersion,
3186
+ stability: "stable",
3187
+ payload: base
3188
+ });
3189
+ })()
1959
3190
  )
1960
3191
  );
1961
3192
  const gotchasUri = new URL(RESOURCE_URIS.gotchas);
@@ -1972,46 +3203,429 @@ function registerResources(server, deps) {
1972
3203
  [
1973
3204
  "# claude-code-mcp: gotchas",
1974
3205
  "",
1975
- "- Permission approvals have a timeout (default 60s) and auto-deny (`actions[].expiresAt`/`remainingMs`).",
1976
- "- `Read` has a per-call size cap in practice (often ~256KB); for large files use `offset`/`limit` or chunk with `Grep`.",
1977
- "- `Edit` with `replace_all=true` is substring replacement; if no match is found the tool returns an error.",
1978
- "- `NotebookEdit` expects native Windows paths; this server normalizes MSYS paths like `/d/...` when possible.",
1979
- "- `TeamDelete` may require members to reach `shutdown_approved`; cleanup can be asynchronous during shutdown.",
1980
- '- Skills may become available later in the same session (early calls may show "Unknown").',
1981
- "- Some internal features (e.g. ToolSearch) may not appear in `availableTools` because it is derived from SDK `system/init.tools`.",
3206
+ ...gotchasEntries.map((entry) => `- ${entry.title}. ${entry.remedy}`),
1982
3207
  ""
1983
3208
  ].join("\n"),
1984
3209
  "text/markdown"
1985
3210
  )
1986
3211
  );
3212
+ const compatReportUri = new URL(RESOURCE_URIS.compatReport);
3213
+ const quickstartUri = new URL(RESOURCE_URIS.quickstart);
3214
+ server.registerResource(
3215
+ "quickstart",
3216
+ quickstartUri.toString(),
3217
+ {
3218
+ title: "Quickstart",
3219
+ description: "Minimal async polling flow for claude_code / claude_code_check.",
3220
+ mimeType: "text/markdown"
3221
+ },
3222
+ () => asTextResource(
3223
+ quickstartUri,
3224
+ [
3225
+ "# claude-code-mcp quickstart",
3226
+ "",
3227
+ "1. Call `claude_code` with `{ prompt }` and keep `sessionId`.",
3228
+ "2. Poll with `claude_code_check(action='poll')` using `nextCursor`.",
3229
+ "3. If actions are returned, respond with `claude_code_check(action='respond_permission')`.",
3230
+ "4. Continue polling until status becomes `idle` / `error` / `cancelled`.",
3231
+ "",
3232
+ "Notes:",
3233
+ "- `respond_user_input` is not supported on this backend.",
3234
+ "- `allowedTools` is pre-approval by default; set `strictAllowedTools=true` for strict allowlist behavior.",
3235
+ "- Prefer `responseMode='delta_compact'` for high-frequency polling."
3236
+ ].join("\n"),
3237
+ "text/markdown"
3238
+ )
3239
+ );
3240
+ const errorsUri = new URL(RESOURCE_URIS.errors);
3241
+ server.registerResource(
3242
+ "errors",
3243
+ errorsUri.toString(),
3244
+ {
3245
+ title: "Errors",
3246
+ description: "Structured error codes and remediation hints.",
3247
+ mimeType: "application/json"
3248
+ },
3249
+ () => {
3250
+ const codes = Object.values(ErrorCode);
3251
+ const hints = {
3252
+ ["INVALID_ARGUMENT" /* INVALID_ARGUMENT */]: "Validate required fields and enum values.",
3253
+ ["SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */]: "Session may be expired or server-restarted.",
3254
+ ["SESSION_BUSY" /* SESSION_BUSY */]: "Wait for running/waiting_permission session to settle.",
3255
+ ["PERMISSION_REQUEST_NOT_FOUND" /* PERMISSION_REQUEST_NOT_FOUND */]: "The permission request was already finished/expired.",
3256
+ ["PERMISSION_DENIED" /* PERMISSION_DENIED */]: "Check token/secrets/policy restrictions.",
3257
+ ["RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */]: "Reduce session count or increase server limits.",
3258
+ ["TIMEOUT" /* TIMEOUT */]: "Increase timeout or poll/respond more frequently.",
3259
+ ["CANCELLED" /* CANCELLED */]: "Request/session was cancelled by caller or shutdown.",
3260
+ ["INTERNAL" /* INTERNAL */]: "Inspect server logs and runtime environment."
3261
+ };
3262
+ return asJsonResource(
3263
+ errorsUri,
3264
+ asVersionedPayload({
3265
+ schemaVersion: resourceSchemaVersion,
3266
+ stability: "stable",
3267
+ payload: {
3268
+ codes,
3269
+ hints
3270
+ }
3271
+ })
3272
+ );
3273
+ }
3274
+ );
3275
+ server.registerResource(
3276
+ "compat_report",
3277
+ compatReportUri.toString(),
3278
+ {
3279
+ title: "Compatibility Report",
3280
+ description: "Compatibility diagnostics for MCP clients and local runtime assumptions.",
3281
+ mimeType: "application/json"
3282
+ },
3283
+ () => {
3284
+ const runtimeWarnings = [];
3285
+ if (process.platform === "win32" && !process.env.CLAUDE_CODE_GIT_BASH_PATH) {
3286
+ runtimeWarnings.push(
3287
+ "CLAUDE_CODE_GIT_BASH_PATH is not set. Auto-detection exists, but explicit path is more reliable for GUI-launched MCP clients."
3288
+ );
3289
+ }
3290
+ const diskResumeEnabled = process.env.CLAUDE_CODE_MCP_ALLOW_DISK_RESUME === "1";
3291
+ const resumeSecretConfigured = typeof process.env.CLAUDE_CODE_MCP_RESUME_SECRET === "string" && process.env.CLAUDE_CODE_MCP_RESUME_SECRET.trim() !== "";
3292
+ const runtimeToolStats = deps.sessionManager.getRuntimeToolStats();
3293
+ const toolCatalogCount = deps.toolCache.getTools().length;
3294
+ const detectedMismatches = [];
3295
+ if (runtimeToolStats.sessionsWithInitTools > 0 && runtimeToolStats.runtimeDiscoveredUniqueCount < toolCatalogCount) {
3296
+ detectedMismatches.push(
3297
+ "Runtime discovered tools are fewer than catalog tools; some features may be hidden or not surfaced in system/init.tools."
3298
+ );
3299
+ }
3300
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3301
+ const base = {
3302
+ transport: "stdio",
3303
+ samePlatformRequired: true,
3304
+ packageVersion: deps.version,
3305
+ runtime: {
3306
+ node: process.version,
3307
+ platform: process.platform,
3308
+ arch: process.arch
3309
+ },
3310
+ limits: {
3311
+ maxSessions: serializeLimit(deps.sessionManager.getMaxSessions()),
3312
+ maxPendingPermissionsPerSession: serializeLimit(
3313
+ deps.sessionManager.getMaxPendingPermissionsPerSession()
3314
+ ),
3315
+ eventBuffer: deps.sessionManager.getEventBufferConfig()
3316
+ },
3317
+ diskResume: {
3318
+ enabled: diskResumeEnabled,
3319
+ resumeSecretConfigured
3320
+ },
3321
+ features: {
3322
+ resources: true,
3323
+ resourceTemplates: true,
3324
+ toolsListChanged: true,
3325
+ resourcesListChanged: true,
3326
+ sessionInterrupt: true,
3327
+ allowForSessionDecision: true,
3328
+ respondUserInput: false,
3329
+ prompts: false,
3330
+ completions: false
3331
+ },
3332
+ recommendedSettings: {
3333
+ responseMode: "delta_compact",
3334
+ poll: {
3335
+ runningMs: 3e3,
3336
+ waitingPermissionMs: 1e3,
3337
+ cursorStrategy: "Persist nextCursor and de-duplicate by event.id."
3338
+ },
3339
+ timeouts: {
3340
+ sessionInitTimeoutMs: 1e4,
3341
+ permissionRequestTimeoutMs: 6e4,
3342
+ permissionRequestTimeoutMaxMs: 3e5
3343
+ }
3344
+ },
3345
+ guidance: [
3346
+ "Some clients cache tool descriptions at connect time. Prefer claude_code_check(pollOptions.includeTools=true) for runtime-authoritative tool lists.",
3347
+ "Use allowedTools/disallowedTools only with exact runtime tool names.",
3348
+ "Set strictAllowedTools=true when you need allowedTools to behave as a strict allowlist.",
3349
+ "This server assumes MCP client and server run on the same machine/platform.",
3350
+ "For high-frequency status checks, prefer responseMode='delta_compact'.",
3351
+ "respond_user_input is not supported on this backend; use poll/respond_permission flow."
3352
+ ],
3353
+ toolCounts: {
3354
+ catalogCount: toolCatalogCount,
3355
+ sessionsWithInitTools: runtimeToolStats.sessionsWithInitTools,
3356
+ runtimeDiscoveredUniqueCount: runtimeToolStats.runtimeDiscoveredUniqueCount,
3357
+ explain: "catalogCount is server catalog size; runtimeDiscoveredUniqueCount is union of system/init.tools across active sessions."
3358
+ },
3359
+ toolCatalogCount,
3360
+ detectedMismatches,
3361
+ runtimeWarnings,
3362
+ resourceTemplates: templateUris
3363
+ };
3364
+ const healthScore = Math.max(
3365
+ 0,
3366
+ 100 - runtimeWarnings.length * 10 - detectedMismatches.length * 15
3367
+ );
3368
+ return asJsonResource(
3369
+ compatReportUri,
3370
+ asVersionedPayload({
3371
+ schemaVersion: resourceSchemaVersion,
3372
+ stability: "stable",
3373
+ payload: {
3374
+ ...base,
3375
+ healthScore,
3376
+ updatedAt
3377
+ }
3378
+ })
3379
+ );
3380
+ }
3381
+ );
3382
+ server.registerResource(
3383
+ "session_snapshot_template",
3384
+ new ResourceTemplate(RESOURCE_TEMPLATES.sessionById, { list: void 0 }),
3385
+ {
3386
+ title: "Session Snapshot Template",
3387
+ description: "Read lightweight session diagnostics by sessionId.",
3388
+ mimeType: "application/json"
3389
+ },
3390
+ (uri, variables) => {
3391
+ const sessionId = extractSingleVariable(variables.sessionId) ?? extractSingleVariable(uri.searchParams.get("sessionId"));
3392
+ const payload = typeof sessionId !== "string" || sessionId.trim() === "" ? {
3393
+ found: false,
3394
+ message: "sessionId is required in URI template variable."
3395
+ } : (() => {
3396
+ const session = deps.sessionManager.get(sessionId);
3397
+ if (!session) {
3398
+ return {
3399
+ sessionId,
3400
+ found: false,
3401
+ message: `Session '${sessionId}' not found.`
3402
+ };
3403
+ }
3404
+ const base = deps.sessionManager.toPublicJSON(session);
3405
+ const stored = deps.sessionManager.getResult(sessionId);
3406
+ return {
3407
+ sessionId,
3408
+ found: true,
3409
+ session: {
3410
+ ...base,
3411
+ pendingPermissionCount: deps.sessionManager.getPendingPermissionCount(sessionId),
3412
+ eventCount: deps.sessionManager.getEventCount(sessionId),
3413
+ currentCursor: deps.sessionManager.getCurrentCursor(sessionId),
3414
+ lastEventId: deps.sessionManager.getLastEventId(sessionId),
3415
+ ttlMs: deps.sessionManager.getRemainingTtlMs(sessionId),
3416
+ lastError: stored?.type === "error" ? stored.result.result : void 0,
3417
+ lastErrorAt: stored?.type === "error" ? stored.createdAt : void 0,
3418
+ redactions: buildSessionRedactions(false)
3419
+ }
3420
+ };
3421
+ })();
3422
+ return asJsonResource(
3423
+ uri,
3424
+ asVersionedPayload({
3425
+ schemaVersion: resourceSchemaVersion,
3426
+ stability: "stable",
3427
+ payload
3428
+ })
3429
+ );
3430
+ }
3431
+ );
3432
+ server.registerResource(
3433
+ "runtime_tools_template",
3434
+ new ResourceTemplate(RESOURCE_TEMPLATES.runtimeTools, { list: void 0 }),
3435
+ {
3436
+ title: "Runtime Tools Template",
3437
+ description: "Read runtime tool view globally or for a specific sessionId.",
3438
+ mimeType: "application/json"
3439
+ },
3440
+ (uri, variables) => {
3441
+ const sessionId = extractSingleVariable(variables.sessionId) ?? extractSingleVariable(uri.searchParams.get("sessionId"));
3442
+ const internalToolCount = catalogToolNames.size;
3443
+ const payload = (() => {
3444
+ if (typeof sessionId === "string" && sessionId.trim() !== "") {
3445
+ const session = deps.sessionManager.get(sessionId);
3446
+ if (!session) {
3447
+ return {
3448
+ sessionId,
3449
+ source: "session_not_found",
3450
+ availableTools: [],
3451
+ toolCounts: {
3452
+ internalToolCount,
3453
+ runtimeAvailableCount: 0,
3454
+ sessionAugmentedToolCount: 0
3455
+ }
3456
+ };
3457
+ }
3458
+ const initTools = deps.sessionManager.getInitTools(sessionId) ?? [];
3459
+ const discovered = discoverToolsFromInit(initTools);
3460
+ const sessionAugmentedToolCount2 = discovered.filter(
3461
+ (tool) => !catalogToolNames.has(tool.name)
3462
+ ).length;
3463
+ return {
3464
+ sessionId,
3465
+ source: initTools.length > 0 ? "session_runtime" : "session_without_init",
3466
+ availableTools: discovered,
3467
+ toolCounts: {
3468
+ internalToolCount,
3469
+ runtimeAvailableCount: discovered.length,
3470
+ sessionAugmentedToolCount: sessionAugmentedToolCount2
3471
+ }
3472
+ };
3473
+ }
3474
+ const catalog = deps.toolCache.getTools();
3475
+ const sessionAugmentedToolCount = catalog.filter(
3476
+ (tool) => !catalogToolNames.has(tool.name)
3477
+ ).length;
3478
+ return {
3479
+ source: "catalog",
3480
+ availableTools: catalog,
3481
+ toolCounts: {
3482
+ internalToolCount,
3483
+ runtimeAvailableCount: catalog.length,
3484
+ sessionAugmentedToolCount
3485
+ }
3486
+ };
3487
+ })();
3488
+ return asJsonResource(
3489
+ uri,
3490
+ asVersionedPayload({
3491
+ schemaVersion: resourceSchemaVersion,
3492
+ stability: "stable",
3493
+ payload
3494
+ })
3495
+ );
3496
+ }
3497
+ );
3498
+ server.registerResource(
3499
+ "compat_diff_template",
3500
+ new ResourceTemplate(RESOURCE_TEMPLATES.compatDiff, { list: void 0 }),
3501
+ {
3502
+ title: "Compatibility Diff Template",
3503
+ description: "Returns client-specific compatibility guidance and recommended settings.",
3504
+ mimeType: "application/json"
3505
+ },
3506
+ (uri, variables) => {
3507
+ const clientRaw = extractSingleVariable(variables.client) ?? extractSingleVariable(uri.searchParams.get("client"));
3508
+ const clientFingerprint = (clientRaw ?? "unknown").trim();
3509
+ const key = clientFingerprint.toLowerCase();
3510
+ const profile = (() => {
3511
+ if (key.includes("codex")) {
3512
+ return {
3513
+ clientFamily: "codex",
3514
+ detectedMismatches: [],
3515
+ recommendations: [
3516
+ "Prefer responseMode='delta_compact' for fast status loops.",
3517
+ "Enable pollOptions.includeTools=true when exact runtime tool names are required."
3518
+ ]
3519
+ };
3520
+ }
3521
+ if (key.includes("claude")) {
3522
+ return {
3523
+ clientFamily: "claude",
3524
+ detectedMismatches: [],
3525
+ recommendations: [
3526
+ "Use resources and resource templates for low-latency diagnostics.",
3527
+ "Use allowedTools/disallowedTools with exact runtime names.",
3528
+ "Enable strictAllowedTools when running in locked-down governance mode."
3529
+ ]
3530
+ };
3531
+ }
3532
+ if (key.includes("cursor")) {
3533
+ return {
3534
+ clientFamily: "cursor",
3535
+ detectedMismatches: [
3536
+ "Verify that resources/list and resourceTemplates/list are refreshed after list_changed notifications."
3537
+ ],
3538
+ recommendations: [
3539
+ "Prefer claude_code_check polling as source of truth for runtime state.",
3540
+ "Fallback to tool calls if resource template support is partial."
3541
+ ]
3542
+ };
3543
+ }
3544
+ return {
3545
+ clientFamily: "generic",
3546
+ detectedMismatches: [],
3547
+ recommendations: [
3548
+ "Persist nextCursor and de-duplicate by event.id.",
3549
+ "Use responseMode='delta_compact' for high-frequency polling, full mode only for diagnostics."
3550
+ ]
3551
+ };
3552
+ })();
3553
+ return asJsonResource(
3554
+ uri,
3555
+ asVersionedPayload({
3556
+ schemaVersion: resourceSchemaVersion,
3557
+ stability: "experimental",
3558
+ payload: {
3559
+ clientFingerprint,
3560
+ ...profile,
3561
+ recommendedSettings: {
3562
+ responseMode: "delta_compact",
3563
+ poll: {
3564
+ runningMs: 3e3,
3565
+ waitingPermissionMs: 1e3,
3566
+ cursorStrategy: "Persist nextCursor and de-duplicate by event.id."
3567
+ }
3568
+ }
3569
+ }
3570
+ })
3571
+ );
3572
+ }
3573
+ );
1987
3574
  }
1988
3575
 
1989
3576
  // src/server.ts
1990
- var SERVER_VERSION = true ? "2.0.3" : "0.0.0-dev";
1991
- function createServer(serverCwd) {
3577
+ var SERVER_VERSION = true ? "2.4.0" : "0.0.0-dev";
3578
+ function createServerContext(serverCwd) {
1992
3579
  const sessionManager = new SessionManager();
1993
- const toolCache = new ToolDiscoveryCache();
1994
- const server = new McpServer({
1995
- name: "claude-code-mcp",
1996
- version: SERVER_VERSION
3580
+ const server = new McpServer(
3581
+ {
3582
+ name: "claude-code-mcp",
3583
+ version: SERVER_VERSION,
3584
+ title: "Claude Code MCP",
3585
+ description: "MCP server that runs Claude Code via the Claude Agent SDK with async polling and interactive permissions.",
3586
+ websiteUrl: "https://github.com/xihuai18/claude-code-mcp",
3587
+ icons: []
3588
+ },
3589
+ {
3590
+ capabilities: {
3591
+ logging: {},
3592
+ tools: { listChanged: true },
3593
+ resources: { listChanged: true }
3594
+ }
3595
+ }
3596
+ );
3597
+ const claudeCodeToolRef = {};
3598
+ const notifyInternalToolsResourceChanged = () => {
3599
+ if (!server.isConnected()) return;
3600
+ server.sendResourceListChanged();
3601
+ };
3602
+ const toolCache = new ToolDiscoveryCache(void 0, (tools) => {
3603
+ try {
3604
+ claudeCodeToolRef.current?.update({ description: buildInternalToolsDescription(tools) });
3605
+ if (server.isConnected()) {
3606
+ server.sendToolListChanged();
3607
+ }
3608
+ notifyInternalToolsResourceChanged();
3609
+ } catch {
3610
+ }
1997
3611
  });
1998
3612
  const agentDefinitionSchema = z.object({
1999
3613
  description: z.string(),
2000
3614
  prompt: z.string(),
2001
- tools: z.array(z.string()).optional(),
2002
- disallowedTools: z.array(z.string()).optional(),
2003
- model: z.enum(AGENT_MODELS).optional(),
2004
- maxTurns: z.number().int().positive().optional(),
2005
- mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional(),
2006
- skills: z.array(z.string()).optional(),
2007
- criticalSystemReminder_EXPERIMENTAL: z.string().optional()
3615
+ tools: z.array(z.string()).optional().describe("Default: inherit"),
3616
+ disallowedTools: z.array(z.string()).optional().describe("Default: none"),
3617
+ model: z.enum(AGENT_MODELS).optional().describe("Default: inherit"),
3618
+ maxTurns: z.number().int().positive().optional().describe("Default: none"),
3619
+ mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional().describe("Default: inherit"),
3620
+ skills: z.array(z.string()).optional().describe("Default: none"),
3621
+ criticalSystemReminder_EXPERIMENTAL: z.string().optional().describe("Default: none")
2008
3622
  });
2009
3623
  const systemPromptSchema = z.union([
2010
3624
  z.string(),
2011
3625
  z.object({
2012
3626
  type: z.literal("preset"),
2013
3627
  preset: z.literal("claude_code"),
2014
- append: z.string().optional().describe("Appended to preset prompt")
3628
+ append: z.string().optional().describe("Default: none")
2015
3629
  })
2016
3630
  ]);
2017
3631
  const toolsConfigSchema = z.union([
@@ -2029,47 +3643,137 @@ function createServer(serverCwd) {
2029
3643
  }),
2030
3644
  z.object({ type: z.literal("disabled") })
2031
3645
  ]);
3646
+ const effortOptionSchema = z.enum(EFFORT_LEVELS).optional();
3647
+ const thinkingOptionSchema = thinkingSchema.optional();
2032
3648
  const outputFormatSchema = z.object({
2033
3649
  type: z.literal("json_schema"),
2034
3650
  schema: z.record(z.string(), z.unknown())
2035
3651
  });
2036
- const advancedOptionsSchema = z.object({
2037
- tools: toolsConfigSchema.optional().describe("Visible tool set. Default: SDK"),
3652
+ const sharedOptionFieldsSchemaShape = {
3653
+ tools: toolsConfigSchema.optional().describe("Tool set. Default: SDK"),
2038
3654
  persistSession: z.boolean().optional().describe("Default: true"),
2039
- sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000"),
2040
- agents: z.record(z.string(), agentDefinitionSchema).optional().describe("Sub-agent definitions. Default: none"),
2041
- agent: z.string().optional().describe("Primary agent name (from 'agents'). Default: none"),
3655
+ agents: z.record(z.string(), agentDefinitionSchema).optional().describe("Default: none"),
3656
+ agent: z.string().optional().describe("Default: none"),
2042
3657
  maxBudgetUsd: z.number().positive().optional().describe("Default: none"),
2043
- effort: z.enum(EFFORT_LEVELS).optional().describe("Default: SDK"),
2044
3658
  betas: z.array(z.string()).optional().describe("Default: none"),
2045
3659
  additionalDirectories: z.array(z.string()).optional().describe("Default: none"),
2046
- outputFormat: outputFormatSchema.optional().describe("Default: none (plain text)"),
2047
- thinking: thinkingSchema.optional().describe("Default: SDK"),
3660
+ outputFormat: outputFormatSchema.optional().describe("Default: none"),
2048
3661
  pathToClaudeCodeExecutable: z.string().optional().describe("Default: SDK-bundled"),
2049
3662
  mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("Default: none"),
2050
3663
  sandbox: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
2051
3664
  fallbackModel: z.string().optional().describe("Default: none"),
2052
3665
  enableFileCheckpointing: z.boolean().optional().describe("Default: false"),
2053
- includePartialMessages: z.boolean().optional().describe("Stream events to claude_code_check. Default: false"),
3666
+ includePartialMessages: z.boolean().optional().describe("Default: false"),
2054
3667
  strictMcpConfig: z.boolean().optional().describe("Default: false"),
2055
- settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. [] = isolation mode"),
3668
+ settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. []=isolation"),
2056
3669
  debug: z.boolean().optional().describe("Default: false"),
2057
- debugFile: z.string().optional().describe("Enables debug. Default: none"),
2058
- env: z.record(z.string(), z.string().optional()).optional().describe("Merged with process.env. Default: none")
2059
- }).optional().describe("Low-frequency SDK options (all optional)");
2060
- server.tool(
3670
+ debugFile: z.string().optional().describe("Default: none"),
3671
+ env: z.record(z.string(), z.string().optional()).optional().describe("Default: none")
3672
+ };
3673
+ const advancedOptionFieldsSchemaShape = {
3674
+ ...sharedOptionFieldsSchemaShape,
3675
+ effort: effortOptionSchema.describe("Deprecated, use top-level. Default: SDK"),
3676
+ thinking: thinkingOptionSchema.describe("Deprecated, use top-level. Default: SDK")
3677
+ };
3678
+ const diskResumeOptionFieldsSchemaShape = {
3679
+ ...sharedOptionFieldsSchemaShape,
3680
+ effort: effortOptionSchema.describe("Default: SDK"),
3681
+ thinking: thinkingOptionSchema.describe("Default: SDK")
3682
+ };
3683
+ const advancedOptionsSchema = z.object({
3684
+ ...advancedOptionFieldsSchemaShape,
3685
+ sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000")
3686
+ }).optional().describe("Default: none");
3687
+ const diskResumeConfigSchema = z.object({
3688
+ resumeToken: z.string(),
3689
+ cwd: z.string(),
3690
+ allowedTools: z.array(z.string()).optional().describe("Default: []"),
3691
+ disallowedTools: z.array(z.string()).optional().describe("Default: []"),
3692
+ strictAllowedTools: z.boolean().optional().describe("Default: false"),
3693
+ maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
3694
+ model: z.string().optional().describe("Default: SDK"),
3695
+ systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
3696
+ resumeSessionAt: z.string().optional().describe("Default: none"),
3697
+ ...diskResumeOptionFieldsSchemaShape
3698
+ }).optional().describe("Default: none");
3699
+ const startResultSchema = z.object({
3700
+ sessionId: z.string(),
3701
+ status: z.enum(["running", "error"]),
3702
+ pollInterval: z.number().optional(),
3703
+ resumeToken: z.string().optional(),
3704
+ compatWarnings: z.array(z.string()).optional(),
3705
+ error: z.string().optional()
3706
+ }).passthrough();
3707
+ const sessionResultSchema = z.object({
3708
+ sessions: z.array(z.record(z.string(), z.unknown())),
3709
+ message: z.string().optional(),
3710
+ isError: z.boolean().optional()
3711
+ }).passthrough();
3712
+ const checkEventSchema = z.object({
3713
+ id: z.number().int().nonnegative(),
3714
+ type: z.string(),
3715
+ data: z.unknown(),
3716
+ timestamp: z.string()
3717
+ }).passthrough();
3718
+ const checkActionSchema = z.object({
3719
+ type: z.string(),
3720
+ requestId: z.string().optional(),
3721
+ toolName: z.string().optional(),
3722
+ input: z.record(z.string(), z.unknown()).optional(),
3723
+ summary: z.string().optional()
3724
+ }).passthrough();
3725
+ const checkResultSchema = z.object({
3726
+ sessionId: z.string(),
3727
+ status: z.string(),
3728
+ pollInterval: z.number().optional(),
3729
+ cursorResetTo: z.number().optional(),
3730
+ truncated: z.boolean().optional(),
3731
+ truncatedFields: z.array(z.string()).optional(),
3732
+ events: z.array(checkEventSchema),
3733
+ nextCursor: z.number().optional(),
3734
+ availableTools: z.array(z.record(z.string(), z.unknown())).optional(),
3735
+ toolValidation: z.object({
3736
+ runtimeToolsKnown: z.boolean(),
3737
+ unknownAllowedTools: z.array(z.string()),
3738
+ unknownDisallowedTools: z.array(z.string())
3739
+ }).optional(),
3740
+ compatWarnings: z.array(z.string()).optional(),
3741
+ actions: z.array(checkActionSchema).optional(),
3742
+ result: z.unknown().optional(),
3743
+ cancelledAt: z.string().optional(),
3744
+ cancelledReason: z.string().optional(),
3745
+ cancelledSource: z.string().optional(),
3746
+ lastEventId: z.number().optional(),
3747
+ lastToolUseId: z.string().optional(),
3748
+ isError: z.boolean().optional(),
3749
+ error: z.string().optional()
3750
+ }).passthrough();
3751
+ claudeCodeToolRef.current = server.registerTool(
2061
3752
  "claude_code",
2062
- buildInternalToolsDescription(toolCache.getTools()),
2063
3753
  {
2064
- prompt: z.string().describe("Task or question"),
2065
- cwd: z.string().optional().describe("Working directory. Default: server cwd"),
2066
- allowedTools: z.array(z.string()).optional().describe("Auto-approved tools, e.g. ['Bash','Read','Write','Edit']. Default: []"),
2067
- disallowedTools: z.array(z.string()).optional().describe("Forbidden tools (priority over allowedTools). Default: []"),
2068
- maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
2069
- model: z.string().optional().describe("e.g. 'opus'. Default: SDK"),
2070
- systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
2071
- permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Auto-deny timeout (ms). Default: 60000"),
2072
- advanced: advancedOptionsSchema
3754
+ description: buildInternalToolsDescription(toolCache.getTools()),
3755
+ inputSchema: {
3756
+ prompt: z.string().describe("Prompt"),
3757
+ cwd: z.string().optional().describe("Working dir. Default: server cwd"),
3758
+ allowedTools: z.array(z.string()).optional().describe("Default: []"),
3759
+ disallowedTools: z.array(z.string()).optional().describe("Default: []"),
3760
+ strictAllowedTools: z.boolean().optional().describe("Default: false"),
3761
+ maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
3762
+ model: z.string().optional().describe("Default: SDK"),
3763
+ effort: effortOptionSchema.describe("Default: SDK"),
3764
+ thinking: thinkingOptionSchema.describe("Default: SDK"),
3765
+ systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
3766
+ permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000, clamped to 300000"),
3767
+ sessionInitTimeoutMs: z.number().int().positive().optional().describe("Deprecated, use advanced.sessionInitTimeoutMs. Default: 10000"),
3768
+ advanced: advancedOptionsSchema
3769
+ },
3770
+ outputSchema: startResultSchema,
3771
+ annotations: {
3772
+ readOnlyHint: false,
3773
+ destructiveHint: true,
3774
+ idempotentHint: false,
3775
+ openWorldHint: true
3776
+ }
2073
3777
  },
2074
3778
  async (args, extra) => {
2075
3779
  try {
@@ -2088,76 +3792,50 @@ function createServer(serverCwd) {
2088
3792
  text: JSON.stringify(result, null, 2)
2089
3793
  }
2090
3794
  ],
3795
+ structuredContent: result,
2091
3796
  isError
2092
3797
  };
2093
3798
  } catch (err) {
2094
3799
  const message = err instanceof Error ? err.message : String(err);
3800
+ const errorResult = {
3801
+ sessionId: "",
3802
+ status: "error",
3803
+ error: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
3804
+ };
2095
3805
  return {
2096
3806
  content: [
2097
3807
  {
2098
3808
  type: "text",
2099
- text: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
3809
+ text: JSON.stringify(errorResult, null, 2)
2100
3810
  }
2101
3811
  ],
3812
+ structuredContent: errorResult,
2102
3813
  isError: true
2103
3814
  };
2104
3815
  }
2105
3816
  }
2106
3817
  );
2107
- server.tool(
3818
+ server.registerTool(
2108
3819
  "claude_code_reply",
2109
- `Send a follow-up message to an existing Claude Code session.
2110
-
2111
- The agent retains full context from previous turns (files read, code analyzed, conversation history). Returns immediately \u2014 use claude_code_check to poll for the result.
2112
-
2113
- Supports session forking (forkSession=true) to explore alternative approaches without modifying the original session.
2114
-
2115
- Defaults:
2116
- - forkSession: false
2117
- - sessionInitTimeoutMs: 10000 (only used when forkSession=true)
2118
- - permissionRequestTimeoutMs: 60000
2119
- - Disk resume: disabled unless CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1
2120
-
2121
- Disk resume: If the server restarted and the session is no longer in memory, set CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1 to let the agent resume from its on-disk transcript. Pass diskResumeConfig with resumeToken and session parameters.`,
2122
3820
  {
2123
- sessionId: z.string().describe("Session ID from claude_code"),
2124
- prompt: z.string().describe("Follow-up message"),
2125
- forkSession: z.boolean().optional().describe("Branch into new session copy. Default: false"),
2126
- sessionInitTimeoutMs: z.number().int().positive().optional().describe("Fork init timeout (ms). Default: 10000"),
2127
- permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Auto-deny timeout (ms). Default: 60000"),
2128
- diskResumeConfig: z.object({
2129
- resumeToken: z.string().optional().describe("Required"),
2130
- cwd: z.string().optional().describe("Required"),
2131
- allowedTools: z.array(z.string()).optional().describe("Default: []"),
2132
- disallowedTools: z.array(z.string()).optional().describe("Default: []"),
2133
- tools: toolsConfigSchema.optional().describe("Default: SDK"),
2134
- persistSession: z.boolean().optional().describe("Default: true"),
2135
- maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
2136
- model: z.string().optional().describe("Default: SDK"),
2137
- systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
2138
- agents: z.record(z.string(), agentDefinitionSchema).optional().describe("Default: none"),
2139
- agent: z.string().optional().describe("Default: none"),
2140
- maxBudgetUsd: z.number().positive().optional().describe("Default: none"),
2141
- effort: z.enum(EFFORT_LEVELS).optional().describe("Default: SDK"),
2142
- betas: z.array(z.string()).optional().describe("Default: none"),
2143
- additionalDirectories: z.array(z.string()).optional().describe("Default: none"),
2144
- outputFormat: outputFormatSchema.optional().describe("Default: none"),
2145
- thinking: thinkingSchema.optional().describe("Default: SDK"),
2146
- resumeSessionAt: z.string().optional().describe("Resume to specific message UUID. Default: none"),
2147
- pathToClaudeCodeExecutable: z.string().optional().describe("Default: SDK-bundled"),
2148
- mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("Default: none"),
2149
- sandbox: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
2150
- fallbackModel: z.string().optional().describe("Default: none"),
2151
- enableFileCheckpointing: z.boolean().optional().describe("Default: false"),
2152
- includePartialMessages: z.boolean().optional().describe("Stream events to claude_code_check. Default: false"),
2153
- strictMcpConfig: z.boolean().optional().describe("Default: false"),
2154
- settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. [] = isolation mode"),
2155
- debug: z.boolean().optional().describe("Default: false"),
2156
- debugFile: z.string().optional().describe("Enables debug. Default: none"),
2157
- env: z.record(z.string(), z.string().optional()).optional().describe("Default: none")
2158
- }).optional().describe(
2159
- "Disk resume config (needs CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1). Requires resumeToken + cwd."
2160
- )
3821
+ description: "Send a follow-up to an existing session. Returns immediately; use claude_code_check to poll.",
3822
+ inputSchema: {
3823
+ sessionId: z.string().describe("Session ID"),
3824
+ prompt: z.string().describe("Prompt"),
3825
+ forkSession: z.boolean().optional().describe("Default: false"),
3826
+ effort: effortOptionSchema.describe("Default: SDK"),
3827
+ thinking: thinkingOptionSchema.describe("Default: SDK"),
3828
+ sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000"),
3829
+ permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000, clamped to 300000"),
3830
+ diskResumeConfig: diskResumeConfigSchema
3831
+ },
3832
+ outputSchema: startResultSchema,
3833
+ annotations: {
3834
+ readOnlyHint: false,
3835
+ destructiveHint: true,
3836
+ idempotentHint: false,
3837
+ openWorldHint: true
3838
+ }
2161
3839
  },
2162
3840
  async (args, extra) => {
2163
3841
  try {
@@ -2170,137 +3848,339 @@ Disk resume: If the server restarted and the session is no longer in memory, set
2170
3848
  text: JSON.stringify(result, null, 2)
2171
3849
  }
2172
3850
  ],
3851
+ structuredContent: result,
2173
3852
  isError
2174
3853
  };
2175
3854
  } catch (err) {
2176
3855
  const message = err instanceof Error ? err.message : String(err);
3856
+ const errorResult = {
3857
+ sessionId: "",
3858
+ status: "error",
3859
+ error: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
3860
+ };
2177
3861
  return {
2178
3862
  content: [
2179
3863
  {
2180
3864
  type: "text",
2181
- text: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
3865
+ text: JSON.stringify(errorResult, null, 2)
2182
3866
  }
2183
3867
  ],
3868
+ structuredContent: errorResult,
2184
3869
  isError: true
2185
3870
  };
2186
3871
  }
2187
3872
  }
2188
3873
  );
2189
- server.tool(
3874
+ server.registerTool(
2190
3875
  "claude_code_session",
2191
- `List, inspect, or cancel Claude Code sessions.
2192
-
2193
- - action="list": Get all sessions with their status, cost, turn count, and settings.
2194
- - action="get": Get full details for one session (pass sessionId). Add includeSensitive=true to also see cwd, systemPrompt, agents, and additionalDirectories.
2195
- - action="cancel": Stop a running session immediately (pass sessionId).`,
2196
3876
  {
2197
- action: z.enum(SESSION_ACTIONS),
2198
- sessionId: z.string().optional().describe("Required for 'get' and 'cancel'"),
2199
- includeSensitive: z.boolean().optional().describe("Include cwd/systemPrompt/agents/additionalDirectories. Default: false")
3877
+ description: "List, inspect, cancel, or interrupt sessions.",
3878
+ inputSchema: {
3879
+ action: z.enum(SESSION_ACTIONS),
3880
+ sessionId: z.string().optional().describe("Required for get/cancel/interrupt"),
3881
+ includeSensitive: z.boolean().optional().describe("Default: false")
3882
+ },
3883
+ outputSchema: sessionResultSchema,
3884
+ annotations: {
3885
+ readOnlyHint: false,
3886
+ destructiveHint: true,
3887
+ idempotentHint: false,
3888
+ openWorldHint: false
3889
+ }
2200
3890
  },
2201
- async (args) => {
2202
- const result = executeClaudeCodeSession(args, sessionManager);
2203
- return {
2204
- content: [
2205
- {
2206
- type: "text",
2207
- text: JSON.stringify(result, null, 2)
2208
- }
2209
- ],
2210
- isError: result.isError ?? false
2211
- };
3891
+ async (args, extra) => {
3892
+ try {
3893
+ const result = executeClaudeCodeSession(args, sessionManager, extra.signal);
3894
+ return {
3895
+ content: [
3896
+ {
3897
+ type: "text",
3898
+ text: JSON.stringify(result, null, 2)
3899
+ }
3900
+ ],
3901
+ structuredContent: result,
3902
+ isError: result.isError ?? false
3903
+ };
3904
+ } catch (err) {
3905
+ const message = err instanceof Error ? err.message : String(err);
3906
+ const errorResult = {
3907
+ sessions: [],
3908
+ message: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`,
3909
+ isError: true
3910
+ };
3911
+ return {
3912
+ content: [
3913
+ {
3914
+ type: "text",
3915
+ text: JSON.stringify(errorResult, null, 2)
3916
+ }
3917
+ ],
3918
+ structuredContent: errorResult,
3919
+ isError: true
3920
+ };
3921
+ }
2212
3922
  }
2213
3923
  );
2214
- server.tool(
3924
+ server.registerTool(
2215
3925
  "claude_code_check",
2216
- `Query a running session for new events, retrieve the final result, or respond to permission requests.
2217
-
2218
- Two actions:
2219
-
2220
- Defaults (poll):
2221
- - responseMode: "minimal"
2222
- - minimal mode strips verbose fields from assistant messages (usage, model, id, cache_control) and filters out noisy progress events (tool_progress, auth_status)
2223
- - maxEvents: 200 in minimal mode (unlimited in full mode unless maxEvents is set)
2224
-
2225
- action="poll" \u2014 Retrieve events since the last poll.
2226
- Returns events (agent output, progress updates, permission requests, errors, final result).
2227
- Pass the cursor from the previous poll's nextCursor for incremental updates. Omit cursor to get all buffered events.
2228
- If the agent is waiting for permission, the response includes an "actions" array with pending requests.
2229
-
2230
- action="respond_permission" \u2014 Approve or deny a pending permission request.
2231
- Pass the requestId from the actions array, plus decision="allow" or decision="deny".
2232
- Approving resumes agent execution. Denying (with optional interrupt=true) can halt the entire session.
2233
- The response also includes the latest poll state (events, status, etc.), so a separate poll call is not needed.`,
2234
3926
  {
2235
- action: z.enum(CHECK_ACTIONS),
2236
- sessionId: z.string().describe("Target session ID"),
2237
- cursor: z.number().int().nonnegative().optional().describe("Event offset for incremental poll. Default: 0"),
2238
- responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Default: 'minimal'"),
2239
- maxEvents: z.number().int().positive().optional().describe("Events per poll. Default: 200 (minimal)"),
2240
- requestId: z.string().optional().describe("Permission request ID (from actions[])"),
2241
- decision: z.enum(["allow", "deny"]).optional().describe("For respond_permission"),
2242
- denyMessage: z.string().optional().describe("Reason shown to agent on deny. Default: 'Permission denied by caller'"),
2243
- interrupt: z.boolean().optional().describe("Stop session on deny. Default: false"),
2244
- pollOptions: z.object({
2245
- includeTools: z.boolean().optional().describe("Default: false"),
2246
- includeEvents: z.boolean().optional().describe("Default: true"),
2247
- includeActions: z.boolean().optional().describe("Default: true"),
2248
- includeResult: z.boolean().optional().describe("Default: true"),
2249
- includeUsage: z.boolean().optional().describe("Default: full=true, minimal=false"),
2250
- includeModelUsage: z.boolean().optional().describe("Default: full=true, minimal=false"),
2251
- includeStructuredOutput: z.boolean().optional().describe("Default: full=true, minimal=false"),
2252
- includeTerminalEvents: z.boolean().optional().describe("Default: full=true, minimal=false"),
2253
- includeProgressEvents: z.boolean().optional().describe("Default: full=true, minimal=false")
2254
- }).optional().describe("Override responseMode defaults"),
2255
- permissionOptions: z.object({
2256
- updatedInput: z.record(z.string(), z.unknown()).optional().describe("Replace tool input on allow. Default: none"),
2257
- updatedPermissions: z.array(z.record(z.string(), z.unknown())).optional().describe("Update permission rules on allow. Default: none")
2258
- }).optional().describe("Allow-only: modify tool input or update rules")
3927
+ description: "Poll session events or respond to permission requests.",
3928
+ inputSchema: {
3929
+ action: z.enum(CHECK_ACTIONS),
3930
+ sessionId: z.string().describe("Session ID"),
3931
+ cursor: z.number().int().nonnegative().optional().describe("Default: 0"),
3932
+ responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Default: 'minimal'. Use 'delta_compact' for lightweight polling."),
3933
+ maxEvents: z.number().int().positive().optional().describe("Default: 200 (minimal), unlimited (full/delta_compact)"),
3934
+ requestId: z.string().optional().describe("Default: none"),
3935
+ decision: z.enum(["allow", "deny", "allow_for_session"]).optional().describe("Default: none"),
3936
+ denyMessage: z.string().optional().describe("Default: 'Permission denied by caller'"),
3937
+ interrupt: z.boolean().optional().describe("Default: false"),
3938
+ pollOptions: z.object({
3939
+ includeTools: z.boolean().optional().describe("Default: false"),
3940
+ includeEvents: z.boolean().optional().describe("Default: true"),
3941
+ includeActions: z.boolean().optional().describe("Default: true"),
3942
+ includeResult: z.boolean().optional().describe("Default: true"),
3943
+ includeUsage: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3944
+ includeModelUsage: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3945
+ includeStructuredOutput: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3946
+ includeTerminalEvents: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3947
+ includeProgressEvents: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3948
+ maxBytes: z.number().int().positive().optional().describe("Default: unlimited")
3949
+ }).optional().describe("Default: none"),
3950
+ permissionOptions: z.object({
3951
+ updatedInput: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
3952
+ updatedPermissions: z.array(z.record(z.string(), z.unknown())).optional().describe("Default: none")
3953
+ }).optional().describe("Default: none")
3954
+ },
3955
+ outputSchema: checkResultSchema,
3956
+ annotations: {
3957
+ readOnlyHint: false,
3958
+ destructiveHint: true,
3959
+ idempotentHint: false,
3960
+ openWorldHint: false
3961
+ }
2259
3962
  },
2260
- async (args) => {
2261
- const result = executeClaudeCodeCheck(args, sessionManager, toolCache);
2262
- const isError = result.isError === true;
2263
- return {
2264
- content: [
2265
- {
2266
- type: "text",
2267
- text: JSON.stringify(result, null, 2)
2268
- }
2269
- ],
2270
- isError
2271
- };
3963
+ async (args, extra) => {
3964
+ try {
3965
+ const result = executeClaudeCodeCheck(args, sessionManager, toolCache, extra.signal);
3966
+ const isError = result.isError === true;
3967
+ return {
3968
+ content: [
3969
+ {
3970
+ type: "text",
3971
+ text: JSON.stringify(result, null, 2)
3972
+ }
3973
+ ],
3974
+ structuredContent: result,
3975
+ isError
3976
+ };
3977
+ } catch (err) {
3978
+ const message = err instanceof Error ? err.message : String(err);
3979
+ const errorResult = {
3980
+ sessionId: args.sessionId ?? "",
3981
+ status: "error",
3982
+ events: [],
3983
+ isError: true,
3984
+ error: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
3985
+ };
3986
+ return {
3987
+ content: [
3988
+ {
3989
+ type: "text",
3990
+ text: JSON.stringify(errorResult, null, 2)
3991
+ }
3992
+ ],
3993
+ structuredContent: errorResult,
3994
+ isError: true
3995
+ };
3996
+ }
2272
3997
  }
2273
3998
  );
2274
- registerResources(server, { toolCache });
2275
- const originalClose = server.close.bind(server);
2276
- server.close = async () => {
2277
- sessionManager.destroy();
2278
- await originalClose();
2279
- };
2280
- return server;
3999
+ registerResources(server, { toolCache, version: SERVER_VERSION, sessionManager });
4000
+ return { server, sessionManager, toolCache };
4001
+ }
4002
+
4003
+ // src/utils/runtime-errors.ts
4004
+ var BENIGN_CODES = /* @__PURE__ */ new Set([
4005
+ "EPIPE",
4006
+ "ECONNRESET",
4007
+ "ERR_STREAM_WRITE_AFTER_END",
4008
+ "ERR_STREAM_DESTROYED",
4009
+ "ABORT_ERR"
4010
+ ]);
4011
+ var BENIGN_MESSAGE_PATTERNS = [
4012
+ "operation aborted",
4013
+ "request was cancelled",
4014
+ "transport closed",
4015
+ "write after end",
4016
+ "stream was destroyed",
4017
+ "cannot call write after a stream was destroyed",
4018
+ "the pipe is being closed"
4019
+ ];
4020
+ function isBenignRuntimeError(error) {
4021
+ if (!(error instanceof Error)) return false;
4022
+ const withCode = error;
4023
+ const code = typeof withCode.code === "string" ? withCode.code : void 0;
4024
+ if (code && BENIGN_CODES.has(code)) return true;
4025
+ if (error.name === "AbortError") return true;
4026
+ const message = error.message.toLowerCase();
4027
+ return BENIGN_MESSAGE_PATTERNS.some((pattern) => message.includes(pattern));
2281
4028
  }
2282
4029
 
2283
4030
  // src/index.ts
4031
+ var STDIN_SHUTDOWN_CHECK_MS = 750;
4032
+ var STDIN_SHUTDOWN_MAX_WAIT_MS = process.platform === "win32" ? 15e3 : 1e4;
4033
+ function summarizeSessions(ctx) {
4034
+ const sessions = ctx.sessionManager.list();
4035
+ const running = sessions.filter((s) => s.status === "running").length;
4036
+ const waitingPermission = sessions.filter((s) => s.status === "waiting_permission").length;
4037
+ return {
4038
+ total: sessions.length,
4039
+ running,
4040
+ waitingPermission,
4041
+ terminal: sessions.length - running - waitingPermission
4042
+ };
4043
+ }
2284
4044
  async function main() {
2285
4045
  const serverCwd = process.cwd();
2286
- const server = createServer(serverCwd);
4046
+ const ctx = createServerContext(serverCwd);
4047
+ const server = ctx.server;
4048
+ const sessionManager = ctx.sessionManager;
2287
4049
  const transport = new StdioServerTransport();
2288
4050
  let closing = false;
2289
- const shutdown = async () => {
4051
+ let lastExitCode = 0;
4052
+ let stdinClosedAt;
4053
+ let stdinClosedReason;
4054
+ let stdinShutdownTimer;
4055
+ const onStdinEnd = () => handleStdinTerminated("end");
4056
+ const onStdinClose = () => handleStdinTerminated("close");
4057
+ const clearStdinShutdownTimer = () => {
4058
+ if (stdinShutdownTimer) {
4059
+ clearTimeout(stdinShutdownTimer);
4060
+ stdinShutdownTimer = void 0;
4061
+ }
4062
+ };
4063
+ const shutdown = async (reason = "unknown") => {
2290
4064
  if (closing) return;
2291
4065
  closing = true;
4066
+ clearStdinShutdownTimer();
4067
+ if (typeof process.stdin.off === "function") {
4068
+ process.stdin.off("error", handleStdinError);
4069
+ process.stdin.off("end", onStdinEnd);
4070
+ process.stdin.off("close", onStdinClose);
4071
+ }
4072
+ const forceExitMs = process.platform === "win32" ? 1e4 : 5e3;
4073
+ const forceExitTimer = setTimeout(() => process.exit(lastExitCode), forceExitMs);
4074
+ if (forceExitTimer.unref) forceExitTimer.unref();
4075
+ const sessionSummary = summarizeSessions(ctx);
2292
4076
  try {
4077
+ if (server?.isConnected()) {
4078
+ await server.sendLoggingMessage({
4079
+ level: "info",
4080
+ data: { event: "server_stopping", reason, ...sessionSummary }
4081
+ });
4082
+ }
4083
+ sessionManager.destroy();
2293
4084
  await server.close();
2294
4085
  } catch {
2295
4086
  }
2296
- process.exitCode = 0;
2297
- setTimeout(() => process.exit(0), 100);
4087
+ process.exitCode = lastExitCode;
4088
+ try {
4089
+ await new Promise((resolve) => process.stderr.write("", () => resolve()));
4090
+ } catch {
4091
+ } finally {
4092
+ clearTimeout(forceExitTimer);
4093
+ }
4094
+ };
4095
+ function handleStdinError(error) {
4096
+ console.error("stdin error:", error);
4097
+ lastExitCode = 1;
4098
+ void shutdown("stdin_error");
4099
+ }
4100
+ function hasActiveSessions() {
4101
+ return sessionManager.list().some((s) => s.status === "running" || s.status === "waiting_permission");
4102
+ }
4103
+ const evaluateStdinTermination = () => {
4104
+ if (closing || stdinClosedAt === void 0) return;
4105
+ const stdinUnavailable = process.stdin.destroyed || process.stdin.readableEnded || !process.stdin.readable;
4106
+ if (!stdinUnavailable) {
4107
+ stdinClosedAt = void 0;
4108
+ stdinClosedReason = void 0;
4109
+ return;
4110
+ }
4111
+ const elapsedMs = Date.now() - stdinClosedAt;
4112
+ const active = hasActiveSessions();
4113
+ const connected = server.isConnected();
4114
+ if (!active && !connected) {
4115
+ void shutdown(`stdin_${stdinClosedReason ?? "closed"}`);
4116
+ return;
4117
+ }
4118
+ if (elapsedMs >= STDIN_SHUTDOWN_MAX_WAIT_MS) {
4119
+ void shutdown(`stdin_${stdinClosedReason ?? "closed"}_timeout`);
4120
+ return;
4121
+ }
4122
+ stdinShutdownTimer = setTimeout(evaluateStdinTermination, STDIN_SHUTDOWN_CHECK_MS);
4123
+ if (stdinShutdownTimer.unref) stdinShutdownTimer.unref();
4124
+ };
4125
+ function handleStdinTerminated(event) {
4126
+ if (closing) return;
4127
+ if (stdinClosedAt === void 0) {
4128
+ stdinClosedAt = Date.now();
4129
+ stdinClosedReason = event;
4130
+ console.error(`[lifecycle] stdin ${event} observed; entering guarded shutdown checks`);
4131
+ }
4132
+ clearStdinShutdownTimer();
4133
+ stdinShutdownTimer = setTimeout(evaluateStdinTermination, STDIN_SHUTDOWN_CHECK_MS);
4134
+ if (stdinShutdownTimer.unref) stdinShutdownTimer.unref();
4135
+ }
4136
+ const handleUnexpectedError = (error) => {
4137
+ if (isBenignRuntimeError(error)) {
4138
+ console.error("Ignored benign runtime abort:", error);
4139
+ return;
4140
+ }
4141
+ console.error("Unhandled runtime error:", error);
4142
+ lastExitCode = 1;
4143
+ void shutdown("runtime_error");
2298
4144
  };
2299
- process.on("SIGINT", shutdown);
2300
- process.on("SIGTERM", shutdown);
4145
+ process.on("SIGINT", () => {
4146
+ void shutdown("SIGINT");
4147
+ });
4148
+ process.on("SIGTERM", () => {
4149
+ void shutdown("SIGTERM");
4150
+ });
4151
+ process.on("SIGHUP", () => {
4152
+ void shutdown("SIGHUP");
4153
+ });
4154
+ process.on("beforeExit", () => {
4155
+ void shutdown("beforeExit");
4156
+ });
4157
+ process.on("uncaughtException", handleUnexpectedError);
4158
+ process.on("unhandledRejection", handleUnexpectedError);
4159
+ if (process.platform === "win32") {
4160
+ process.on("SIGBREAK", () => {
4161
+ void shutdown("SIGBREAK");
4162
+ });
4163
+ }
4164
+ if (typeof process.stdin.resume === "function") {
4165
+ process.stdin.resume();
4166
+ }
4167
+ process.stdin.on("error", handleStdinError);
4168
+ process.stdin.on("end", onStdinEnd);
4169
+ process.stdin.on("close", onStdinClose);
2301
4170
  await server.connect(transport);
4171
+ server.sendToolListChanged();
4172
+ server.sendResourceListChanged();
2302
4173
  checkWindowsBashAvailability();
2303
- console.error(`claude-code-mcp server started (cwd: ${serverCwd})`);
4174
+ try {
4175
+ if (transport && server) {
4176
+ await server.sendLoggingMessage({
4177
+ level: "info",
4178
+ data: { event: "server_started", cwd: serverCwd }
4179
+ });
4180
+ }
4181
+ } catch {
4182
+ }
4183
+ console.error(`claude-code-mcp server started (transport=stdio, cwd: ${serverCwd})`);
2304
4184
  }
2305
4185
  main().catch((err) => {
2306
4186
  console.error("Fatal error:", err);