@leo000001/claude-code-mcp 2.2.0 → 2.4.3

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,17 +7,20 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
8
  import { z } from "zod";
9
9
 
10
- // src/utils/permission-updated-input.ts
11
- function normalizePermissionUpdatedInput(input) {
12
- if (input && typeof input === "object" && !Array.isArray(input)) {
13
- return input;
14
- }
15
- return { input };
16
- }
17
-
18
10
  // src/utils/normalize-windows-path.ts
19
11
  import path from "path";
20
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
+ ];
21
24
  function maybeAddWindowsLongPathPrefix(value) {
22
25
  if (value.startsWith("\\\\?\\") || value.startsWith("\\\\.\\") || value.length < 240)
23
26
  return value;
@@ -32,7 +35,7 @@ function expandTilde(value, platform) {
32
35
  const join2 = platform === "win32" ? path.win32.join : path.posix.join;
33
36
  return join2(os.homedir(), rest);
34
37
  }
35
- function normalizeMsysToWindowsPathInner(rawPath) {
38
+ function convertMsysToWindowsPath(rawPath) {
36
39
  if (/^[a-zA-Z]:[\\/]/.test(rawPath) || rawPath.startsWith("\\\\")) return void 0;
37
40
  if (rawPath.startsWith("//")) {
38
41
  const m2 = rawPath.match(/^\/\/([^/]{2,})\/(.*)$/);
@@ -55,10 +58,32 @@ function normalizeMsysToWindowsPathInner(rawPath) {
55
58
  return `${drive}:\\${rest.replace(/\//g, "\\")}`;
56
59
  }
57
60
  function normalizeMsysToWindowsPath(rawPath) {
58
- const converted = normalizeMsysToWindowsPathInner(rawPath);
61
+ const converted = convertMsysToWindowsPath(rawPath);
59
62
  if (!converted) return void 0;
60
63
  return path.win32.normalize(converted);
61
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
+ }
62
87
  function normalizeWindowsPathLike(value, platform = process.platform) {
63
88
  const expanded = expandTilde(value, platform);
64
89
  if (platform !== "win32") return expanded;
@@ -88,6 +113,20 @@ var DEFAULT_EVENT_BUFFER_MAX_SIZE = 1e3;
88
113
  var DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE = 2e3;
89
114
  var DEFAULT_MAX_SESSIONS = 128;
90
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
+ }
91
130
  var SessionManager = class _SessionManager {
92
131
  sessions = /* @__PURE__ */ new Map();
93
132
  runtime = /* @__PURE__ */ new Map();
@@ -98,6 +137,9 @@ var SessionManager = class _SessionManager {
98
137
  platform;
99
138
  maxSessions;
100
139
  maxPendingPermissionsPerSession;
140
+ eventBufferMaxSize;
141
+ eventBufferHardMaxSize;
142
+ destroyed = false;
101
143
  constructor(opts) {
102
144
  this.platform = opts?.platform ?? process.platform;
103
145
  const envRaw = process.env.CLAUDE_CODE_MCP_MAX_SESSIONS;
@@ -108,12 +150,23 @@ var SessionManager = class _SessionManager {
108
150
  const maxPendingEnvParsed = typeof maxPendingEnvRaw === "string" && maxPendingEnvRaw.trim() !== "" ? Number.parseInt(maxPendingEnvRaw, 10) : NaN;
109
151
  const maxPendingConfigured = opts?.maxPendingPermissionsPerSession ?? (Number.isFinite(maxPendingEnvParsed) ? maxPendingEnvParsed : void 0);
110
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
+ }
111
163
  this.cleanupTimer = setInterval(() => this.cleanup(), DEFAULT_CLEANUP_INTERVAL_MS);
112
164
  if (this.cleanupTimer.unref) {
113
165
  this.cleanupTimer.unref();
114
166
  }
115
167
  }
116
168
  getSessionCount() {
169
+ if (this.destroyed) return 0;
117
170
  return this.sessions.size;
118
171
  }
119
172
  getMaxSessions() {
@@ -122,11 +175,27 @@ var SessionManager = class _SessionManager {
122
175
  getMaxPendingPermissionsPerSession() {
123
176
  return this.maxPendingPermissionsPerSession;
124
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
+ }
125
190
  hasCapacityFor(additionalSessions) {
191
+ if (this.destroyed) return false;
126
192
  if (additionalSessions <= 0) return true;
127
193
  return this.sessions.size + additionalSessions <= this.maxSessions;
128
194
  }
129
195
  create(params) {
196
+ if (this.destroyed) {
197
+ throw new Error("SessionManager is destroyed and no longer accepts new sessions.");
198
+ }
130
199
  const now = (/* @__PURE__ */ new Date()).toISOString();
131
200
  const existing = this.sessions.get(params.sessionId);
132
201
  if (existing) {
@@ -144,6 +213,7 @@ var SessionManager = class _SessionManager {
144
213
  permissionMode: params.permissionMode ?? "default",
145
214
  allowedTools: params.allowedTools,
146
215
  disallowedTools: params.disallowedTools,
216
+ strictAllowedTools: params.strictAllowedTools,
147
217
  tools: params.tools,
148
218
  maxTurns: params.maxTurns,
149
219
  systemPrompt: params.systemPrompt,
@@ -167,14 +237,15 @@ var SessionManager = class _SessionManager {
167
237
  debug: params.debug,
168
238
  debugFile: params.debugFile,
169
239
  env: params.env,
170
- abortController: params.abortController
240
+ abortController: params.abortController,
241
+ queryInterrupt: params.queryInterrupt
171
242
  };
172
243
  this.sessions.set(params.sessionId, info);
173
244
  this.runtime.set(params.sessionId, {
174
245
  buffer: {
175
246
  events: [],
176
- maxSize: DEFAULT_EVENT_BUFFER_MAX_SIZE,
177
- hardMaxSize: DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE,
247
+ maxSize: this.eventBufferMaxSize,
248
+ hardMaxSize: this.eventBufferHardMaxSize,
178
249
  nextId: 0
179
250
  },
180
251
  pendingPermissions: /* @__PURE__ */ new Map()
@@ -182,36 +253,43 @@ var SessionManager = class _SessionManager {
182
253
  return info;
183
254
  }
184
255
  get(sessionId) {
256
+ if (this.destroyed) return void 0;
185
257
  return this.sessions.get(sessionId);
186
258
  }
187
259
  updateStatus(sessionId, status) {
188
260
  return this.update(sessionId, { status });
189
261
  }
190
262
  list() {
263
+ if (this.destroyed) return [];
191
264
  return Array.from(this.sessions.values());
192
265
  }
193
266
  update(sessionId, patch) {
267
+ if (this.destroyed) return void 0;
194
268
  const info = this.sessions.get(sessionId);
195
269
  if (!info) return void 0;
196
270
  Object.assign(info, patch, { lastActiveAt: (/* @__PURE__ */ new Date()).toISOString() });
197
271
  return info;
198
272
  }
199
273
  /**
200
- * 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.
201
276
  * Returns the session if successful, undefined if the session doesn't exist
202
277
  * or its current status doesn't match `expectedStatus`.
203
278
  */
204
279
  tryAcquire(sessionId, expectedStatus, abortController) {
280
+ if (this.destroyed) return void 0;
205
281
  if (expectedStatus !== "idle" && expectedStatus !== "error") return void 0;
206
282
  const info = this.sessions.get(sessionId);
207
283
  if (!info || info.status !== expectedStatus) return void 0;
208
284
  info.status = "running";
209
285
  info.abortController = abortController;
286
+ info.queryInterrupt = void 0;
210
287
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
211
288
  this.clearTerminalEvents(sessionId);
212
289
  return info;
213
290
  }
214
291
  cancel(sessionId, opts) {
292
+ if (this.destroyed) return false;
215
293
  const info = this.sessions.get(sessionId);
216
294
  if (!info) return false;
217
295
  if (info.status !== "running" && info.status !== "waiting_permission") return false;
@@ -225,6 +303,12 @@ var SessionManager = class _SessionManager {
225
303
  "cancel"
226
304
  );
227
305
  }
306
+ if (info.queryInterrupt) {
307
+ try {
308
+ info.queryInterrupt();
309
+ } catch {
310
+ }
311
+ }
228
312
  if (info.abortController) {
229
313
  info.abortController.abort();
230
314
  }
@@ -232,10 +316,45 @@ var SessionManager = class _SessionManager {
232
316
  info.cancelledAt = (/* @__PURE__ */ new Date()).toISOString();
233
317
  info.cancelledReason = opts?.reason ?? "Session cancelled";
234
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
+ });
235
353
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
236
354
  return true;
237
355
  }
238
356
  delete(sessionId) {
357
+ if (this.destroyed) return false;
239
358
  this.finishAllPending(
240
359
  sessionId,
241
360
  { behavior: "deny", message: "Session deleted", interrupt: true },
@@ -247,22 +366,27 @@ var SessionManager = class _SessionManager {
247
366
  return this.sessions.delete(sessionId);
248
367
  }
249
368
  setResult(sessionId, result) {
369
+ if (this.destroyed) return;
250
370
  const state = this.runtime.get(sessionId);
251
371
  if (!state) return;
252
372
  state.storedResult = result;
253
373
  }
254
374
  getResult(sessionId) {
375
+ if (this.destroyed) return void 0;
255
376
  return this.runtime.get(sessionId)?.storedResult;
256
377
  }
257
378
  setInitTools(sessionId, tools) {
379
+ if (this.destroyed) return;
258
380
  const state = this.runtime.get(sessionId);
259
381
  if (!state) return;
260
382
  state.initTools = tools;
261
383
  }
262
384
  getInitTools(sessionId) {
385
+ if (this.destroyed) return void 0;
263
386
  return this.runtime.get(sessionId)?.initTools;
264
387
  }
265
388
  pushEvent(sessionId, event) {
389
+ if (this.destroyed) return void 0;
266
390
  const state = this.runtime.get(sessionId);
267
391
  if (!state) return void 0;
268
392
  const full = _SessionManager.pushEvent(
@@ -280,21 +404,25 @@ var SessionManager = class _SessionManager {
280
404
  return full;
281
405
  }
282
406
  getLastEventId(sessionId) {
407
+ if (this.destroyed) return void 0;
283
408
  const state = this.runtime.get(sessionId);
284
409
  if (!state) return void 0;
285
410
  return state.buffer.nextId > 0 ? state.buffer.nextId - 1 : void 0;
286
411
  }
287
412
  readEvents(sessionId, cursor) {
413
+ if (this.destroyed) return { events: [], nextCursor: cursor ?? 0 };
288
414
  const state = this.runtime.get(sessionId);
289
415
  if (!state) return { events: [], nextCursor: cursor ?? 0 };
290
416
  return _SessionManager.readEvents(state.buffer, cursor);
291
417
  }
292
418
  clearTerminalEvents(sessionId) {
419
+ if (this.destroyed) return;
293
420
  const state = this.runtime.get(sessionId);
294
421
  if (!state) return;
295
422
  _SessionManager.clearTerminalEvents(state.buffer);
296
423
  }
297
424
  setPendingPermission(sessionId, req, finish, timeoutMs) {
425
+ if (this.destroyed) return false;
298
426
  const state = this.runtime.get(sessionId);
299
427
  const info = this.sessions.get(sessionId);
300
428
  if (!state || !info) return false;
@@ -376,14 +504,79 @@ var SessionManager = class _SessionManager {
376
504
  return false;
377
505
  }
378
506
  getPendingPermissionCount(sessionId) {
507
+ if (this.destroyed) return 0;
379
508
  return this.runtime.get(sessionId)?.pendingPermissions.size ?? 0;
380
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
+ }
381
549
  listPendingPermissions(sessionId) {
550
+ if (this.destroyed) return [];
382
551
  const state = this.runtime.get(sessionId);
383
552
  if (!state) return [];
384
553
  return Array.from(state.pendingPermissions.values()).map((p) => p.record).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
385
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
+ }
386
578
  finishRequest(sessionId, requestId, result, source) {
579
+ if (this.destroyed) return false;
387
580
  const state = this.runtime.get(sessionId);
388
581
  const info = this.sessions.get(sessionId);
389
582
  if (!state || !info) return false;
@@ -391,11 +584,12 @@ var SessionManager = class _SessionManager {
391
584
  if (!pending) return false;
392
585
  let finalResult = result;
393
586
  if (finalResult.behavior === "allow") {
394
- const disallowed = info.disallowedTools;
395
- 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)) {
396
590
  finalResult = {
397
591
  behavior: "deny",
398
- message: `Tool '${pending.record.toolName}' is disallowed by session policy.`,
592
+ message: `Tool '${pendingToolName}' is disallowed by session policy.`,
399
593
  interrupt: false
400
594
  };
401
595
  }
@@ -403,19 +597,25 @@ var SessionManager = class _SessionManager {
403
597
  if (finalResult.behavior === "allow") {
404
598
  const updatedInput = finalResult.updatedInput;
405
599
  const validRecord = updatedInput !== null && updatedInput !== void 0 && typeof updatedInput === "object" && !Array.isArray(updatedInput);
406
- 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) {
407
610
  finalResult = {
408
- ...finalResult,
409
- 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
410
614
  };
411
615
  } else {
412
616
  finalResult = {
413
617
  ...finalResult,
414
- updatedInput: normalizeToolInput(
415
- pending.record.toolName,
416
- updatedInput,
417
- this.platform
418
- )
618
+ updatedInput: normalizedUpdatedInput
419
619
  };
420
620
  }
421
621
  }
@@ -452,6 +652,7 @@ var SessionManager = class _SessionManager {
452
652
  return true;
453
653
  }
454
654
  finishAllPending(sessionId, result, source, opts) {
655
+ if (this.destroyed) return;
455
656
  const state = this.runtime.get(sessionId);
456
657
  if (!state) return;
457
658
  if (state.pendingPermissions.size === 0) return;
@@ -484,6 +685,7 @@ var SessionManager = class _SessionManager {
484
685
  }
485
686
  /** Remove sessions that have been idle for too long, or stuck running too long */
486
687
  cleanup() {
688
+ if (this.destroyed) return;
487
689
  const now = Date.now();
488
690
  for (const [id, info] of this.sessions) {
489
691
  const lastActive = new Date(info.lastActiveAt).getTime();
@@ -502,7 +704,8 @@ var SessionManager = class _SessionManager {
502
704
  info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
503
705
  info.cancelledReason = info.cancelledReason ?? "Session timed out";
504
706
  info.cancelledSource = info.cancelledSource ?? "cleanup";
505
- info.status = "error";
707
+ info.queryInterrupt = void 0;
708
+ info.status = "cancelled";
506
709
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
507
710
  } else if (info.status === "waiting_permission" && now - lastActive > this.runningSessionMaxMs) {
508
711
  this.finishAllPending(
@@ -515,7 +718,8 @@ var SessionManager = class _SessionManager {
515
718
  info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
516
719
  info.cancelledReason = info.cancelledReason ?? "Session timed out";
517
720
  info.cancelledSource = info.cancelledSource ?? "cleanup";
518
- info.status = "error";
721
+ info.queryInterrupt = void 0;
722
+ info.status = "cancelled";
519
723
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
520
724
  } else if (info.status !== "running" && info.status !== "waiting_permission" && now - lastActive > this.sessionTtlMs) {
521
725
  this.finishAllPending(
@@ -553,6 +757,7 @@ var SessionManager = class _SessionManager {
553
757
  toPublicJSON(info) {
554
758
  const {
555
759
  abortController: _abortController,
760
+ queryInterrupt: _queryInterrupt,
556
761
  cwd: _cwd,
557
762
  systemPrompt: _systemPrompt,
558
763
  agents: _agents,
@@ -568,6 +773,7 @@ var SessionManager = class _SessionManager {
568
773
  return rest;
569
774
  }
570
775
  destroy() {
776
+ if (this.destroyed) return;
571
777
  clearInterval(this.cleanupTimer);
572
778
  for (const info of this.sessions.values()) {
573
779
  this.finishAllPending(
@@ -579,12 +785,17 @@ var SessionManager = class _SessionManager {
579
785
  if ((info.status === "running" || info.status === "waiting_permission") && info.abortController) {
580
786
  info.abortController.abort();
581
787
  }
788
+ info.queryInterrupt = void 0;
582
789
  info.status = "cancelled";
583
790
  info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
584
791
  info.cancelledReason = info.cancelledReason ?? "Server shutting down";
585
792
  info.cancelledSource = info.cancelledSource ?? "destroy";
586
793
  info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
587
794
  }
795
+ this.drainingSessions.clear();
796
+ this.runtime.clear();
797
+ this.sessions.clear();
798
+ this.destroyed = true;
588
799
  }
589
800
  static pushEvent(buffer, event, isActivePermissionRequest) {
590
801
  const pinned = event.pinned ?? (event.type === "permission_request" || event.type === "permission_result" || event.type === "result" || event.type === "error");
@@ -596,38 +807,61 @@ var SessionManager = class _SessionManager {
596
807
  pinned
597
808
  };
598
809
  buffer.events.push(full);
599
- while (buffer.events.length > buffer.maxSize) {
600
- const idx = buffer.events.findIndex((e) => !e.pinned);
601
- if (idx !== -1) {
602
- buffer.events.splice(idx, 1);
603
- 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;
604
828
  }
605
- const pinnedDropIdx = buffer.events.findIndex((e) => {
606
- if (e.type === "permission_result") return true;
607
- if (e.type === "permission_request") {
608
- const requestId = e.data?.requestId;
609
- if (typeof requestId !== "string") return true;
610
- return isActivePermissionRequest ? !isActivePermissionRequest(requestId) : true;
611
- }
612
- return false;
613
- });
614
- if (pinnedDropIdx === -1) break;
615
- buffer.events.splice(pinnedDropIdx, 1);
616
- }
617
- while (buffer.events.length > buffer.hardMaxSize) {
618
- const idx = buffer.events.findIndex((e) => {
619
- if (e.type === "permission_request") {
620
- const requestId = e.data?.requestId;
621
- if (typeof requestId !== "string") return true;
622
- return isActivePermissionRequest ? !isActivePermissionRequest(requestId) : true;
623
- }
624
- if (e.type === "permission_result") return true;
625
- return false;
626
- });
627
- if (idx === -1) break;
628
- 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));
629
854
  }
630
- 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;
631
865
  }
632
866
  static readEvents(buffer, cursor) {
633
867
  let cursorResetTo;
@@ -637,7 +871,8 @@ var SessionManager = class _SessionManager {
637
871
  if (earliest == null && buffer.nextId > cursor) cursorResetTo = buffer.nextId;
638
872
  }
639
873
  const startFrom = cursorResetTo ?? cursor ?? 0;
640
- const filtered = buffer.events.filter((e) => e.id >= startFrom);
874
+ const startIndex = _SessionManager.lowerBoundByEventId(buffer.events, startFrom);
875
+ const filtered = buffer.events.slice(startIndex);
641
876
  const nextCursor = filtered.length > 0 ? filtered[filtered.length - 1].id + 1 : startFrom;
642
877
  return { events: filtered, nextCursor, cursorResetTo };
643
878
  }
@@ -660,13 +895,28 @@ var SessionManager = class _SessionManager {
660
895
  }
661
896
  };
662
897
 
898
+ // src/tools/claude-code.ts
899
+ import { existsSync as existsSync2, statSync } from "fs";
900
+
663
901
  // src/types.ts
664
902
  var EFFORT_LEVELS = ["low", "medium", "high", "max"];
665
903
  var AGENT_MODELS = ["sonnet", "opus", "haiku", "inherit"];
666
- var SESSION_ACTIONS = ["list", "get", "cancel"];
904
+ var SESSION_ACTIONS = ["list", "get", "cancel", "interrupt"];
667
905
  var DEFAULT_SETTING_SOURCES = ["user", "project", "local"];
668
906
  var CHECK_ACTIONS = ["poll", "respond_permission"];
669
- 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 || {});
670
920
 
671
921
  // src/tools/query-consumer.ts
672
922
  import { AbortError, query } from "@anthropic-ai/claude-agent-sdk";
@@ -843,6 +1093,14 @@ function enhanceWindowsError(errorMessage) {
843
1093
  return errorMessage;
844
1094
  }
845
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
+
846
1104
  // src/tools/query-consumer.ts
847
1105
  var MAX_TRANSIENT_RETRIES = 3;
848
1106
  var INITIAL_RETRY_DELAY_MS = 1e3;
@@ -876,6 +1134,10 @@ function describeTool(toolName, toolCache) {
876
1134
  const found = tools?.find((t) => t.name === toolName);
877
1135
  return found?.description;
878
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
+ }
879
1141
  function sdkResultToAgentResult(result) {
880
1142
  const sessionTotalTurns = result.session_total_turns;
881
1143
  const sessionTotalCostUsd = result.session_total_cost_usd;
@@ -1003,13 +1265,41 @@ function consumeQuery(params) {
1003
1265
  );
1004
1266
  const canUseTool = async (toolName, input, options2) => {
1005
1267
  const sessionId = await getSessionId();
1268
+ const normalizedToolName = toolName.trim();
1006
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
+ }
1007
1285
  const sessionInfo = params.sessionManager.get(sessionId);
1008
1286
  if (sessionInfo) {
1009
- if (Array.isArray(sessionInfo.disallowedTools) && sessionInfo.disallowedTools.includes(toolName)) {
1010
- 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
+ };
1011
1301
  }
1012
- if (!options2.blockedPath && Array.isArray(sessionInfo.allowedTools) && sessionInfo.allowedTools.includes(toolName)) {
1302
+ if (!options2.blockedPath && normalizedToolName !== "" && allowedTools.includes(normalizedToolName)) {
1013
1303
  return {
1014
1304
  behavior: "allow",
1015
1305
  updatedInput: normalizePermissionUpdatedInput(normalizedInput)
@@ -1182,13 +1472,15 @@ function consumeQuery(params) {
1182
1472
  status: agentResult.isError ? "error" : "idle",
1183
1473
  totalTurns: sessionTotalTurns,
1184
1474
  totalCostUsd: sessionTotalCostUsd,
1185
- abortController: void 0
1475
+ abortController: void 0,
1476
+ queryInterrupt: void 0
1186
1477
  });
1187
1478
  } else if (current) {
1188
1479
  params.sessionManager.update(sessionId2, {
1189
1480
  totalTurns: sessionTotalTurns,
1190
1481
  totalCostUsd: sessionTotalCostUsd,
1191
- abortController: void 0
1482
+ abortController: void 0,
1483
+ queryInterrupt: void 0
1192
1484
  });
1193
1485
  }
1194
1486
  return;
@@ -1240,7 +1532,8 @@ function consumeQuery(params) {
1240
1532
  });
1241
1533
  params.sessionManager.update(sessionId, {
1242
1534
  status: "error",
1243
- abortController: void 0
1535
+ abortController: void 0,
1536
+ queryInterrupt: void 0
1244
1537
  });
1245
1538
  }
1246
1539
  }
@@ -1323,7 +1616,11 @@ function consumeQuery(params) {
1323
1616
  data: agentResult,
1324
1617
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1325
1618
  });
1326
- 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
+ });
1327
1624
  }
1328
1625
  return;
1329
1626
  } finally {
@@ -1335,7 +1632,7 @@ function consumeQuery(params) {
1335
1632
  }
1336
1633
 
1337
1634
  // src/utils/resume-token.ts
1338
- import { createHmac } from "crypto";
1635
+ import { createHmac, timingSafeEqual } from "crypto";
1339
1636
  function getResumeSecrets() {
1340
1637
  const raw = process.env.CLAUDE_CODE_MCP_RESUME_SECRET;
1341
1638
  if (typeof raw !== "string") return [];
@@ -1347,9 +1644,18 @@ function getResumeSecret() {
1347
1644
  function computeResumeToken(sessionId, secret) {
1348
1645
  return createHmac("sha256", secret).update(sessionId).digest("base64url");
1349
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
+ }
1350
1655
  function isValidResumeToken(sessionId, token, secrets) {
1351
1656
  for (const secret of secrets) {
1352
- if (computeResumeToken(sessionId, secret) === token) return true;
1657
+ const computed = computeResumeToken(sessionId, secret);
1658
+ if (timingSafeEqualText(computed, token)) return true;
1353
1659
  }
1354
1660
  return false;
1355
1661
  }
@@ -1425,6 +1731,7 @@ function toSessionCreateParams(input) {
1425
1731
  permissionMode: input.permissionMode,
1426
1732
  allowedTools: src.allowedTools,
1427
1733
  disallowedTools: src.disallowedTools,
1734
+ strictAllowedTools: src.strictAllowedTools,
1428
1735
  tools: src.tools,
1429
1736
  maxTurns: src.maxTurns,
1430
1737
  systemPrompt: src.systemPrompt,
@@ -1448,13 +1755,15 @@ function toSessionCreateParams(input) {
1448
1755
  debug: src.debug,
1449
1756
  debugFile: src.debugFile,
1450
1757
  env: src.env,
1451
- abortController: input.abortController
1758
+ abortController: input.abortController,
1759
+ queryInterrupt: input.queryInterrupt
1452
1760
  };
1453
1761
  }
1454
1762
 
1455
1763
  // src/tools/claude-code.ts
1456
1764
  async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, requestSignal) {
1457
- const cwd = input.cwd !== void 0 ? input.cwd : serverCwd;
1765
+ const cwdProvided = input.cwd !== void 0;
1766
+ const cwd = cwdProvided ? input.cwd : serverCwd;
1458
1767
  if (typeof cwd !== "string" || cwd.trim() === "") {
1459
1768
  return {
1460
1769
  sessionId: "",
@@ -1462,6 +1771,32 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1462
1771
  error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a non-empty string.`
1463
1772
  };
1464
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
+ }
1465
1800
  if (!sessionManager.hasCapacityFor(1)) {
1466
1801
  return {
1467
1802
  sessionId: "",
@@ -1472,28 +1807,18 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1472
1807
  const abortController = new AbortController();
1473
1808
  const adv = input.advanced ?? {};
1474
1809
  const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
1475
- const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ?? input.sessionInitTimeoutMs ?? 1e4;
1476
- const compatWarnings = [];
1477
- if (input.sessionInitTimeoutMs !== void 0) {
1478
- compatWarnings.push(
1479
- "Top-level sessionInitTimeoutMs for claude_code is a compatibility alias; prefer advanced.sessionInitTimeoutMs."
1480
- );
1481
- }
1482
- if (input.sessionInitTimeoutMs !== void 0 && adv.sessionInitTimeoutMs !== void 0 && input.sessionInitTimeoutMs !== adv.sessionInitTimeoutMs) {
1483
- compatWarnings.push(
1484
- `Both advanced.sessionInitTimeoutMs (${adv.sessionInitTimeoutMs}) and top-level sessionInitTimeoutMs (${input.sessionInitTimeoutMs}) were provided; using advanced.sessionInitTimeoutMs.`
1485
- );
1486
- }
1810
+ const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ?? 1e4;
1487
1811
  const flat = {
1488
- cwd,
1812
+ cwd: normalizedCwd,
1489
1813
  allowedTools: input.allowedTools,
1490
1814
  disallowedTools: input.disallowedTools,
1815
+ strictAllowedTools: input.strictAllowedTools ?? adv.strictAllowedTools,
1491
1816
  maxTurns: input.maxTurns,
1492
1817
  model: input.model,
1493
1818
  systemPrompt: input.systemPrompt,
1494
1819
  ...adv,
1495
- effort: input.effort ?? adv.effort,
1496
- thinking: input.thinking ?? adv.thinking
1820
+ effort: input.effort,
1821
+ thinking: input.thinking
1497
1822
  };
1498
1823
  const normalizedFlat = {
1499
1824
  ...flat,
@@ -1519,7 +1844,10 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1519
1844
  sessionId: init.session_id,
1520
1845
  source: normalizedFlat,
1521
1846
  permissionMode: "default",
1522
- abortController
1847
+ abortController,
1848
+ queryInterrupt: () => {
1849
+ handle.interrupt();
1850
+ }
1523
1851
  })
1524
1852
  );
1525
1853
  }
@@ -1534,8 +1862,7 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1534
1862
  sessionId,
1535
1863
  status: "running",
1536
1864
  pollInterval: 3e3,
1537
- resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0,
1538
- compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0
1865
+ resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0
1539
1866
  };
1540
1867
  } catch (err) {
1541
1868
  const message = err instanceof Error ? err.message : String(err);
@@ -1548,6 +1875,42 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1548
1875
  }
1549
1876
 
1550
1877
  // src/tools/claude-code-reply.ts
1878
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
1879
+ import os2 from "os";
1880
+ import path3 from "path";
1881
+ function normalizeAndAssertCwd(cwd, contextLabel) {
1882
+ const normalizedCwd = normalizeWindowsPathLike(cwd);
1883
+ const resolvedCwd = resolvePortableTmpAlias(normalizedCwd);
1884
+ if (!existsSync3(resolvedCwd)) {
1885
+ throw new Error(
1886
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} path does not exist: ${resolvedCwd}`
1887
+ );
1888
+ }
1889
+ try {
1890
+ const stat = statSync2(resolvedCwd);
1891
+ if (!stat.isDirectory()) {
1892
+ throw new Error(
1893
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} must be a directory: ${resolvedCwd}`
1894
+ );
1895
+ }
1896
+ } catch (err) {
1897
+ if (err instanceof Error && err.message.includes("Error [")) throw err;
1898
+ const detail = err instanceof Error ? ` (${err.message})` : "";
1899
+ throw new Error(
1900
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} is not accessible: ${resolvedCwd}${detail}`
1901
+ );
1902
+ }
1903
+ return resolvedCwd;
1904
+ }
1905
+ function resolvePortableTmpAlias(cwd) {
1906
+ if (process.platform !== "win32") return cwd;
1907
+ const normalized = cwd.replace(/\\/g, "/");
1908
+ if (normalized === "/tmp") return os2.tmpdir();
1909
+ if (normalized.startsWith("/tmp/")) {
1910
+ return path3.join(os2.tmpdir(), normalized.slice("/tmp/".length));
1911
+ }
1912
+ return cwd;
1913
+ }
1551
1914
  function toStartError(sessionId, err) {
1552
1915
  const message = err instanceof Error ? err.message : String(err);
1553
1916
  const errorText = message.includes("Error [") ? message : `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`;
@@ -1567,7 +1930,11 @@ function buildOptionsFromDiskResume(dr) {
1567
1930
  if (dr.cwd === void 0 || typeof dr.cwd !== "string" || dr.cwd.trim() === "") {
1568
1931
  throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be provided for disk resume.`);
1569
1932
  }
1570
- return buildOptions(dr);
1933
+ const normalizedCwd = normalizeAndAssertCwd(dr.cwd, "disk resume cwd");
1934
+ return buildOptions({
1935
+ ...dr,
1936
+ cwd: normalizedCwd
1937
+ });
1571
1938
  }
1572
1939
  async function executeClaudeCodeReply(input, sessionManager, toolCache, requestSignal) {
1573
1940
  const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
@@ -1638,7 +2005,7 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1638
2005
  })
1639
2006
  );
1640
2007
  try {
1641
- consumeQuery({
2008
+ const handle = consumeQuery({
1642
2009
  mode: "disk-resume",
1643
2010
  sessionId: input.sessionId,
1644
2011
  prompt: input.prompt,
@@ -1649,6 +2016,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1649
2016
  sessionManager,
1650
2017
  toolCache
1651
2018
  });
2019
+ sessionManager.update(input.sessionId, {
2020
+ queryInterrupt: () => {
2021
+ handle.interrupt();
2022
+ }
2023
+ });
1652
2024
  } catch (err) {
1653
2025
  const { agentResult, errorText } = toStartError(input.sessionId, err);
1654
2026
  sessionManager.setResult(input.sessionId, {
@@ -1661,7 +2033,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1661
2033
  data: agentResult,
1662
2034
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1663
2035
  });
1664
- sessionManager.update(input.sessionId, { status: "error", abortController: void 0 });
2036
+ sessionManager.update(input.sessionId, {
2037
+ status: "error",
2038
+ abortController: void 0,
2039
+ queryInterrupt: void 0
2040
+ });
1665
2041
  return { sessionId: input.sessionId, status: "error", error: errorText };
1666
2042
  }
1667
2043
  return {
@@ -1683,7 +2059,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1683
2059
  data: agentResult,
1684
2060
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1685
2061
  });
1686
- sessionManager.update(input.sessionId, { status: "error", abortController: void 0 });
2062
+ sessionManager.update(input.sessionId, {
2063
+ status: "error",
2064
+ abortController: void 0,
2065
+ queryInterrupt: void 0
2066
+ });
1687
2067
  }
1688
2068
  return {
1689
2069
  sessionId: input.sessionId,
@@ -1718,7 +2098,9 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1718
2098
  };
1719
2099
  }
1720
2100
  const session = acquired;
2101
+ const normalizedCwd = normalizeAndAssertCwd(session.cwd, "session cwd");
1721
2102
  const options = buildOptions(session);
2103
+ options.cwd = normalizedCwd;
1722
2104
  if (input.forkSession) options.forkSession = true;
1723
2105
  if (input.forkSession && !sessionManager.hasCapacityFor(1)) {
1724
2106
  sessionManager.update(input.sessionId, { status: originalStatus, abortController: void 0 });
@@ -1757,7 +2139,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1757
2139
  if (init.session_id === input.sessionId) return;
1758
2140
  sessionManager.update(input.sessionId, {
1759
2141
  status: originalStatus,
1760
- abortController: void 0
2142
+ abortController: void 0,
2143
+ queryInterrupt: void 0
1761
2144
  });
1762
2145
  if (!sessionManager.get(init.session_id)) {
1763
2146
  sessionManager.create(
@@ -1765,12 +2148,22 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1765
2148
  sessionId: init.session_id,
1766
2149
  source: { ...session, ...sourceOverrides },
1767
2150
  permissionMode: "default",
1768
- abortController
2151
+ abortController,
2152
+ queryInterrupt: () => {
2153
+ handle.interrupt();
2154
+ }
1769
2155
  })
1770
2156
  );
1771
2157
  }
1772
2158
  }
1773
2159
  });
2160
+ if (!input.forkSession) {
2161
+ sessionManager.update(input.sessionId, {
2162
+ queryInterrupt: () => {
2163
+ handle.interrupt();
2164
+ }
2165
+ });
2166
+ }
1774
2167
  const sessionId = input.forkSession ? await raceWithAbort(
1775
2168
  handle.sdkSessionIdPromise,
1776
2169
  requestSignal,
@@ -1795,7 +2188,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1795
2188
  if (input.forkSession) {
1796
2189
  sessionManager.update(input.sessionId, {
1797
2190
  status: originalStatus,
1798
- abortController: void 0
2191
+ abortController: void 0,
2192
+ queryInterrupt: void 0
1799
2193
  });
1800
2194
  } else {
1801
2195
  sessionManager.setResult(input.sessionId, {
@@ -1808,7 +2202,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1808
2202
  data: agentResult,
1809
2203
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1810
2204
  });
1811
- sessionManager.update(input.sessionId, { status: "error", abortController: void 0 });
2205
+ sessionManager.update(input.sessionId, {
2206
+ status: "error",
2207
+ abortController: void 0,
2208
+ queryInterrupt: void 0
2209
+ });
1812
2210
  }
1813
2211
  return {
1814
2212
  sessionId: input.sessionId,
@@ -1819,14 +2217,18 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1819
2217
  }
1820
2218
 
1821
2219
  // src/tools/tool-discovery.ts
2220
+ var DEFAULT_PERMISSION_MODEL = "policy_controlled";
2221
+ var DEFAULT_SCHEMA_AVAILABILITY = "none";
1822
2222
  var TOOL_CATALOG = {
1823
2223
  Bash: {
1824
2224
  description: "Run shell commands",
1825
- category: "execute"
2225
+ category: "execute",
2226
+ notes: ["Execution may require permission approval depending on session policy."]
1826
2227
  },
1827
2228
  Read: {
1828
2229
  description: "Read file contents (large files: use offset/limit or Grep)",
1829
- category: "file_read"
2230
+ category: "file_read",
2231
+ notes: ["Large reads are often capped by the backend; use offset/limit when needed."]
1830
2232
  },
1831
2233
  Write: {
1832
2234
  description: "Create or overwrite files",
@@ -1855,7 +2257,8 @@ var TOOL_CATALOG = {
1855
2257
  WebSearch: { description: "Web search", category: "network" },
1856
2258
  Task: {
1857
2259
  description: "Spawn subagent (must be in allowedTools)",
1858
- category: "agent"
2260
+ category: "agent",
2261
+ availabilityConditions: ["Requires Task to be visible and approved by session policy."]
1859
2262
  },
1860
2263
  TaskOutput: { description: "Get subagent output", category: "agent" },
1861
2264
  TaskStop: { description: "Cancel subagent", category: "agent" },
@@ -1869,22 +2272,35 @@ var TOOL_CATALOG = {
1869
2272
  },
1870
2273
  TeamDelete: {
1871
2274
  description: "Delete team (may need shutdown_approved first)",
1872
- category: "agent"
2275
+ category: "agent",
2276
+ notes: ["Team cleanup can be asynchronous during shutdown."]
1873
2277
  }
1874
2278
  };
1875
2279
  function uniq(items) {
1876
2280
  return Array.from(new Set(items));
1877
2281
  }
2282
+ function withToolDefaults(tool) {
2283
+ return {
2284
+ ...tool,
2285
+ permissionModel: tool.permissionModel ?? DEFAULT_PERMISSION_MODEL,
2286
+ schemaAvailability: tool.schemaAvailability ?? DEFAULT_SCHEMA_AVAILABILITY
2287
+ };
2288
+ }
1878
2289
  function discoverToolsFromInit(initTools) {
1879
2290
  const names = uniq(initTools.filter((t) => typeof t === "string" && t.trim() !== ""));
1880
- return names.map((name) => ({
1881
- name,
1882
- description: TOOL_CATALOG[name]?.description ?? name,
1883
- category: TOOL_CATALOG[name]?.category
1884
- }));
2291
+ return names.map(
2292
+ (name) => withToolDefaults({
2293
+ name,
2294
+ description: TOOL_CATALOG[name]?.description ?? name,
2295
+ category: TOOL_CATALOG[name]?.category,
2296
+ availabilityConditions: TOOL_CATALOG[name]?.availabilityConditions,
2297
+ platformConstraints: TOOL_CATALOG[name]?.platformConstraints,
2298
+ notes: TOOL_CATALOG[name]?.notes
2299
+ })
2300
+ );
1885
2301
  }
1886
2302
  function defaultCatalogTools() {
1887
- return Object.keys(TOOL_CATALOG).sort((a, b) => a.localeCompare(b)).map((name) => ({ name, ...TOOL_CATALOG[name] }));
2303
+ return Object.keys(TOOL_CATALOG).sort((a, b) => a.localeCompare(b)).map((name) => withToolDefaults({ name, ...TOOL_CATALOG[name] }));
1888
2304
  }
1889
2305
  var ToolDiscoveryCache = class {
1890
2306
  cached;
@@ -1953,7 +2369,7 @@ function pollIntervalForStatus(status) {
1953
2369
  return void 0;
1954
2370
  }
1955
2371
  function toPermissionResult(params) {
1956
- if (params.decision === "allow") {
2372
+ if (params.decision === "allow" || params.decision === "allow_for_session") {
1957
2373
  return {
1958
2374
  behavior: "allow",
1959
2375
  updatedInput: params.updatedInput,
@@ -1966,6 +2382,19 @@ function toPermissionResult(params) {
1966
2382
  interrupt: params.interrupt
1967
2383
  };
1968
2384
  }
2385
+ function appendAllowForSessionUpdate(updates, toolName) {
2386
+ const normalizedToolName = toolName?.trim();
2387
+ if (!normalizedToolName) return updates;
2388
+ return [
2389
+ ...updates ?? [],
2390
+ {
2391
+ type: "addRules",
2392
+ behavior: "allow",
2393
+ destination: "session",
2394
+ rules: [{ toolName: normalizedToolName }]
2395
+ }
2396
+ ];
2397
+ }
1969
2398
  function slimAssistantData(data) {
1970
2399
  if (!data || typeof data !== "object") return data;
1971
2400
  const d = data;
@@ -2026,10 +2455,23 @@ function detectPathCompatibilityWarnings(session) {
2026
2455
  `cwd '${cwd}' looks like a Windows UNC path on ${process.platform}. Confirm MCP client/server run on the same platform.`
2027
2456
  );
2028
2457
  }
2458
+ if (process.platform === "win32" && Array.isArray(session.additionalDirectories)) {
2459
+ for (const dir of session.additionalDirectories) {
2460
+ if (typeof dir === "string" && dir.startsWith("/") && !dir.startsWith("//")) {
2461
+ warnings.push(
2462
+ `additionalDirectories contains POSIX-style path '${dir}' on Windows. Consider using a Windows path to avoid path compatibility issues.`
2463
+ );
2464
+ }
2465
+ }
2466
+ }
2029
2467
  return warnings;
2030
2468
  }
2031
2469
  function isPosixHomePath(value) {
2032
- return /^\/home\/[^/]+(?:\/|$)/.test(value);
2470
+ return /^\/home\/[^/\s]+(?:\/|$)/.test(value);
2471
+ }
2472
+ function extractPosixHomePath(value) {
2473
+ const match = value.match(/\/home\/[^/\s"'`]+(?:\/[^\s"'`]*)?/);
2474
+ return match?.[0];
2033
2475
  }
2034
2476
  function detectPendingPermissionPathWarnings(pending, session) {
2035
2477
  if (process.platform !== "win32" || pending.length === 0) return [];
@@ -2041,6 +2483,25 @@ function detectPendingPermissionPathWarnings(pending, session) {
2041
2483
  if (typeof req.blockedPath === "string") candidates.push(req.blockedPath);
2042
2484
  const filePath = req.input.file_path;
2043
2485
  if (typeof filePath === "string") candidates.push(filePath);
2486
+ const pathFields = [
2487
+ "path",
2488
+ "directory",
2489
+ "folder",
2490
+ "cwd",
2491
+ "dest",
2492
+ "destination",
2493
+ "source",
2494
+ "target"
2495
+ ];
2496
+ for (const field of pathFields) {
2497
+ const val = req.input[field];
2498
+ if (typeof val === "string") candidates.push(val);
2499
+ }
2500
+ const command = req.input.command;
2501
+ if (typeof command === "string") {
2502
+ const embeddedPath = extractPosixHomePath(command);
2503
+ if (embeddedPath) candidates.push(embeddedPath);
2504
+ }
2044
2505
  const badPath = candidates.find((p) => isPosixHomePath(p));
2045
2506
  if (!badPath) continue;
2046
2507
  warnings.push(
@@ -2053,8 +2514,14 @@ function computeToolValidation(session, initTools) {
2053
2514
  if (!session) return { summary: void 0, warnings: [] };
2054
2515
  const allowedTools = uniqSorted(session.allowedTools);
2055
2516
  const disallowedTools = uniqSorted(session.disallowedTools);
2517
+ const warnings = [];
2518
+ if (allowedTools.length > 0 && session.strictAllowedTools !== true) {
2519
+ warnings.push(
2520
+ "allowedTools currently acts as pre-approval only. Set strictAllowedTools=true to enforce a strict allowlist."
2521
+ );
2522
+ }
2056
2523
  if (allowedTools.length === 0 && disallowedTools.length === 0) {
2057
- return { summary: void 0, warnings: [] };
2524
+ return { summary: void 0, warnings };
2058
2525
  }
2059
2526
  if (!Array.isArray(initTools) || initTools.length === 0) {
2060
2527
  return {
@@ -2064,6 +2531,7 @@ function computeToolValidation(session, initTools) {
2064
2531
  unknownDisallowedTools: []
2065
2532
  },
2066
2533
  warnings: [
2534
+ ...warnings,
2067
2535
  "Runtime tool list is not available yet; unknown allowedTools/disallowedTools names cannot be validated until system/init tools arrive."
2068
2536
  ]
2069
2537
  };
@@ -2073,7 +2541,6 @@ function computeToolValidation(session, initTools) {
2073
2541
  );
2074
2542
  const unknownAllowedTools = allowedTools.filter((name) => !runtime.has(name));
2075
2543
  const unknownDisallowedTools = disallowedTools.filter((name) => !runtime.has(name));
2076
- const warnings = [];
2077
2544
  if (unknownAllowedTools.length > 0) {
2078
2545
  warnings.push(
2079
2546
  `Unknown allowedTools (not present in runtime tools): ${unknownAllowedTools.join(", ")}.`
@@ -2114,11 +2581,12 @@ function capEventsByBytes(events, maxBytes) {
2114
2581
  }
2115
2582
  function buildResult(sessionManager, toolCache, input) {
2116
2583
  const responseMode = input.responseMode ?? "minimal";
2584
+ const compactMode = responseMode === "delta_compact";
2117
2585
  const po = input.pollOptions ?? {};
2118
2586
  const includeTools = po.includeTools;
2119
- const includeEvents = po.includeEvents ?? true;
2587
+ const includeEvents = po.includeEvents ?? !compactMode;
2120
2588
  const includeActions = po.includeActions ?? true;
2121
- const includeResult = po.includeResult ?? true;
2589
+ const includeResult = po.includeResult ?? !compactMode;
2122
2590
  const includeUsage = po.includeUsage ?? responseMode === "full";
2123
2591
  const includeModelUsage = po.includeModelUsage ?? responseMode === "full";
2124
2592
  const includeStructuredOutput = po.includeStructuredOutput ?? responseMode === "full";
@@ -2174,7 +2642,7 @@ function buildResult(sessionManager, toolCache, input) {
2174
2642
  includeUsage,
2175
2643
  includeModelUsage,
2176
2644
  includeStructuredOutput,
2177
- slim: responseMode === "minimal"
2645
+ slim: responseMode === "minimal" || responseMode === "delta_compact"
2178
2646
  });
2179
2647
  const cappedEvents = capEventsByBytes(shapedEvents, maxBytes);
2180
2648
  if (cappedEvents.truncated) {
@@ -2219,7 +2687,7 @@ function buildResult(sessionManager, toolCache, input) {
2219
2687
  includeUsage,
2220
2688
  includeModelUsage,
2221
2689
  includeStructuredOutput,
2222
- slim: responseMode === "minimal"
2690
+ slim: responseMode === "minimal" || responseMode === "delta_compact"
2223
2691
  }) : void 0,
2224
2692
  cancelledAt: session?.cancelledAt,
2225
2693
  cancelledReason: session?.cancelledReason,
@@ -2286,20 +2754,25 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache, requestSignal)
2286
2754
  isError: true
2287
2755
  };
2288
2756
  }
2289
- if (input.decision !== "allow" && input.decision !== "deny") {
2757
+ if (input.decision !== "allow" && input.decision !== "deny" && input.decision !== "allow_for_session") {
2290
2758
  return {
2291
2759
  sessionId: input.sessionId,
2292
- error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision must be 'allow' or 'deny'.`,
2760
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision must be 'allow', 'deny', or 'allow_for_session'.`,
2293
2761
  isError: true
2294
2762
  };
2295
2763
  }
2764
+ const pendingRequest = input.decision === "allow_for_session" ? sessionManager.getPendingPermission(input.sessionId, input.requestId) : void 0;
2765
+ const updatedPermissions = input.decision === "allow_for_session" ? appendAllowForSessionUpdate(
2766
+ input.permissionOptions?.updatedPermissions,
2767
+ pendingRequest?.toolName
2768
+ ) : input.permissionOptions?.updatedPermissions;
2296
2769
  const ok = sessionManager.finishRequest(
2297
2770
  input.sessionId,
2298
2771
  input.requestId,
2299
2772
  toPermissionResult({
2300
2773
  decision: input.decision,
2301
2774
  updatedInput: input.permissionOptions?.updatedInput,
2302
- updatedPermissions: input.permissionOptions?.updatedPermissions,
2775
+ updatedPermissions,
2303
2776
  denyMessage: input.denyMessage,
2304
2777
  interrupt: input.interrupt
2305
2778
  }),
@@ -2312,10 +2785,38 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache, requestSignal)
2312
2785
  isError: true
2313
2786
  };
2314
2787
  }
2788
+ if (input.decision === "allow_for_session" && pendingRequest?.toolName) {
2789
+ sessionManager.allowToolForSession(input.sessionId, pendingRequest.toolName);
2790
+ }
2315
2791
  return buildResult(sessionManager, toolCache, input);
2316
2792
  }
2317
2793
 
2318
2794
  // src/tools/claude-code-session.ts
2795
+ var ALWAYS_REDACTED_FIELDS = [
2796
+ "env",
2797
+ "mcpServers",
2798
+ "sandbox",
2799
+ "debugFile",
2800
+ "pathToClaudeCodeExecutable"
2801
+ ];
2802
+ var CONDITIONAL_REDACTED_FIELDS = [
2803
+ "cwd",
2804
+ "systemPrompt",
2805
+ "agents",
2806
+ "additionalDirectories"
2807
+ ];
2808
+ function buildRedactions(includeSensitive) {
2809
+ const redactions = [];
2810
+ for (const field of ALWAYS_REDACTED_FIELDS) {
2811
+ redactions?.push({ field, reason: "secret_or_internal" });
2812
+ }
2813
+ if (!includeSensitive) {
2814
+ for (const field of CONDITIONAL_REDACTED_FIELDS) {
2815
+ redactions?.push({ field, reason: "sensitive_by_default" });
2816
+ }
2817
+ }
2818
+ return redactions;
2819
+ }
2319
2820
  function executeClaudeCodeSession(input, sessionManager, requestSignal) {
2320
2821
  if (requestSignal?.aborted) {
2321
2822
  return {
@@ -2324,7 +2825,23 @@ function executeClaudeCodeSession(input, sessionManager, requestSignal) {
2324
2825
  isError: true
2325
2826
  };
2326
2827
  }
2327
- const toSessionJson = (s) => input.includeSensitive ? sessionManager.toSensitiveJSON(s) : sessionManager.toPublicJSON(s);
2828
+ const toSessionJson = (s) => {
2829
+ const base = input.includeSensitive ? sessionManager.toSensitiveJSON(s) : sessionManager.toPublicJSON(s);
2830
+ const stored = sessionManager.getResult(s.sessionId);
2831
+ const lastError = stored?.type === "error" ? stored.result.result : void 0;
2832
+ const lastErrorAt = stored?.type === "error" ? stored.createdAt : void 0;
2833
+ return {
2834
+ ...base,
2835
+ pendingPermissionCount: sessionManager.getPendingPermissionCount(s.sessionId),
2836
+ eventCount: sessionManager.getEventCount(s.sessionId),
2837
+ currentCursor: sessionManager.getCurrentCursor(s.sessionId),
2838
+ lastEventId: sessionManager.getLastEventId(s.sessionId),
2839
+ ttlMs: sessionManager.getRemainingTtlMs(s.sessionId),
2840
+ lastError,
2841
+ lastErrorAt,
2842
+ redactions: buildRedactions(input.includeSensitive)
2843
+ };
2844
+ };
2328
2845
  switch (input.action) {
2329
2846
  case "list": {
2330
2847
  const sessions = sessionManager.list().map((s) => toSessionJson(s));
@@ -2381,23 +2898,65 @@ function executeClaudeCodeSession(input, sessionManager, requestSignal) {
2381
2898
  message: `Session '${input.sessionId}' cancelled.`
2382
2899
  };
2383
2900
  }
2901
+ case "interrupt": {
2902
+ if (!input.sessionId) {
2903
+ return {
2904
+ sessions: [],
2905
+ message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: sessionId is required for 'interrupt' action.`,
2906
+ isError: true
2907
+ };
2908
+ }
2909
+ const interrupted = sessionManager.interrupt(input.sessionId, {
2910
+ reason: "Interrupted by caller",
2911
+ source: "claude_code_session"
2912
+ });
2913
+ if (!interrupted) {
2914
+ const session = sessionManager.get(input.sessionId);
2915
+ if (!session) {
2916
+ return {
2917
+ sessions: [],
2918
+ message: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found.`,
2919
+ isError: true
2920
+ };
2921
+ }
2922
+ return {
2923
+ sessions: [toSessionJson(session)],
2924
+ message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Session '${input.sessionId}' is not running (status: ${session.status}).`,
2925
+ isError: true
2926
+ };
2927
+ }
2928
+ const updated = sessionManager.get(input.sessionId);
2929
+ return {
2930
+ sessions: updated ? [toSessionJson(updated)] : [],
2931
+ message: `Session '${input.sessionId}' interrupted.`
2932
+ };
2933
+ }
2384
2934
  default:
2385
2935
  return {
2386
2936
  sessions: [],
2387
- message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown action '${input.action}'. Use 'list', 'get', or 'cancel'.`,
2937
+ message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown action '${input.action}'. Use 'list', 'get', 'cancel', or 'interrupt'.`,
2388
2938
  isError: true
2389
2939
  };
2390
2940
  }
2391
2941
  }
2392
2942
 
2393
2943
  // src/resources/register-resources.ts
2944
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2945
+ import { createHash } from "crypto";
2394
2946
  var RESOURCE_SCHEME = "claude-code-mcp";
2395
2947
  var RESOURCE_URIS = {
2396
2948
  serverInfo: `${RESOURCE_SCHEME}:///server-info`,
2397
2949
  internalTools: `${RESOURCE_SCHEME}:///internal-tools`,
2398
2950
  gotchas: `${RESOURCE_SCHEME}:///gotchas`,
2951
+ quickstart: `${RESOURCE_SCHEME}:///quickstart`,
2952
+ errors: `${RESOURCE_SCHEME}:///errors`,
2399
2953
  compatReport: `${RESOURCE_SCHEME}:///compat-report`
2400
2954
  };
2955
+ var RESOURCE_TEMPLATES = {
2956
+ sessionById: `${RESOURCE_SCHEME}:///session/{sessionId}`,
2957
+ runtimeTools: `${RESOURCE_SCHEME}:///tools/runtime{?sessionId}`,
2958
+ compatDiff: `${RESOURCE_SCHEME}:///compat/diff{?client}`
2959
+ };
2401
2960
  function asTextResource(uri, text, mimeType) {
2402
2961
  return {
2403
2962
  contents: [
@@ -2409,7 +2968,136 @@ function asTextResource(uri, text, mimeType) {
2409
2968
  ]
2410
2969
  };
2411
2970
  }
2971
+ function asJsonResource(uri, value) {
2972
+ return asTextResource(uri, JSON.stringify(value, null, 2), "application/json");
2973
+ }
2974
+ function serializeLimit(limit) {
2975
+ return Number.isFinite(limit) ? limit : "unlimited";
2976
+ }
2977
+ function computeEtag(value) {
2978
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
2979
+ }
2980
+ function extractSingleVariable(value) {
2981
+ if (typeof value === "string" && value.trim() !== "") return value;
2982
+ if (Array.isArray(value) && typeof value[0] === "string" && value[0].trim() !== "")
2983
+ return value[0];
2984
+ return void 0;
2985
+ }
2986
+ function buildSessionRedactions(includeSensitive) {
2987
+ const redactions = [
2988
+ { field: "env", reason: "secret_or_internal" },
2989
+ { field: "mcpServers", reason: "secret_or_internal" },
2990
+ { field: "sandbox", reason: "secret_or_internal" },
2991
+ { field: "debugFile", reason: "secret_or_internal" },
2992
+ { field: "pathToClaudeCodeExecutable", reason: "secret_or_internal" }
2993
+ ];
2994
+ if (!includeSensitive) {
2995
+ redactions.push(
2996
+ { field: "cwd", reason: "sensitive_by_default" },
2997
+ { field: "systemPrompt", reason: "sensitive_by_default" },
2998
+ { field: "agents", reason: "sensitive_by_default" },
2999
+ { field: "additionalDirectories", reason: "sensitive_by_default" }
3000
+ );
3001
+ }
3002
+ return redactions;
3003
+ }
3004
+ function buildGotchasEntries() {
3005
+ return [
3006
+ {
3007
+ id: "permission-timeout",
3008
+ title: "Permission requests auto-deny on timeout",
3009
+ severity: "high",
3010
+ appliesTo: ["claude_code_check.actions", "permission workflow"],
3011
+ symptom: "Session waits for approval, then tool call is denied without explicit caller decision.",
3012
+ detection: "Observe actions[].expiresAt/remainingMs and permission_result with source=timeout.",
3013
+ remedy: "Poll more frequently and respond before timeout; increase permissionRequestTimeoutMs if needed.",
3014
+ example: "Default timeout is 60000ms and server-clamped to 300000ms."
3015
+ },
3016
+ {
3017
+ id: "read-size-cap",
3018
+ title: "Read may cap response size",
3019
+ severity: "medium",
3020
+ appliesTo: ["Read tool"],
3021
+ symptom: "Large file reads are truncated or fail.",
3022
+ detection: "Read responses are incomplete for large files.",
3023
+ remedy: "Use offset/limit paging or chunk with Grep."
3024
+ },
3025
+ {
3026
+ id: "edit-replace-all",
3027
+ title: "Edit replace_all uses substring matching",
3028
+ severity: "medium",
3029
+ appliesTo: ["Edit tool"],
3030
+ symptom: "replace_all=true fails unexpectedly.",
3031
+ detection: "Tool returns error when no exact substring match is found.",
3032
+ remedy: "Validate exact match text first; prefer smaller targeted replacements."
3033
+ },
3034
+ {
3035
+ id: "windows-path-normalization",
3036
+ title: "Windows path normalization applies to common MSYS-style paths",
3037
+ severity: "medium",
3038
+ appliesTo: ["Windows", "cwd", "additionalDirectories", "file_path"],
3039
+ symptom: "Permission/path behavior differs from raw input path text.",
3040
+ detection: "Compare submitted path with effective path in logs/permission prompts.",
3041
+ remedy: "Use absolute native Windows paths when possible; avoid relying on implicit conversion behavior.",
3042
+ example: "/d/... /mnt/c/... /cygdrive/c/... //server/share/... are normalized on Windows."
3043
+ },
3044
+ {
3045
+ id: "team-delete-async-cleanup",
3046
+ title: "TeamDelete cleanup can be asynchronous",
3047
+ severity: "low",
3048
+ appliesTo: ["TeamDelete"],
3049
+ symptom: "Immediate follow-up calls still see active members.",
3050
+ detection: "TeamDelete reports shutdown_approved or active member transitions.",
3051
+ remedy: "Retry after short delay; wait for shutdown state to settle."
3052
+ },
3053
+ {
3054
+ id: "skills-late-availability",
3055
+ title: "Skills may become available later in a session",
3056
+ severity: "low",
3057
+ appliesTo: ["Skills"],
3058
+ symptom: "Early calls show unknown skill/tool errors.",
3059
+ detection: "Later calls in same session succeed without config changes.",
3060
+ remedy: "Retry after initialization events are complete."
3061
+ },
3062
+ {
3063
+ id: "tool-count-sources-differ",
3064
+ title: "toolCatalogCount and availableTools have different sources",
3065
+ severity: "medium",
3066
+ appliesTo: ["internal-tools", "claude_code_check.availableTools", "compat-report"],
3067
+ symptom: "Tool counts appear inconsistent (for example 15 vs 28).",
3068
+ detection: "Compare compat-report.toolCounts and session-level availableTools.",
3069
+ remedy: "Treat catalog count as server-known baseline and availableTools as session runtime view from system/init.tools."
3070
+ },
3071
+ {
3072
+ id: "available-tools-not-exhaustive",
3073
+ title: "availableTools may omit internal features",
3074
+ severity: "low",
3075
+ appliesTo: ["claude_code_check.availableTools"],
3076
+ symptom: "A known feature is callable but not listed in availableTools.",
3077
+ detection: "Feature works while missing from availableTools list.",
3078
+ remedy: "Use availableTools as runtime hint, not exhaustive capability proof.",
3079
+ example: "Some internal features (e.g. ToolSearch) may not appear."
3080
+ }
3081
+ ];
3082
+ }
3083
+ function asVersionedPayload(params) {
3084
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3085
+ return {
3086
+ ...params.payload,
3087
+ schemaVersion: params.schemaVersion,
3088
+ updatedAt,
3089
+ etag: computeEtag(params.payload),
3090
+ stability: params.stability
3091
+ };
3092
+ }
2412
3093
  function registerResources(server, deps) {
3094
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3095
+ const resourceSchemaVersion = "1.3";
3096
+ const mcpProtocolVersion = "2025-03-26";
3097
+ const gotchasEntries = buildGotchasEntries();
3098
+ const catalogToolNames = new Set(defaultCatalogTools().map((tool) => tool.name));
3099
+ const staticResourceUris = Object.values(RESOURCE_URIS);
3100
+ const templateUris = Object.values(RESOURCE_TEMPLATES);
2413
3101
  const serverInfoUri = new URL(RESOURCE_URIS.serverInfo);
2414
3102
  server.registerResource(
2415
3103
  "server_info",
@@ -2419,21 +3107,50 @@ function registerResources(server, deps) {
2419
3107
  description: "Server metadata (version/platform/runtime).",
2420
3108
  mimeType: "application/json"
2421
3109
  },
2422
- () => asTextResource(
3110
+ () => asJsonResource(
2423
3111
  serverInfoUri,
2424
- JSON.stringify(
2425
- {
3112
+ (() => {
3113
+ const base = {
2426
3114
  name: "claude-code-mcp",
3115
+ version: deps.version,
2427
3116
  node: process.version,
2428
3117
  platform: process.platform,
2429
3118
  arch: process.arch,
2430
- resources: Object.values(RESOURCE_URIS),
2431
- toolCatalogCount: deps.toolCache.getTools().length
2432
- },
2433
- null,
2434
- 2
2435
- ),
2436
- "application/json"
3119
+ mcpProtocolVersion,
3120
+ startedAt,
3121
+ uptimeSec: Math.floor(process.uptime()),
3122
+ resources: staticResourceUris,
3123
+ resourceTemplates: templateUris,
3124
+ toolCatalogCount: deps.toolCache.getTools().length,
3125
+ capabilities: {
3126
+ resources: true,
3127
+ toolsListChanged: true,
3128
+ resourcesListChanged: true,
3129
+ prompts: false,
3130
+ completions: false
3131
+ },
3132
+ limits: {
3133
+ maxSessions: serializeLimit(deps.sessionManager.getMaxSessions()),
3134
+ maxPendingPermissionsPerSession: serializeLimit(
3135
+ deps.sessionManager.getMaxPendingPermissionsPerSession()
3136
+ ),
3137
+ eventBuffer: deps.sessionManager.getEventBufferConfig(),
3138
+ pollDefaults: {
3139
+ runningMs: 3e3,
3140
+ waitingPermissionMs: 1e3
3141
+ }
3142
+ }
3143
+ };
3144
+ if (typeof process.env.CLAUDE_CODE_MCP_BUILD_COMMIT === "string") {
3145
+ const commit = process.env.CLAUDE_CODE_MCP_BUILD_COMMIT.trim();
3146
+ if (commit !== "") base.buildCommit = commit;
3147
+ }
3148
+ return asVersionedPayload({
3149
+ schemaVersion: resourceSchemaVersion,
3150
+ stability: "stable",
3151
+ payload: base
3152
+ });
3153
+ })()
2437
3154
  )
2438
3155
  );
2439
3156
  const toolsUri = new URL(RESOURCE_URIS.internalTools);
@@ -2445,10 +3162,19 @@ function registerResources(server, deps) {
2445
3162
  description: "Claude Code internal tool catalog (runtime-aware).",
2446
3163
  mimeType: "application/json"
2447
3164
  },
2448
- () => asTextResource(
3165
+ () => asJsonResource(
2449
3166
  toolsUri,
2450
- JSON.stringify({ tools: deps.toolCache.getTools() }, null, 2),
2451
- "application/json"
3167
+ (() => {
3168
+ const base = {
3169
+ tools: deps.toolCache.getTools(),
3170
+ toolCatalogCount: deps.toolCache.getTools().length
3171
+ };
3172
+ return asVersionedPayload({
3173
+ schemaVersion: resourceSchemaVersion,
3174
+ stability: "stable",
3175
+ payload: base
3176
+ });
3177
+ })()
2452
3178
  )
2453
3179
  );
2454
3180
  const gotchasUri = new URL(RESOURCE_URIS.gotchas);
@@ -2465,19 +3191,75 @@ function registerResources(server, deps) {
2465
3191
  [
2466
3192
  "# claude-code-mcp: gotchas",
2467
3193
  "",
2468
- "- Permission approvals have a timeout (default 60s, server-clamped to 5min) and auto-deny (`actions[].expiresAt`/`remainingMs`).",
2469
- "- `Read` has a per-call size cap in practice (often ~256KB); for large files use `offset`/`limit` or chunk with `Grep`.",
2470
- "- `Edit` with `replace_all=true` is substring replacement; if no match is found the tool returns an error.",
2471
- "- On Windows, this server normalizes common MSYS-style paths (e.g. `/d/...`, `/mnt/c/...`, `/cygdrive/c/...`, `//server/share/...`) for `cwd`, `additionalDirectories`, and tool inputs that include `file_path`.",
2472
- "- `TeamDelete` may require members to reach `shutdown_approved`; cleanup can be asynchronous during shutdown.",
2473
- '- Skills may become available later in the same session (early calls may show "Unknown").',
2474
- "- Some internal features (e.g. ToolSearch) may not appear in `availableTools` because it is derived from SDK `system/init.tools`.",
3194
+ ...gotchasEntries.map((entry) => `- ${entry.title}. ${entry.remedy}`),
2475
3195
  ""
2476
3196
  ].join("\n"),
2477
3197
  "text/markdown"
2478
3198
  )
2479
3199
  );
2480
3200
  const compatReportUri = new URL(RESOURCE_URIS.compatReport);
3201
+ const quickstartUri = new URL(RESOURCE_URIS.quickstart);
3202
+ server.registerResource(
3203
+ "quickstart",
3204
+ quickstartUri.toString(),
3205
+ {
3206
+ title: "Quickstart",
3207
+ description: "Minimal async polling flow for claude_code / claude_code_check.",
3208
+ mimeType: "text/markdown"
3209
+ },
3210
+ () => asTextResource(
3211
+ quickstartUri,
3212
+ [
3213
+ "# claude-code-mcp quickstart",
3214
+ "",
3215
+ "1. Call `claude_code` with `{ prompt }` and keep `sessionId`.",
3216
+ "2. Poll with `claude_code_check(action='poll')` using `nextCursor`.",
3217
+ "3. If actions are returned, respond with `claude_code_check(action='respond_permission')`.",
3218
+ "4. Continue polling until status becomes `idle` / `error` / `cancelled`.",
3219
+ "",
3220
+ "Notes:",
3221
+ "- `respond_user_input` is not supported on this backend.",
3222
+ "- `allowedTools` is pre-approval by default; set `strictAllowedTools=true` for strict allowlist behavior.",
3223
+ "- Prefer `responseMode='delta_compact'` for high-frequency polling."
3224
+ ].join("\n"),
3225
+ "text/markdown"
3226
+ )
3227
+ );
3228
+ const errorsUri = new URL(RESOURCE_URIS.errors);
3229
+ server.registerResource(
3230
+ "errors",
3231
+ errorsUri.toString(),
3232
+ {
3233
+ title: "Errors",
3234
+ description: "Structured error codes and remediation hints.",
3235
+ mimeType: "application/json"
3236
+ },
3237
+ () => {
3238
+ const codes = Object.values(ErrorCode);
3239
+ const hints = {
3240
+ ["INVALID_ARGUMENT" /* INVALID_ARGUMENT */]: "Validate required fields and enum values.",
3241
+ ["SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */]: "Session may be expired or server-restarted.",
3242
+ ["SESSION_BUSY" /* SESSION_BUSY */]: "Wait for running/waiting_permission session to settle.",
3243
+ ["PERMISSION_REQUEST_NOT_FOUND" /* PERMISSION_REQUEST_NOT_FOUND */]: "The permission request was already finished/expired.",
3244
+ ["PERMISSION_DENIED" /* PERMISSION_DENIED */]: "Check token/secrets/policy restrictions.",
3245
+ ["RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */]: "Reduce session count or increase server limits.",
3246
+ ["TIMEOUT" /* TIMEOUT */]: "Increase timeout or poll/respond more frequently.",
3247
+ ["CANCELLED" /* CANCELLED */]: "Request/session was cancelled by caller or shutdown.",
3248
+ ["INTERNAL" /* INTERNAL */]: "Inspect server logs and runtime environment."
3249
+ };
3250
+ return asJsonResource(
3251
+ errorsUri,
3252
+ asVersionedPayload({
3253
+ schemaVersion: resourceSchemaVersion,
3254
+ stability: "stable",
3255
+ payload: {
3256
+ codes,
3257
+ hints
3258
+ }
3259
+ })
3260
+ );
3261
+ }
3262
+ );
2481
3263
  server.registerResource(
2482
3264
  "compat_report",
2483
3265
  compatReportUri.toString(),
@@ -2493,43 +3275,294 @@ function registerResources(server, deps) {
2493
3275
  "CLAUDE_CODE_GIT_BASH_PATH is not set. Auto-detection exists, but explicit path is more reliable for GUI-launched MCP clients."
2494
3276
  );
2495
3277
  }
2496
- return asTextResource(
3278
+ const diskResumeEnabled = process.env.CLAUDE_CODE_MCP_ALLOW_DISK_RESUME === "1";
3279
+ const resumeSecretConfigured = typeof process.env.CLAUDE_CODE_MCP_RESUME_SECRET === "string" && process.env.CLAUDE_CODE_MCP_RESUME_SECRET.trim() !== "";
3280
+ const runtimeToolStats = deps.sessionManager.getRuntimeToolStats();
3281
+ const toolCatalogCount = deps.toolCache.getTools().length;
3282
+ const detectedMismatches = [];
3283
+ if (runtimeToolStats.sessionsWithInitTools > 0 && runtimeToolStats.runtimeDiscoveredUniqueCount < toolCatalogCount) {
3284
+ detectedMismatches.push(
3285
+ "Runtime discovered tools are fewer than catalog tools; some features may be hidden or not surfaced in system/init.tools."
3286
+ );
3287
+ }
3288
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3289
+ const base = {
3290
+ transport: "stdio",
3291
+ samePlatformRequired: true,
3292
+ packageVersion: deps.version,
3293
+ runtime: {
3294
+ node: process.version,
3295
+ platform: process.platform,
3296
+ arch: process.arch
3297
+ },
3298
+ limits: {
3299
+ maxSessions: serializeLimit(deps.sessionManager.getMaxSessions()),
3300
+ maxPendingPermissionsPerSession: serializeLimit(
3301
+ deps.sessionManager.getMaxPendingPermissionsPerSession()
3302
+ ),
3303
+ eventBuffer: deps.sessionManager.getEventBufferConfig()
3304
+ },
3305
+ diskResume: {
3306
+ enabled: diskResumeEnabled,
3307
+ resumeSecretConfigured
3308
+ },
3309
+ features: {
3310
+ resources: true,
3311
+ resourceTemplates: true,
3312
+ toolsListChanged: true,
3313
+ resourcesListChanged: true,
3314
+ sessionInterrupt: true,
3315
+ allowForSessionDecision: true,
3316
+ respondUserInput: false,
3317
+ prompts: false,
3318
+ completions: false
3319
+ },
3320
+ recommendedSettings: {
3321
+ responseMode: "delta_compact",
3322
+ poll: {
3323
+ runningMs: 3e3,
3324
+ waitingPermissionMs: 1e3,
3325
+ cursorStrategy: "Persist nextCursor and de-duplicate by event.id."
3326
+ },
3327
+ timeouts: {
3328
+ sessionInitTimeoutMs: 1e4,
3329
+ permissionRequestTimeoutMs: 6e4,
3330
+ permissionRequestTimeoutMaxMs: 3e5
3331
+ }
3332
+ },
3333
+ guidance: [
3334
+ "Some clients cache tool descriptions at connect time. Prefer claude_code_check(pollOptions.includeTools=true) for runtime-authoritative tool lists.",
3335
+ "Use allowedTools/disallowedTools only with exact runtime tool names.",
3336
+ "Set strictAllowedTools=true when you need allowedTools to behave as a strict allowlist.",
3337
+ "This server assumes MCP client and server run on the same machine/platform.",
3338
+ "For high-frequency status checks, prefer responseMode='delta_compact'.",
3339
+ "respond_user_input is not supported on this backend; use poll/respond_permission flow."
3340
+ ],
3341
+ toolCounts: {
3342
+ catalogCount: toolCatalogCount,
3343
+ sessionsWithInitTools: runtimeToolStats.sessionsWithInitTools,
3344
+ runtimeDiscoveredUniqueCount: runtimeToolStats.runtimeDiscoveredUniqueCount,
3345
+ explain: "catalogCount is server catalog size; runtimeDiscoveredUniqueCount is union of system/init.tools across active sessions."
3346
+ },
3347
+ toolCatalogCount,
3348
+ detectedMismatches,
3349
+ runtimeWarnings,
3350
+ resourceTemplates: templateUris
3351
+ };
3352
+ const healthScore = Math.max(
3353
+ 0,
3354
+ 100 - runtimeWarnings.length * 10 - detectedMismatches.length * 15
3355
+ );
3356
+ return asJsonResource(
2497
3357
  compatReportUri,
2498
- JSON.stringify(
2499
- {
2500
- transport: "stdio",
2501
- samePlatformRequired: true,
2502
- runtime: {
2503
- node: process.version,
2504
- platform: process.platform,
2505
- arch: process.arch
2506
- },
2507
- features: {
2508
- resources: true,
2509
- toolsListChanged: true,
2510
- resourcesListChanged: true,
2511
- prompts: false,
2512
- completions: false
2513
- },
2514
- guidance: [
2515
- "Some clients cache tool descriptions at connect time. Prefer claude_code_check(pollOptions.includeTools=true) for runtime-authoritative tool lists.",
2516
- "Use allowedTools/disallowedTools only with exact runtime tool names.",
2517
- "This server assumes MCP client and server run on the same machine/platform."
3358
+ asVersionedPayload({
3359
+ schemaVersion: resourceSchemaVersion,
3360
+ stability: "stable",
3361
+ payload: {
3362
+ ...base,
3363
+ healthScore,
3364
+ updatedAt
3365
+ }
3366
+ })
3367
+ );
3368
+ }
3369
+ );
3370
+ server.registerResource(
3371
+ "session_snapshot_template",
3372
+ new ResourceTemplate(RESOURCE_TEMPLATES.sessionById, { list: void 0 }),
3373
+ {
3374
+ title: "Session Snapshot Template",
3375
+ description: "Read lightweight session diagnostics by sessionId.",
3376
+ mimeType: "application/json"
3377
+ },
3378
+ (uri, variables) => {
3379
+ const sessionId = extractSingleVariable(variables.sessionId) ?? extractSingleVariable(uri.searchParams.get("sessionId"));
3380
+ const payload = typeof sessionId !== "string" || sessionId.trim() === "" ? {
3381
+ found: false,
3382
+ message: "sessionId is required in URI template variable."
3383
+ } : (() => {
3384
+ const session = deps.sessionManager.get(sessionId);
3385
+ if (!session) {
3386
+ return {
3387
+ sessionId,
3388
+ found: false,
3389
+ message: `Session '${sessionId}' not found.`
3390
+ };
3391
+ }
3392
+ const base = deps.sessionManager.toPublicJSON(session);
3393
+ const stored = deps.sessionManager.getResult(sessionId);
3394
+ return {
3395
+ sessionId,
3396
+ found: true,
3397
+ session: {
3398
+ ...base,
3399
+ pendingPermissionCount: deps.sessionManager.getPendingPermissionCount(sessionId),
3400
+ eventCount: deps.sessionManager.getEventCount(sessionId),
3401
+ currentCursor: deps.sessionManager.getCurrentCursor(sessionId),
3402
+ lastEventId: deps.sessionManager.getLastEventId(sessionId),
3403
+ ttlMs: deps.sessionManager.getRemainingTtlMs(sessionId),
3404
+ lastError: stored?.type === "error" ? stored.result.result : void 0,
3405
+ lastErrorAt: stored?.type === "error" ? stored.createdAt : void 0,
3406
+ redactions: buildSessionRedactions(false)
3407
+ }
3408
+ };
3409
+ })();
3410
+ return asJsonResource(
3411
+ uri,
3412
+ asVersionedPayload({
3413
+ schemaVersion: resourceSchemaVersion,
3414
+ stability: "stable",
3415
+ payload
3416
+ })
3417
+ );
3418
+ }
3419
+ );
3420
+ server.registerResource(
3421
+ "runtime_tools_template",
3422
+ new ResourceTemplate(RESOURCE_TEMPLATES.runtimeTools, { list: void 0 }),
3423
+ {
3424
+ title: "Runtime Tools Template",
3425
+ description: "Read runtime tool view globally or for a specific sessionId.",
3426
+ mimeType: "application/json"
3427
+ },
3428
+ (uri, variables) => {
3429
+ const sessionId = extractSingleVariable(variables.sessionId) ?? extractSingleVariable(uri.searchParams.get("sessionId"));
3430
+ const internalToolCount = catalogToolNames.size;
3431
+ const payload = (() => {
3432
+ if (typeof sessionId === "string" && sessionId.trim() !== "") {
3433
+ const session = deps.sessionManager.get(sessionId);
3434
+ if (!session) {
3435
+ return {
3436
+ sessionId,
3437
+ source: "session_not_found",
3438
+ availableTools: [],
3439
+ toolCounts: {
3440
+ internalToolCount,
3441
+ runtimeAvailableCount: 0,
3442
+ sessionAugmentedToolCount: 0
3443
+ }
3444
+ };
3445
+ }
3446
+ const initTools = deps.sessionManager.getInitTools(sessionId) ?? [];
3447
+ const discovered = discoverToolsFromInit(initTools);
3448
+ const sessionAugmentedToolCount2 = discovered.filter(
3449
+ (tool) => !catalogToolNames.has(tool.name)
3450
+ ).length;
3451
+ return {
3452
+ sessionId,
3453
+ source: initTools.length > 0 ? "session_runtime" : "session_without_init",
3454
+ availableTools: discovered,
3455
+ toolCounts: {
3456
+ internalToolCount,
3457
+ runtimeAvailableCount: discovered.length,
3458
+ sessionAugmentedToolCount: sessionAugmentedToolCount2
3459
+ }
3460
+ };
3461
+ }
3462
+ const catalog = deps.toolCache.getTools();
3463
+ const sessionAugmentedToolCount = catalog.filter(
3464
+ (tool) => !catalogToolNames.has(tool.name)
3465
+ ).length;
3466
+ return {
3467
+ source: "catalog",
3468
+ availableTools: catalog,
3469
+ toolCounts: {
3470
+ internalToolCount,
3471
+ runtimeAvailableCount: catalog.length,
3472
+ sessionAugmentedToolCount
3473
+ }
3474
+ };
3475
+ })();
3476
+ return asJsonResource(
3477
+ uri,
3478
+ asVersionedPayload({
3479
+ schemaVersion: resourceSchemaVersion,
3480
+ stability: "stable",
3481
+ payload
3482
+ })
3483
+ );
3484
+ }
3485
+ );
3486
+ server.registerResource(
3487
+ "compat_diff_template",
3488
+ new ResourceTemplate(RESOURCE_TEMPLATES.compatDiff, { list: void 0 }),
3489
+ {
3490
+ title: "Compatibility Diff Template",
3491
+ description: "Returns client-specific compatibility guidance and recommended settings.",
3492
+ mimeType: "application/json"
3493
+ },
3494
+ (uri, variables) => {
3495
+ const clientRaw = extractSingleVariable(variables.client) ?? extractSingleVariable(uri.searchParams.get("client"));
3496
+ const clientFingerprint = (clientRaw ?? "unknown").trim();
3497
+ const key = clientFingerprint.toLowerCase();
3498
+ const profile = (() => {
3499
+ if (key.includes("codex")) {
3500
+ return {
3501
+ clientFamily: "codex",
3502
+ detectedMismatches: [],
3503
+ recommendations: [
3504
+ "Prefer responseMode='delta_compact' for fast status loops.",
3505
+ "Enable pollOptions.includeTools=true when exact runtime tool names are required."
3506
+ ]
3507
+ };
3508
+ }
3509
+ if (key.includes("claude")) {
3510
+ return {
3511
+ clientFamily: "claude",
3512
+ detectedMismatches: [],
3513
+ recommendations: [
3514
+ "Use resources and resource templates for low-latency diagnostics.",
3515
+ "Use allowedTools/disallowedTools with exact runtime names.",
3516
+ "Enable strictAllowedTools when running in locked-down governance mode."
3517
+ ]
3518
+ };
3519
+ }
3520
+ if (key.includes("cursor")) {
3521
+ return {
3522
+ clientFamily: "cursor",
3523
+ detectedMismatches: [
3524
+ "Verify that resources/list and resourceTemplates/list are refreshed after list_changed notifications."
2518
3525
  ],
2519
- toolCatalogCount: deps.toolCache.getTools().length,
2520
- runtimeWarnings
2521
- },
2522
- null,
2523
- 2
2524
- ),
2525
- "application/json"
3526
+ recommendations: [
3527
+ "Prefer claude_code_check polling as source of truth for runtime state.",
3528
+ "Fallback to tool calls if resource template support is partial."
3529
+ ]
3530
+ };
3531
+ }
3532
+ return {
3533
+ clientFamily: "generic",
3534
+ detectedMismatches: [],
3535
+ recommendations: [
3536
+ "Persist nextCursor and de-duplicate by event.id.",
3537
+ "Use responseMode='delta_compact' for high-frequency polling, full mode only for diagnostics."
3538
+ ]
3539
+ };
3540
+ })();
3541
+ return asJsonResource(
3542
+ uri,
3543
+ asVersionedPayload({
3544
+ schemaVersion: resourceSchemaVersion,
3545
+ stability: "experimental",
3546
+ payload: {
3547
+ clientFingerprint,
3548
+ ...profile,
3549
+ recommendedSettings: {
3550
+ responseMode: "delta_compact",
3551
+ poll: {
3552
+ runningMs: 3e3,
3553
+ waitingPermissionMs: 1e3,
3554
+ cursorStrategy: "Persist nextCursor and de-duplicate by event.id."
3555
+ }
3556
+ }
3557
+ }
3558
+ })
2526
3559
  );
2527
3560
  }
2528
3561
  );
2529
3562
  }
2530
3563
 
2531
3564
  // src/server.ts
2532
- var SERVER_VERSION = true ? "2.2.0" : "0.0.0-dev";
3565
+ var SERVER_VERSION = true ? "2.4.3" : "0.0.0-dev";
2533
3566
  function createServerContext(serverCwd) {
2534
3567
  const sessionManager = new SessionManager();
2535
3568
  const server = new McpServer(
@@ -2550,12 +3583,17 @@ function createServerContext(serverCwd) {
2550
3583
  }
2551
3584
  );
2552
3585
  const claudeCodeToolRef = {};
3586
+ const notifyInternalToolsResourceChanged = () => {
3587
+ if (!server.isConnected()) return;
3588
+ server.sendResourceListChanged();
3589
+ };
2553
3590
  const toolCache = new ToolDiscoveryCache(void 0, (tools) => {
2554
3591
  try {
2555
3592
  claudeCodeToolRef.current?.update({ description: buildInternalToolsDescription(tools) });
2556
3593
  if (server.isConnected()) {
2557
3594
  server.sendToolListChanged();
2558
3595
  }
3596
+ notifyInternalToolsResourceChanged();
2559
3597
  } catch {
2560
3598
  }
2561
3599
  });
@@ -2621,9 +3659,7 @@ function createServerContext(serverCwd) {
2621
3659
  env: z.record(z.string(), z.string().optional()).optional().describe("Default: none")
2622
3660
  };
2623
3661
  const advancedOptionFieldsSchemaShape = {
2624
- ...sharedOptionFieldsSchemaShape,
2625
- effort: effortOptionSchema.describe("Deprecated, use top-level. Default: SDK"),
2626
- thinking: thinkingOptionSchema.describe("Deprecated, use top-level. Default: SDK")
3662
+ ...sharedOptionFieldsSchemaShape
2627
3663
  };
2628
3664
  const diskResumeOptionFieldsSchemaShape = {
2629
3665
  ...sharedOptionFieldsSchemaShape,
@@ -2639,6 +3675,7 @@ function createServerContext(serverCwd) {
2639
3675
  cwd: z.string(),
2640
3676
  allowedTools: z.array(z.string()).optional().describe("Default: []"),
2641
3677
  disallowedTools: z.array(z.string()).optional().describe("Default: []"),
3678
+ strictAllowedTools: z.boolean().optional().describe("Default: false"),
2642
3679
  maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
2643
3680
  model: z.string().optional().describe("Default: SDK"),
2644
3681
  systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
@@ -2706,13 +3743,13 @@ function createServerContext(serverCwd) {
2706
3743
  cwd: z.string().optional().describe("Working dir. Default: server cwd"),
2707
3744
  allowedTools: z.array(z.string()).optional().describe("Default: []"),
2708
3745
  disallowedTools: z.array(z.string()).optional().describe("Default: []"),
3746
+ strictAllowedTools: z.boolean().optional().describe("Default: false"),
2709
3747
  maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
2710
3748
  model: z.string().optional().describe("Default: SDK"),
2711
3749
  effort: effortOptionSchema.describe("Default: SDK"),
2712
3750
  thinking: thinkingOptionSchema.describe("Default: SDK"),
2713
3751
  systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
2714
- permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000"),
2715
- sessionInitTimeoutMs: z.number().int().positive().optional().describe("Deprecated, use advanced.sessionInitTimeoutMs. Default: 10000"),
3752
+ permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000, clamped to 300000"),
2716
3753
  advanced: advancedOptionsSchema
2717
3754
  },
2718
3755
  outputSchema: startResultSchema,
@@ -2774,7 +3811,7 @@ function createServerContext(serverCwd) {
2774
3811
  effort: effortOptionSchema.describe("Default: SDK"),
2775
3812
  thinking: thinkingOptionSchema.describe("Default: SDK"),
2776
3813
  sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000"),
2777
- permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000"),
3814
+ permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000, clamped to 300000"),
2778
3815
  diskResumeConfig: diskResumeConfigSchema
2779
3816
  },
2780
3817
  outputSchema: startResultSchema,
@@ -2822,10 +3859,10 @@ function createServerContext(serverCwd) {
2822
3859
  server.registerTool(
2823
3860
  "claude_code_session",
2824
3861
  {
2825
- description: "List, inspect, or cancel sessions.",
3862
+ description: "List, inspect, cancel, or interrupt sessions.",
2826
3863
  inputSchema: {
2827
3864
  action: z.enum(SESSION_ACTIONS),
2828
- sessionId: z.string().optional().describe("Required for get/cancel"),
3865
+ sessionId: z.string().optional().describe("Required for get/cancel/interrupt"),
2829
3866
  includeSensitive: z.boolean().optional().describe("Default: false")
2830
3867
  },
2831
3868
  outputSchema: sessionResultSchema,
@@ -2877,10 +3914,10 @@ function createServerContext(serverCwd) {
2877
3914
  action: z.enum(CHECK_ACTIONS),
2878
3915
  sessionId: z.string().describe("Session ID"),
2879
3916
  cursor: z.number().int().nonnegative().optional().describe("Default: 0"),
2880
- responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Default: 'minimal'"),
2881
- maxEvents: z.number().int().positive().optional().describe("Default: 200 (minimal), unlimited (full)"),
2882
- requestId: z.string().optional().describe("Permission request ID"),
2883
- decision: z.enum(["allow", "deny"]).optional().describe("Decision"),
3917
+ responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Default: 'minimal'. Use 'delta_compact' for lightweight polling."),
3918
+ maxEvents: z.number().int().positive().optional().describe("Default: 200 (minimal), unlimited (full/delta_compact)"),
3919
+ requestId: z.string().optional().describe("Default: none"),
3920
+ decision: z.enum(["allow", "deny", "allow_for_session"]).optional().describe("Default: none"),
2884
3921
  denyMessage: z.string().optional().describe("Default: 'Permission denied by caller'"),
2885
3922
  interrupt: z.boolean().optional().describe("Default: false"),
2886
3923
  pollOptions: z.object({
@@ -2888,11 +3925,11 @@ function createServerContext(serverCwd) {
2888
3925
  includeEvents: z.boolean().optional().describe("Default: true"),
2889
3926
  includeActions: z.boolean().optional().describe("Default: true"),
2890
3927
  includeResult: z.boolean().optional().describe("Default: true"),
2891
- includeUsage: z.boolean().optional().describe("Default: full=true, minimal=false"),
2892
- includeModelUsage: z.boolean().optional().describe("Default: full=true, minimal=false"),
2893
- includeStructuredOutput: z.boolean().optional().describe("Default: full=true, minimal=false"),
2894
- includeTerminalEvents: z.boolean().optional().describe("Default: full=true, minimal=false"),
2895
- includeProgressEvents: z.boolean().optional().describe("Default: full=true, minimal=false"),
3928
+ includeUsage: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3929
+ includeModelUsage: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3930
+ includeStructuredOutput: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3931
+ includeTerminalEvents: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3932
+ includeProgressEvents: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
2896
3933
  maxBytes: z.number().int().positive().optional().describe("Default: unlimited")
2897
3934
  }).optional().describe("Default: none"),
2898
3935
  permissionOptions: z.object({
@@ -2944,7 +3981,7 @@ function createServerContext(serverCwd) {
2944
3981
  }
2945
3982
  }
2946
3983
  );
2947
- registerResources(server, { toolCache });
3984
+ registerResources(server, { toolCache, version: SERVER_VERSION, sessionManager });
2948
3985
  return { server, sessionManager, toolCache };
2949
3986
  }
2950
3987