@leo000001/claude-code-mcp 2.2.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7,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);
629
844
  }
630
- return full;
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));
854
+ }
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
+ };
1011
1294
  }
1012
- if (!options2.blockedPath && Array.isArray(sessionInfo.allowedTools) && sessionInfo.allowedTools.includes(toolName)) {
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
+ };
1301
+ }
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: "",
@@ -1485,9 +1820,10 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1485
1820
  );
1486
1821
  }
1487
1822
  const flat = {
1488
- cwd,
1823
+ cwd: normalizedCwd,
1489
1824
  allowedTools: input.allowedTools,
1490
1825
  disallowedTools: input.disallowedTools,
1826
+ strictAllowedTools: input.strictAllowedTools ?? adv.strictAllowedTools,
1491
1827
  maxTurns: input.maxTurns,
1492
1828
  model: input.model,
1493
1829
  systemPrompt: input.systemPrompt,
@@ -1519,7 +1855,10 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1519
1855
  sessionId: init.session_id,
1520
1856
  source: normalizedFlat,
1521
1857
  permissionMode: "default",
1522
- abortController
1858
+ abortController,
1859
+ queryInterrupt: () => {
1860
+ handle.interrupt();
1861
+ }
1523
1862
  })
1524
1863
  );
1525
1864
  }
@@ -1548,6 +1887,42 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
1548
1887
  }
1549
1888
 
1550
1889
  // src/tools/claude-code-reply.ts
1890
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
1891
+ import os2 from "os";
1892
+ import path3 from "path";
1893
+ function normalizeAndAssertCwd(cwd, contextLabel) {
1894
+ const normalizedCwd = normalizeWindowsPathLike(cwd);
1895
+ const resolvedCwd = resolvePortableTmpAlias(normalizedCwd);
1896
+ if (!existsSync3(resolvedCwd)) {
1897
+ throw new Error(
1898
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} path does not exist: ${resolvedCwd}`
1899
+ );
1900
+ }
1901
+ try {
1902
+ const stat = statSync2(resolvedCwd);
1903
+ if (!stat.isDirectory()) {
1904
+ throw new Error(
1905
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} must be a directory: ${resolvedCwd}`
1906
+ );
1907
+ }
1908
+ } catch (err) {
1909
+ if (err instanceof Error && err.message.includes("Error [")) throw err;
1910
+ const detail = err instanceof Error ? ` (${err.message})` : "";
1911
+ throw new Error(
1912
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} is not accessible: ${resolvedCwd}${detail}`
1913
+ );
1914
+ }
1915
+ return resolvedCwd;
1916
+ }
1917
+ function resolvePortableTmpAlias(cwd) {
1918
+ if (process.platform !== "win32") return cwd;
1919
+ const normalized = cwd.replace(/\\/g, "/");
1920
+ if (normalized === "/tmp") return os2.tmpdir();
1921
+ if (normalized.startsWith("/tmp/")) {
1922
+ return path3.join(os2.tmpdir(), normalized.slice("/tmp/".length));
1923
+ }
1924
+ return cwd;
1925
+ }
1551
1926
  function toStartError(sessionId, err) {
1552
1927
  const message = err instanceof Error ? err.message : String(err);
1553
1928
  const errorText = message.includes("Error [") ? message : `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`;
@@ -1567,7 +1942,11 @@ function buildOptionsFromDiskResume(dr) {
1567
1942
  if (dr.cwd === void 0 || typeof dr.cwd !== "string" || dr.cwd.trim() === "") {
1568
1943
  throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be provided for disk resume.`);
1569
1944
  }
1570
- return buildOptions(dr);
1945
+ const normalizedCwd = normalizeAndAssertCwd(dr.cwd, "disk resume cwd");
1946
+ return buildOptions({
1947
+ ...dr,
1948
+ cwd: normalizedCwd
1949
+ });
1571
1950
  }
1572
1951
  async function executeClaudeCodeReply(input, sessionManager, toolCache, requestSignal) {
1573
1952
  const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
@@ -1638,7 +2017,7 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1638
2017
  })
1639
2018
  );
1640
2019
  try {
1641
- consumeQuery({
2020
+ const handle = consumeQuery({
1642
2021
  mode: "disk-resume",
1643
2022
  sessionId: input.sessionId,
1644
2023
  prompt: input.prompt,
@@ -1649,6 +2028,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1649
2028
  sessionManager,
1650
2029
  toolCache
1651
2030
  });
2031
+ sessionManager.update(input.sessionId, {
2032
+ queryInterrupt: () => {
2033
+ handle.interrupt();
2034
+ }
2035
+ });
1652
2036
  } catch (err) {
1653
2037
  const { agentResult, errorText } = toStartError(input.sessionId, err);
1654
2038
  sessionManager.setResult(input.sessionId, {
@@ -1661,7 +2045,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1661
2045
  data: agentResult,
1662
2046
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1663
2047
  });
1664
- sessionManager.update(input.sessionId, { status: "error", abortController: void 0 });
2048
+ sessionManager.update(input.sessionId, {
2049
+ status: "error",
2050
+ abortController: void 0,
2051
+ queryInterrupt: void 0
2052
+ });
1665
2053
  return { sessionId: input.sessionId, status: "error", error: errorText };
1666
2054
  }
1667
2055
  return {
@@ -1683,7 +2071,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1683
2071
  data: agentResult,
1684
2072
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1685
2073
  });
1686
- sessionManager.update(input.sessionId, { status: "error", abortController: void 0 });
2074
+ sessionManager.update(input.sessionId, {
2075
+ status: "error",
2076
+ abortController: void 0,
2077
+ queryInterrupt: void 0
2078
+ });
1687
2079
  }
1688
2080
  return {
1689
2081
  sessionId: input.sessionId,
@@ -1718,7 +2110,9 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1718
2110
  };
1719
2111
  }
1720
2112
  const session = acquired;
2113
+ const normalizedCwd = normalizeAndAssertCwd(session.cwd, "session cwd");
1721
2114
  const options = buildOptions(session);
2115
+ options.cwd = normalizedCwd;
1722
2116
  if (input.forkSession) options.forkSession = true;
1723
2117
  if (input.forkSession && !sessionManager.hasCapacityFor(1)) {
1724
2118
  sessionManager.update(input.sessionId, { status: originalStatus, abortController: void 0 });
@@ -1757,7 +2151,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1757
2151
  if (init.session_id === input.sessionId) return;
1758
2152
  sessionManager.update(input.sessionId, {
1759
2153
  status: originalStatus,
1760
- abortController: void 0
2154
+ abortController: void 0,
2155
+ queryInterrupt: void 0
1761
2156
  });
1762
2157
  if (!sessionManager.get(init.session_id)) {
1763
2158
  sessionManager.create(
@@ -1765,12 +2160,22 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1765
2160
  sessionId: init.session_id,
1766
2161
  source: { ...session, ...sourceOverrides },
1767
2162
  permissionMode: "default",
1768
- abortController
2163
+ abortController,
2164
+ queryInterrupt: () => {
2165
+ handle.interrupt();
2166
+ }
1769
2167
  })
1770
2168
  );
1771
2169
  }
1772
2170
  }
1773
2171
  });
2172
+ if (!input.forkSession) {
2173
+ sessionManager.update(input.sessionId, {
2174
+ queryInterrupt: () => {
2175
+ handle.interrupt();
2176
+ }
2177
+ });
2178
+ }
1774
2179
  const sessionId = input.forkSession ? await raceWithAbort(
1775
2180
  handle.sdkSessionIdPromise,
1776
2181
  requestSignal,
@@ -1795,7 +2200,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1795
2200
  if (input.forkSession) {
1796
2201
  sessionManager.update(input.sessionId, {
1797
2202
  status: originalStatus,
1798
- abortController: void 0
2203
+ abortController: void 0,
2204
+ queryInterrupt: void 0
1799
2205
  });
1800
2206
  } else {
1801
2207
  sessionManager.setResult(input.sessionId, {
@@ -1808,7 +2214,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1808
2214
  data: agentResult,
1809
2215
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
1810
2216
  });
1811
- sessionManager.update(input.sessionId, { status: "error", abortController: void 0 });
2217
+ sessionManager.update(input.sessionId, {
2218
+ status: "error",
2219
+ abortController: void 0,
2220
+ queryInterrupt: void 0
2221
+ });
1812
2222
  }
1813
2223
  return {
1814
2224
  sessionId: input.sessionId,
@@ -1819,14 +2229,18 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
1819
2229
  }
1820
2230
 
1821
2231
  // src/tools/tool-discovery.ts
2232
+ var DEFAULT_PERMISSION_MODEL = "policy_controlled";
2233
+ var DEFAULT_SCHEMA_AVAILABILITY = "none";
1822
2234
  var TOOL_CATALOG = {
1823
2235
  Bash: {
1824
2236
  description: "Run shell commands",
1825
- category: "execute"
2237
+ category: "execute",
2238
+ notes: ["Execution may require permission approval depending on session policy."]
1826
2239
  },
1827
2240
  Read: {
1828
2241
  description: "Read file contents (large files: use offset/limit or Grep)",
1829
- category: "file_read"
2242
+ category: "file_read",
2243
+ notes: ["Large reads are often capped by the backend; use offset/limit when needed."]
1830
2244
  },
1831
2245
  Write: {
1832
2246
  description: "Create or overwrite files",
@@ -1855,7 +2269,8 @@ var TOOL_CATALOG = {
1855
2269
  WebSearch: { description: "Web search", category: "network" },
1856
2270
  Task: {
1857
2271
  description: "Spawn subagent (must be in allowedTools)",
1858
- category: "agent"
2272
+ category: "agent",
2273
+ availabilityConditions: ["Requires Task to be visible and approved by session policy."]
1859
2274
  },
1860
2275
  TaskOutput: { description: "Get subagent output", category: "agent" },
1861
2276
  TaskStop: { description: "Cancel subagent", category: "agent" },
@@ -1869,22 +2284,35 @@ var TOOL_CATALOG = {
1869
2284
  },
1870
2285
  TeamDelete: {
1871
2286
  description: "Delete team (may need shutdown_approved first)",
1872
- category: "agent"
2287
+ category: "agent",
2288
+ notes: ["Team cleanup can be asynchronous during shutdown."]
1873
2289
  }
1874
2290
  };
1875
2291
  function uniq(items) {
1876
2292
  return Array.from(new Set(items));
1877
2293
  }
2294
+ function withToolDefaults(tool) {
2295
+ return {
2296
+ ...tool,
2297
+ permissionModel: tool.permissionModel ?? DEFAULT_PERMISSION_MODEL,
2298
+ schemaAvailability: tool.schemaAvailability ?? DEFAULT_SCHEMA_AVAILABILITY
2299
+ };
2300
+ }
1878
2301
  function discoverToolsFromInit(initTools) {
1879
2302
  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
- }));
2303
+ return names.map(
2304
+ (name) => withToolDefaults({
2305
+ name,
2306
+ description: TOOL_CATALOG[name]?.description ?? name,
2307
+ category: TOOL_CATALOG[name]?.category,
2308
+ availabilityConditions: TOOL_CATALOG[name]?.availabilityConditions,
2309
+ platformConstraints: TOOL_CATALOG[name]?.platformConstraints,
2310
+ notes: TOOL_CATALOG[name]?.notes
2311
+ })
2312
+ );
1885
2313
  }
1886
2314
  function defaultCatalogTools() {
1887
- return Object.keys(TOOL_CATALOG).sort((a, b) => a.localeCompare(b)).map((name) => ({ name, ...TOOL_CATALOG[name] }));
2315
+ return Object.keys(TOOL_CATALOG).sort((a, b) => a.localeCompare(b)).map((name) => withToolDefaults({ name, ...TOOL_CATALOG[name] }));
1888
2316
  }
1889
2317
  var ToolDiscoveryCache = class {
1890
2318
  cached;
@@ -1953,7 +2381,7 @@ function pollIntervalForStatus(status) {
1953
2381
  return void 0;
1954
2382
  }
1955
2383
  function toPermissionResult(params) {
1956
- if (params.decision === "allow") {
2384
+ if (params.decision === "allow" || params.decision === "allow_for_session") {
1957
2385
  return {
1958
2386
  behavior: "allow",
1959
2387
  updatedInput: params.updatedInput,
@@ -1966,6 +2394,19 @@ function toPermissionResult(params) {
1966
2394
  interrupt: params.interrupt
1967
2395
  };
1968
2396
  }
2397
+ function appendAllowForSessionUpdate(updates, toolName) {
2398
+ const normalizedToolName = toolName?.trim();
2399
+ if (!normalizedToolName) return updates;
2400
+ return [
2401
+ ...updates ?? [],
2402
+ {
2403
+ type: "addRules",
2404
+ behavior: "allow",
2405
+ destination: "session",
2406
+ rules: [{ toolName: normalizedToolName }]
2407
+ }
2408
+ ];
2409
+ }
1969
2410
  function slimAssistantData(data) {
1970
2411
  if (!data || typeof data !== "object") return data;
1971
2412
  const d = data;
@@ -2026,10 +2467,23 @@ function detectPathCompatibilityWarnings(session) {
2026
2467
  `cwd '${cwd}' looks like a Windows UNC path on ${process.platform}. Confirm MCP client/server run on the same platform.`
2027
2468
  );
2028
2469
  }
2470
+ if (process.platform === "win32" && Array.isArray(session.additionalDirectories)) {
2471
+ for (const dir of session.additionalDirectories) {
2472
+ if (typeof dir === "string" && dir.startsWith("/") && !dir.startsWith("//")) {
2473
+ warnings.push(
2474
+ `additionalDirectories contains POSIX-style path '${dir}' on Windows. Consider using a Windows path to avoid path compatibility issues.`
2475
+ );
2476
+ }
2477
+ }
2478
+ }
2029
2479
  return warnings;
2030
2480
  }
2031
2481
  function isPosixHomePath(value) {
2032
- return /^\/home\/[^/]+(?:\/|$)/.test(value);
2482
+ return /^\/home\/[^/\s]+(?:\/|$)/.test(value);
2483
+ }
2484
+ function extractPosixHomePath(value) {
2485
+ const match = value.match(/\/home\/[^/\s"'`]+(?:\/[^\s"'`]*)?/);
2486
+ return match?.[0];
2033
2487
  }
2034
2488
  function detectPendingPermissionPathWarnings(pending, session) {
2035
2489
  if (process.platform !== "win32" || pending.length === 0) return [];
@@ -2041,6 +2495,25 @@ function detectPendingPermissionPathWarnings(pending, session) {
2041
2495
  if (typeof req.blockedPath === "string") candidates.push(req.blockedPath);
2042
2496
  const filePath = req.input.file_path;
2043
2497
  if (typeof filePath === "string") candidates.push(filePath);
2498
+ const pathFields = [
2499
+ "path",
2500
+ "directory",
2501
+ "folder",
2502
+ "cwd",
2503
+ "dest",
2504
+ "destination",
2505
+ "source",
2506
+ "target"
2507
+ ];
2508
+ for (const field of pathFields) {
2509
+ const val = req.input[field];
2510
+ if (typeof val === "string") candidates.push(val);
2511
+ }
2512
+ const command = req.input.command;
2513
+ if (typeof command === "string") {
2514
+ const embeddedPath = extractPosixHomePath(command);
2515
+ if (embeddedPath) candidates.push(embeddedPath);
2516
+ }
2044
2517
  const badPath = candidates.find((p) => isPosixHomePath(p));
2045
2518
  if (!badPath) continue;
2046
2519
  warnings.push(
@@ -2053,8 +2526,14 @@ function computeToolValidation(session, initTools) {
2053
2526
  if (!session) return { summary: void 0, warnings: [] };
2054
2527
  const allowedTools = uniqSorted(session.allowedTools);
2055
2528
  const disallowedTools = uniqSorted(session.disallowedTools);
2529
+ const warnings = [];
2530
+ if (allowedTools.length > 0 && session.strictAllowedTools !== true) {
2531
+ warnings.push(
2532
+ "allowedTools currently acts as pre-approval only. Set strictAllowedTools=true to enforce a strict allowlist."
2533
+ );
2534
+ }
2056
2535
  if (allowedTools.length === 0 && disallowedTools.length === 0) {
2057
- return { summary: void 0, warnings: [] };
2536
+ return { summary: void 0, warnings };
2058
2537
  }
2059
2538
  if (!Array.isArray(initTools) || initTools.length === 0) {
2060
2539
  return {
@@ -2064,6 +2543,7 @@ function computeToolValidation(session, initTools) {
2064
2543
  unknownDisallowedTools: []
2065
2544
  },
2066
2545
  warnings: [
2546
+ ...warnings,
2067
2547
  "Runtime tool list is not available yet; unknown allowedTools/disallowedTools names cannot be validated until system/init tools arrive."
2068
2548
  ]
2069
2549
  };
@@ -2073,7 +2553,6 @@ function computeToolValidation(session, initTools) {
2073
2553
  );
2074
2554
  const unknownAllowedTools = allowedTools.filter((name) => !runtime.has(name));
2075
2555
  const unknownDisallowedTools = disallowedTools.filter((name) => !runtime.has(name));
2076
- const warnings = [];
2077
2556
  if (unknownAllowedTools.length > 0) {
2078
2557
  warnings.push(
2079
2558
  `Unknown allowedTools (not present in runtime tools): ${unknownAllowedTools.join(", ")}.`
@@ -2114,11 +2593,12 @@ function capEventsByBytes(events, maxBytes) {
2114
2593
  }
2115
2594
  function buildResult(sessionManager, toolCache, input) {
2116
2595
  const responseMode = input.responseMode ?? "minimal";
2596
+ const compactMode = responseMode === "delta_compact";
2117
2597
  const po = input.pollOptions ?? {};
2118
2598
  const includeTools = po.includeTools;
2119
- const includeEvents = po.includeEvents ?? true;
2599
+ const includeEvents = po.includeEvents ?? !compactMode;
2120
2600
  const includeActions = po.includeActions ?? true;
2121
- const includeResult = po.includeResult ?? true;
2601
+ const includeResult = po.includeResult ?? !compactMode;
2122
2602
  const includeUsage = po.includeUsage ?? responseMode === "full";
2123
2603
  const includeModelUsage = po.includeModelUsage ?? responseMode === "full";
2124
2604
  const includeStructuredOutput = po.includeStructuredOutput ?? responseMode === "full";
@@ -2174,7 +2654,7 @@ function buildResult(sessionManager, toolCache, input) {
2174
2654
  includeUsage,
2175
2655
  includeModelUsage,
2176
2656
  includeStructuredOutput,
2177
- slim: responseMode === "minimal"
2657
+ slim: responseMode === "minimal" || responseMode === "delta_compact"
2178
2658
  });
2179
2659
  const cappedEvents = capEventsByBytes(shapedEvents, maxBytes);
2180
2660
  if (cappedEvents.truncated) {
@@ -2219,7 +2699,7 @@ function buildResult(sessionManager, toolCache, input) {
2219
2699
  includeUsage,
2220
2700
  includeModelUsage,
2221
2701
  includeStructuredOutput,
2222
- slim: responseMode === "minimal"
2702
+ slim: responseMode === "minimal" || responseMode === "delta_compact"
2223
2703
  }) : void 0,
2224
2704
  cancelledAt: session?.cancelledAt,
2225
2705
  cancelledReason: session?.cancelledReason,
@@ -2286,20 +2766,25 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache, requestSignal)
2286
2766
  isError: true
2287
2767
  };
2288
2768
  }
2289
- if (input.decision !== "allow" && input.decision !== "deny") {
2769
+ if (input.decision !== "allow" && input.decision !== "deny" && input.decision !== "allow_for_session") {
2290
2770
  return {
2291
2771
  sessionId: input.sessionId,
2292
- error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision must be 'allow' or 'deny'.`,
2772
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision must be 'allow', 'deny', or 'allow_for_session'.`,
2293
2773
  isError: true
2294
2774
  };
2295
2775
  }
2776
+ const pendingRequest = input.decision === "allow_for_session" ? sessionManager.getPendingPermission(input.sessionId, input.requestId) : void 0;
2777
+ const updatedPermissions = input.decision === "allow_for_session" ? appendAllowForSessionUpdate(
2778
+ input.permissionOptions?.updatedPermissions,
2779
+ pendingRequest?.toolName
2780
+ ) : input.permissionOptions?.updatedPermissions;
2296
2781
  const ok = sessionManager.finishRequest(
2297
2782
  input.sessionId,
2298
2783
  input.requestId,
2299
2784
  toPermissionResult({
2300
2785
  decision: input.decision,
2301
2786
  updatedInput: input.permissionOptions?.updatedInput,
2302
- updatedPermissions: input.permissionOptions?.updatedPermissions,
2787
+ updatedPermissions,
2303
2788
  denyMessage: input.denyMessage,
2304
2789
  interrupt: input.interrupt
2305
2790
  }),
@@ -2312,10 +2797,38 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache, requestSignal)
2312
2797
  isError: true
2313
2798
  };
2314
2799
  }
2800
+ if (input.decision === "allow_for_session" && pendingRequest?.toolName) {
2801
+ sessionManager.allowToolForSession(input.sessionId, pendingRequest.toolName);
2802
+ }
2315
2803
  return buildResult(sessionManager, toolCache, input);
2316
2804
  }
2317
2805
 
2318
2806
  // src/tools/claude-code-session.ts
2807
+ var ALWAYS_REDACTED_FIELDS = [
2808
+ "env",
2809
+ "mcpServers",
2810
+ "sandbox",
2811
+ "debugFile",
2812
+ "pathToClaudeCodeExecutable"
2813
+ ];
2814
+ var CONDITIONAL_REDACTED_FIELDS = [
2815
+ "cwd",
2816
+ "systemPrompt",
2817
+ "agents",
2818
+ "additionalDirectories"
2819
+ ];
2820
+ function buildRedactions(includeSensitive) {
2821
+ const redactions = [];
2822
+ for (const field of ALWAYS_REDACTED_FIELDS) {
2823
+ redactions?.push({ field, reason: "secret_or_internal" });
2824
+ }
2825
+ if (!includeSensitive) {
2826
+ for (const field of CONDITIONAL_REDACTED_FIELDS) {
2827
+ redactions?.push({ field, reason: "sensitive_by_default" });
2828
+ }
2829
+ }
2830
+ return redactions;
2831
+ }
2319
2832
  function executeClaudeCodeSession(input, sessionManager, requestSignal) {
2320
2833
  if (requestSignal?.aborted) {
2321
2834
  return {
@@ -2324,7 +2837,23 @@ function executeClaudeCodeSession(input, sessionManager, requestSignal) {
2324
2837
  isError: true
2325
2838
  };
2326
2839
  }
2327
- const toSessionJson = (s) => input.includeSensitive ? sessionManager.toSensitiveJSON(s) : sessionManager.toPublicJSON(s);
2840
+ const toSessionJson = (s) => {
2841
+ const base = input.includeSensitive ? sessionManager.toSensitiveJSON(s) : sessionManager.toPublicJSON(s);
2842
+ const stored = sessionManager.getResult(s.sessionId);
2843
+ const lastError = stored?.type === "error" ? stored.result.result : void 0;
2844
+ const lastErrorAt = stored?.type === "error" ? stored.createdAt : void 0;
2845
+ return {
2846
+ ...base,
2847
+ pendingPermissionCount: sessionManager.getPendingPermissionCount(s.sessionId),
2848
+ eventCount: sessionManager.getEventCount(s.sessionId),
2849
+ currentCursor: sessionManager.getCurrentCursor(s.sessionId),
2850
+ lastEventId: sessionManager.getLastEventId(s.sessionId),
2851
+ ttlMs: sessionManager.getRemainingTtlMs(s.sessionId),
2852
+ lastError,
2853
+ lastErrorAt,
2854
+ redactions: buildRedactions(input.includeSensitive)
2855
+ };
2856
+ };
2328
2857
  switch (input.action) {
2329
2858
  case "list": {
2330
2859
  const sessions = sessionManager.list().map((s) => toSessionJson(s));
@@ -2381,23 +2910,65 @@ function executeClaudeCodeSession(input, sessionManager, requestSignal) {
2381
2910
  message: `Session '${input.sessionId}' cancelled.`
2382
2911
  };
2383
2912
  }
2913
+ case "interrupt": {
2914
+ if (!input.sessionId) {
2915
+ return {
2916
+ sessions: [],
2917
+ message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: sessionId is required for 'interrupt' action.`,
2918
+ isError: true
2919
+ };
2920
+ }
2921
+ const interrupted = sessionManager.interrupt(input.sessionId, {
2922
+ reason: "Interrupted by caller",
2923
+ source: "claude_code_session"
2924
+ });
2925
+ if (!interrupted) {
2926
+ const session = sessionManager.get(input.sessionId);
2927
+ if (!session) {
2928
+ return {
2929
+ sessions: [],
2930
+ message: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found.`,
2931
+ isError: true
2932
+ };
2933
+ }
2934
+ return {
2935
+ sessions: [toSessionJson(session)],
2936
+ message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Session '${input.sessionId}' is not running (status: ${session.status}).`,
2937
+ isError: true
2938
+ };
2939
+ }
2940
+ const updated = sessionManager.get(input.sessionId);
2941
+ return {
2942
+ sessions: updated ? [toSessionJson(updated)] : [],
2943
+ message: `Session '${input.sessionId}' interrupted.`
2944
+ };
2945
+ }
2384
2946
  default:
2385
2947
  return {
2386
2948
  sessions: [],
2387
- message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown action '${input.action}'. Use 'list', 'get', or 'cancel'.`,
2949
+ message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown action '${input.action}'. Use 'list', 'get', 'cancel', or 'interrupt'.`,
2388
2950
  isError: true
2389
2951
  };
2390
2952
  }
2391
2953
  }
2392
2954
 
2393
2955
  // src/resources/register-resources.ts
2956
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2957
+ import { createHash } from "crypto";
2394
2958
  var RESOURCE_SCHEME = "claude-code-mcp";
2395
2959
  var RESOURCE_URIS = {
2396
2960
  serverInfo: `${RESOURCE_SCHEME}:///server-info`,
2397
2961
  internalTools: `${RESOURCE_SCHEME}:///internal-tools`,
2398
2962
  gotchas: `${RESOURCE_SCHEME}:///gotchas`,
2963
+ quickstart: `${RESOURCE_SCHEME}:///quickstart`,
2964
+ errors: `${RESOURCE_SCHEME}:///errors`,
2399
2965
  compatReport: `${RESOURCE_SCHEME}:///compat-report`
2400
2966
  };
2967
+ var RESOURCE_TEMPLATES = {
2968
+ sessionById: `${RESOURCE_SCHEME}:///session/{sessionId}`,
2969
+ runtimeTools: `${RESOURCE_SCHEME}:///tools/runtime{?sessionId}`,
2970
+ compatDiff: `${RESOURCE_SCHEME}:///compat/diff{?client}`
2971
+ };
2401
2972
  function asTextResource(uri, text, mimeType) {
2402
2973
  return {
2403
2974
  contents: [
@@ -2409,7 +2980,136 @@ function asTextResource(uri, text, mimeType) {
2409
2980
  ]
2410
2981
  };
2411
2982
  }
2983
+ function asJsonResource(uri, value) {
2984
+ return asTextResource(uri, JSON.stringify(value, null, 2), "application/json");
2985
+ }
2986
+ function serializeLimit(limit) {
2987
+ return Number.isFinite(limit) ? limit : "unlimited";
2988
+ }
2989
+ function computeEtag(value) {
2990
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
2991
+ }
2992
+ function extractSingleVariable(value) {
2993
+ if (typeof value === "string" && value.trim() !== "") return value;
2994
+ if (Array.isArray(value) && typeof value[0] === "string" && value[0].trim() !== "")
2995
+ return value[0];
2996
+ return void 0;
2997
+ }
2998
+ function buildSessionRedactions(includeSensitive) {
2999
+ const redactions = [
3000
+ { field: "env", reason: "secret_or_internal" },
3001
+ { field: "mcpServers", reason: "secret_or_internal" },
3002
+ { field: "sandbox", reason: "secret_or_internal" },
3003
+ { field: "debugFile", reason: "secret_or_internal" },
3004
+ { field: "pathToClaudeCodeExecutable", reason: "secret_or_internal" }
3005
+ ];
3006
+ if (!includeSensitive) {
3007
+ redactions.push(
3008
+ { field: "cwd", reason: "sensitive_by_default" },
3009
+ { field: "systemPrompt", reason: "sensitive_by_default" },
3010
+ { field: "agents", reason: "sensitive_by_default" },
3011
+ { field: "additionalDirectories", reason: "sensitive_by_default" }
3012
+ );
3013
+ }
3014
+ return redactions;
3015
+ }
3016
+ function buildGotchasEntries() {
3017
+ return [
3018
+ {
3019
+ id: "permission-timeout",
3020
+ title: "Permission requests auto-deny on timeout",
3021
+ severity: "high",
3022
+ appliesTo: ["claude_code_check.actions", "permission workflow"],
3023
+ symptom: "Session waits for approval, then tool call is denied without explicit caller decision.",
3024
+ detection: "Observe actions[].expiresAt/remainingMs and permission_result with source=timeout.",
3025
+ remedy: "Poll more frequently and respond before timeout; increase permissionRequestTimeoutMs if needed.",
3026
+ example: "Default timeout is 60000ms and server-clamped to 300000ms."
3027
+ },
3028
+ {
3029
+ id: "read-size-cap",
3030
+ title: "Read may cap response size",
3031
+ severity: "medium",
3032
+ appliesTo: ["Read tool"],
3033
+ symptom: "Large file reads are truncated or fail.",
3034
+ detection: "Read responses are incomplete for large files.",
3035
+ remedy: "Use offset/limit paging or chunk with Grep."
3036
+ },
3037
+ {
3038
+ id: "edit-replace-all",
3039
+ title: "Edit replace_all uses substring matching",
3040
+ severity: "medium",
3041
+ appliesTo: ["Edit tool"],
3042
+ symptom: "replace_all=true fails unexpectedly.",
3043
+ detection: "Tool returns error when no exact substring match is found.",
3044
+ remedy: "Validate exact match text first; prefer smaller targeted replacements."
3045
+ },
3046
+ {
3047
+ id: "windows-path-normalization",
3048
+ title: "Windows path normalization applies to common MSYS-style paths",
3049
+ severity: "medium",
3050
+ appliesTo: ["Windows", "cwd", "additionalDirectories", "file_path"],
3051
+ symptom: "Permission/path behavior differs from raw input path text.",
3052
+ detection: "Compare submitted path with effective path in logs/permission prompts.",
3053
+ remedy: "Use absolute native Windows paths when possible; avoid relying on implicit conversion behavior.",
3054
+ example: "/d/... /mnt/c/... /cygdrive/c/... //server/share/... are normalized on Windows."
3055
+ },
3056
+ {
3057
+ id: "team-delete-async-cleanup",
3058
+ title: "TeamDelete cleanup can be asynchronous",
3059
+ severity: "low",
3060
+ appliesTo: ["TeamDelete"],
3061
+ symptom: "Immediate follow-up calls still see active members.",
3062
+ detection: "TeamDelete reports shutdown_approved or active member transitions.",
3063
+ remedy: "Retry after short delay; wait for shutdown state to settle."
3064
+ },
3065
+ {
3066
+ id: "skills-late-availability",
3067
+ title: "Skills may become available later in a session",
3068
+ severity: "low",
3069
+ appliesTo: ["Skills"],
3070
+ symptom: "Early calls show unknown skill/tool errors.",
3071
+ detection: "Later calls in same session succeed without config changes.",
3072
+ remedy: "Retry after initialization events are complete."
3073
+ },
3074
+ {
3075
+ id: "tool-count-sources-differ",
3076
+ title: "toolCatalogCount and availableTools have different sources",
3077
+ severity: "medium",
3078
+ appliesTo: ["internal-tools", "claude_code_check.availableTools", "compat-report"],
3079
+ symptom: "Tool counts appear inconsistent (for example 15 vs 28).",
3080
+ detection: "Compare compat-report.toolCounts and session-level availableTools.",
3081
+ remedy: "Treat catalog count as server-known baseline and availableTools as session runtime view from system/init.tools."
3082
+ },
3083
+ {
3084
+ id: "available-tools-not-exhaustive",
3085
+ title: "availableTools may omit internal features",
3086
+ severity: "low",
3087
+ appliesTo: ["claude_code_check.availableTools"],
3088
+ symptom: "A known feature is callable but not listed in availableTools.",
3089
+ detection: "Feature works while missing from availableTools list.",
3090
+ remedy: "Use availableTools as runtime hint, not exhaustive capability proof.",
3091
+ example: "Some internal features (e.g. ToolSearch) may not appear."
3092
+ }
3093
+ ];
3094
+ }
3095
+ function asVersionedPayload(params) {
3096
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3097
+ return {
3098
+ ...params.payload,
3099
+ schemaVersion: params.schemaVersion,
3100
+ updatedAt,
3101
+ etag: computeEtag(params.payload),
3102
+ stability: params.stability
3103
+ };
3104
+ }
2412
3105
  function registerResources(server, deps) {
3106
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
3107
+ const resourceSchemaVersion = "1.3";
3108
+ const mcpProtocolVersion = "2025-03-26";
3109
+ const gotchasEntries = buildGotchasEntries();
3110
+ const catalogToolNames = new Set(defaultCatalogTools().map((tool) => tool.name));
3111
+ const staticResourceUris = Object.values(RESOURCE_URIS);
3112
+ const templateUris = Object.values(RESOURCE_TEMPLATES);
2413
3113
  const serverInfoUri = new URL(RESOURCE_URIS.serverInfo);
2414
3114
  server.registerResource(
2415
3115
  "server_info",
@@ -2419,21 +3119,50 @@ function registerResources(server, deps) {
2419
3119
  description: "Server metadata (version/platform/runtime).",
2420
3120
  mimeType: "application/json"
2421
3121
  },
2422
- () => asTextResource(
3122
+ () => asJsonResource(
2423
3123
  serverInfoUri,
2424
- JSON.stringify(
2425
- {
3124
+ (() => {
3125
+ const base = {
2426
3126
  name: "claude-code-mcp",
3127
+ version: deps.version,
2427
3128
  node: process.version,
2428
3129
  platform: process.platform,
2429
3130
  arch: process.arch,
2430
- resources: Object.values(RESOURCE_URIS),
2431
- toolCatalogCount: deps.toolCache.getTools().length
2432
- },
2433
- null,
2434
- 2
2435
- ),
2436
- "application/json"
3131
+ mcpProtocolVersion,
3132
+ startedAt,
3133
+ uptimeSec: Math.floor(process.uptime()),
3134
+ resources: staticResourceUris,
3135
+ resourceTemplates: templateUris,
3136
+ toolCatalogCount: deps.toolCache.getTools().length,
3137
+ capabilities: {
3138
+ resources: true,
3139
+ toolsListChanged: true,
3140
+ resourcesListChanged: true,
3141
+ prompts: false,
3142
+ completions: false
3143
+ },
3144
+ limits: {
3145
+ maxSessions: serializeLimit(deps.sessionManager.getMaxSessions()),
3146
+ maxPendingPermissionsPerSession: serializeLimit(
3147
+ deps.sessionManager.getMaxPendingPermissionsPerSession()
3148
+ ),
3149
+ eventBuffer: deps.sessionManager.getEventBufferConfig(),
3150
+ pollDefaults: {
3151
+ runningMs: 3e3,
3152
+ waitingPermissionMs: 1e3
3153
+ }
3154
+ }
3155
+ };
3156
+ if (typeof process.env.CLAUDE_CODE_MCP_BUILD_COMMIT === "string") {
3157
+ const commit = process.env.CLAUDE_CODE_MCP_BUILD_COMMIT.trim();
3158
+ if (commit !== "") base.buildCommit = commit;
3159
+ }
3160
+ return asVersionedPayload({
3161
+ schemaVersion: resourceSchemaVersion,
3162
+ stability: "stable",
3163
+ payload: base
3164
+ });
3165
+ })()
2437
3166
  )
2438
3167
  );
2439
3168
  const toolsUri = new URL(RESOURCE_URIS.internalTools);
@@ -2445,10 +3174,19 @@ function registerResources(server, deps) {
2445
3174
  description: "Claude Code internal tool catalog (runtime-aware).",
2446
3175
  mimeType: "application/json"
2447
3176
  },
2448
- () => asTextResource(
3177
+ () => asJsonResource(
2449
3178
  toolsUri,
2450
- JSON.stringify({ tools: deps.toolCache.getTools() }, null, 2),
2451
- "application/json"
3179
+ (() => {
3180
+ const base = {
3181
+ tools: deps.toolCache.getTools(),
3182
+ toolCatalogCount: deps.toolCache.getTools().length
3183
+ };
3184
+ return asVersionedPayload({
3185
+ schemaVersion: resourceSchemaVersion,
3186
+ stability: "stable",
3187
+ payload: base
3188
+ });
3189
+ })()
2452
3190
  )
2453
3191
  );
2454
3192
  const gotchasUri = new URL(RESOURCE_URIS.gotchas);
@@ -2465,19 +3203,75 @@ function registerResources(server, deps) {
2465
3203
  [
2466
3204
  "# claude-code-mcp: gotchas",
2467
3205
  "",
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`.",
3206
+ ...gotchasEntries.map((entry) => `- ${entry.title}. ${entry.remedy}`),
2475
3207
  ""
2476
3208
  ].join("\n"),
2477
3209
  "text/markdown"
2478
3210
  )
2479
3211
  );
2480
3212
  const compatReportUri = new URL(RESOURCE_URIS.compatReport);
3213
+ const quickstartUri = new URL(RESOURCE_URIS.quickstart);
3214
+ server.registerResource(
3215
+ "quickstart",
3216
+ quickstartUri.toString(),
3217
+ {
3218
+ title: "Quickstart",
3219
+ description: "Minimal async polling flow for claude_code / claude_code_check.",
3220
+ mimeType: "text/markdown"
3221
+ },
3222
+ () => asTextResource(
3223
+ quickstartUri,
3224
+ [
3225
+ "# claude-code-mcp quickstart",
3226
+ "",
3227
+ "1. Call `claude_code` with `{ prompt }` and keep `sessionId`.",
3228
+ "2. Poll with `claude_code_check(action='poll')` using `nextCursor`.",
3229
+ "3. If actions are returned, respond with `claude_code_check(action='respond_permission')`.",
3230
+ "4. Continue polling until status becomes `idle` / `error` / `cancelled`.",
3231
+ "",
3232
+ "Notes:",
3233
+ "- `respond_user_input` is not supported on this backend.",
3234
+ "- `allowedTools` is pre-approval by default; set `strictAllowedTools=true` for strict allowlist behavior.",
3235
+ "- Prefer `responseMode='delta_compact'` for high-frequency polling."
3236
+ ].join("\n"),
3237
+ "text/markdown"
3238
+ )
3239
+ );
3240
+ const errorsUri = new URL(RESOURCE_URIS.errors);
3241
+ server.registerResource(
3242
+ "errors",
3243
+ errorsUri.toString(),
3244
+ {
3245
+ title: "Errors",
3246
+ description: "Structured error codes and remediation hints.",
3247
+ mimeType: "application/json"
3248
+ },
3249
+ () => {
3250
+ const codes = Object.values(ErrorCode);
3251
+ const hints = {
3252
+ ["INVALID_ARGUMENT" /* INVALID_ARGUMENT */]: "Validate required fields and enum values.",
3253
+ ["SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */]: "Session may be expired or server-restarted.",
3254
+ ["SESSION_BUSY" /* SESSION_BUSY */]: "Wait for running/waiting_permission session to settle.",
3255
+ ["PERMISSION_REQUEST_NOT_FOUND" /* PERMISSION_REQUEST_NOT_FOUND */]: "The permission request was already finished/expired.",
3256
+ ["PERMISSION_DENIED" /* PERMISSION_DENIED */]: "Check token/secrets/policy restrictions.",
3257
+ ["RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */]: "Reduce session count or increase server limits.",
3258
+ ["TIMEOUT" /* TIMEOUT */]: "Increase timeout or poll/respond more frequently.",
3259
+ ["CANCELLED" /* CANCELLED */]: "Request/session was cancelled by caller or shutdown.",
3260
+ ["INTERNAL" /* INTERNAL */]: "Inspect server logs and runtime environment."
3261
+ };
3262
+ return asJsonResource(
3263
+ errorsUri,
3264
+ asVersionedPayload({
3265
+ schemaVersion: resourceSchemaVersion,
3266
+ stability: "stable",
3267
+ payload: {
3268
+ codes,
3269
+ hints
3270
+ }
3271
+ })
3272
+ );
3273
+ }
3274
+ );
2481
3275
  server.registerResource(
2482
3276
  "compat_report",
2483
3277
  compatReportUri.toString(),
@@ -2493,43 +3287,294 @@ function registerResources(server, deps) {
2493
3287
  "CLAUDE_CODE_GIT_BASH_PATH is not set. Auto-detection exists, but explicit path is more reliable for GUI-launched MCP clients."
2494
3288
  );
2495
3289
  }
2496
- return asTextResource(
3290
+ const diskResumeEnabled = process.env.CLAUDE_CODE_MCP_ALLOW_DISK_RESUME === "1";
3291
+ const resumeSecretConfigured = typeof process.env.CLAUDE_CODE_MCP_RESUME_SECRET === "string" && process.env.CLAUDE_CODE_MCP_RESUME_SECRET.trim() !== "";
3292
+ const runtimeToolStats = deps.sessionManager.getRuntimeToolStats();
3293
+ const toolCatalogCount = deps.toolCache.getTools().length;
3294
+ const detectedMismatches = [];
3295
+ if (runtimeToolStats.sessionsWithInitTools > 0 && runtimeToolStats.runtimeDiscoveredUniqueCount < toolCatalogCount) {
3296
+ detectedMismatches.push(
3297
+ "Runtime discovered tools are fewer than catalog tools; some features may be hidden or not surfaced in system/init.tools."
3298
+ );
3299
+ }
3300
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
3301
+ const base = {
3302
+ transport: "stdio",
3303
+ samePlatformRequired: true,
3304
+ packageVersion: deps.version,
3305
+ runtime: {
3306
+ node: process.version,
3307
+ platform: process.platform,
3308
+ arch: process.arch
3309
+ },
3310
+ limits: {
3311
+ maxSessions: serializeLimit(deps.sessionManager.getMaxSessions()),
3312
+ maxPendingPermissionsPerSession: serializeLimit(
3313
+ deps.sessionManager.getMaxPendingPermissionsPerSession()
3314
+ ),
3315
+ eventBuffer: deps.sessionManager.getEventBufferConfig()
3316
+ },
3317
+ diskResume: {
3318
+ enabled: diskResumeEnabled,
3319
+ resumeSecretConfigured
3320
+ },
3321
+ features: {
3322
+ resources: true,
3323
+ resourceTemplates: true,
3324
+ toolsListChanged: true,
3325
+ resourcesListChanged: true,
3326
+ sessionInterrupt: true,
3327
+ allowForSessionDecision: true,
3328
+ respondUserInput: false,
3329
+ prompts: false,
3330
+ completions: false
3331
+ },
3332
+ recommendedSettings: {
3333
+ responseMode: "delta_compact",
3334
+ poll: {
3335
+ runningMs: 3e3,
3336
+ waitingPermissionMs: 1e3,
3337
+ cursorStrategy: "Persist nextCursor and de-duplicate by event.id."
3338
+ },
3339
+ timeouts: {
3340
+ sessionInitTimeoutMs: 1e4,
3341
+ permissionRequestTimeoutMs: 6e4,
3342
+ permissionRequestTimeoutMaxMs: 3e5
3343
+ }
3344
+ },
3345
+ guidance: [
3346
+ "Some clients cache tool descriptions at connect time. Prefer claude_code_check(pollOptions.includeTools=true) for runtime-authoritative tool lists.",
3347
+ "Use allowedTools/disallowedTools only with exact runtime tool names.",
3348
+ "Set strictAllowedTools=true when you need allowedTools to behave as a strict allowlist.",
3349
+ "This server assumes MCP client and server run on the same machine/platform.",
3350
+ "For high-frequency status checks, prefer responseMode='delta_compact'.",
3351
+ "respond_user_input is not supported on this backend; use poll/respond_permission flow."
3352
+ ],
3353
+ toolCounts: {
3354
+ catalogCount: toolCatalogCount,
3355
+ sessionsWithInitTools: runtimeToolStats.sessionsWithInitTools,
3356
+ runtimeDiscoveredUniqueCount: runtimeToolStats.runtimeDiscoveredUniqueCount,
3357
+ explain: "catalogCount is server catalog size; runtimeDiscoveredUniqueCount is union of system/init.tools across active sessions."
3358
+ },
3359
+ toolCatalogCount,
3360
+ detectedMismatches,
3361
+ runtimeWarnings,
3362
+ resourceTemplates: templateUris
3363
+ };
3364
+ const healthScore = Math.max(
3365
+ 0,
3366
+ 100 - runtimeWarnings.length * 10 - detectedMismatches.length * 15
3367
+ );
3368
+ return asJsonResource(
2497
3369
  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."
3370
+ asVersionedPayload({
3371
+ schemaVersion: resourceSchemaVersion,
3372
+ stability: "stable",
3373
+ payload: {
3374
+ ...base,
3375
+ healthScore,
3376
+ updatedAt
3377
+ }
3378
+ })
3379
+ );
3380
+ }
3381
+ );
3382
+ server.registerResource(
3383
+ "session_snapshot_template",
3384
+ new ResourceTemplate(RESOURCE_TEMPLATES.sessionById, { list: void 0 }),
3385
+ {
3386
+ title: "Session Snapshot Template",
3387
+ description: "Read lightweight session diagnostics by sessionId.",
3388
+ mimeType: "application/json"
3389
+ },
3390
+ (uri, variables) => {
3391
+ const sessionId = extractSingleVariable(variables.sessionId) ?? extractSingleVariable(uri.searchParams.get("sessionId"));
3392
+ const payload = typeof sessionId !== "string" || sessionId.trim() === "" ? {
3393
+ found: false,
3394
+ message: "sessionId is required in URI template variable."
3395
+ } : (() => {
3396
+ const session = deps.sessionManager.get(sessionId);
3397
+ if (!session) {
3398
+ return {
3399
+ sessionId,
3400
+ found: false,
3401
+ message: `Session '${sessionId}' not found.`
3402
+ };
3403
+ }
3404
+ const base = deps.sessionManager.toPublicJSON(session);
3405
+ const stored = deps.sessionManager.getResult(sessionId);
3406
+ return {
3407
+ sessionId,
3408
+ found: true,
3409
+ session: {
3410
+ ...base,
3411
+ pendingPermissionCount: deps.sessionManager.getPendingPermissionCount(sessionId),
3412
+ eventCount: deps.sessionManager.getEventCount(sessionId),
3413
+ currentCursor: deps.sessionManager.getCurrentCursor(sessionId),
3414
+ lastEventId: deps.sessionManager.getLastEventId(sessionId),
3415
+ ttlMs: deps.sessionManager.getRemainingTtlMs(sessionId),
3416
+ lastError: stored?.type === "error" ? stored.result.result : void 0,
3417
+ lastErrorAt: stored?.type === "error" ? stored.createdAt : void 0,
3418
+ redactions: buildSessionRedactions(false)
3419
+ }
3420
+ };
3421
+ })();
3422
+ return asJsonResource(
3423
+ uri,
3424
+ asVersionedPayload({
3425
+ schemaVersion: resourceSchemaVersion,
3426
+ stability: "stable",
3427
+ payload
3428
+ })
3429
+ );
3430
+ }
3431
+ );
3432
+ server.registerResource(
3433
+ "runtime_tools_template",
3434
+ new ResourceTemplate(RESOURCE_TEMPLATES.runtimeTools, { list: void 0 }),
3435
+ {
3436
+ title: "Runtime Tools Template",
3437
+ description: "Read runtime tool view globally or for a specific sessionId.",
3438
+ mimeType: "application/json"
3439
+ },
3440
+ (uri, variables) => {
3441
+ const sessionId = extractSingleVariable(variables.sessionId) ?? extractSingleVariable(uri.searchParams.get("sessionId"));
3442
+ const internalToolCount = catalogToolNames.size;
3443
+ const payload = (() => {
3444
+ if (typeof sessionId === "string" && sessionId.trim() !== "") {
3445
+ const session = deps.sessionManager.get(sessionId);
3446
+ if (!session) {
3447
+ return {
3448
+ sessionId,
3449
+ source: "session_not_found",
3450
+ availableTools: [],
3451
+ toolCounts: {
3452
+ internalToolCount,
3453
+ runtimeAvailableCount: 0,
3454
+ sessionAugmentedToolCount: 0
3455
+ }
3456
+ };
3457
+ }
3458
+ const initTools = deps.sessionManager.getInitTools(sessionId) ?? [];
3459
+ const discovered = discoverToolsFromInit(initTools);
3460
+ const sessionAugmentedToolCount2 = discovered.filter(
3461
+ (tool) => !catalogToolNames.has(tool.name)
3462
+ ).length;
3463
+ return {
3464
+ sessionId,
3465
+ source: initTools.length > 0 ? "session_runtime" : "session_without_init",
3466
+ availableTools: discovered,
3467
+ toolCounts: {
3468
+ internalToolCount,
3469
+ runtimeAvailableCount: discovered.length,
3470
+ sessionAugmentedToolCount: sessionAugmentedToolCount2
3471
+ }
3472
+ };
3473
+ }
3474
+ const catalog = deps.toolCache.getTools();
3475
+ const sessionAugmentedToolCount = catalog.filter(
3476
+ (tool) => !catalogToolNames.has(tool.name)
3477
+ ).length;
3478
+ return {
3479
+ source: "catalog",
3480
+ availableTools: catalog,
3481
+ toolCounts: {
3482
+ internalToolCount,
3483
+ runtimeAvailableCount: catalog.length,
3484
+ sessionAugmentedToolCount
3485
+ }
3486
+ };
3487
+ })();
3488
+ return asJsonResource(
3489
+ uri,
3490
+ asVersionedPayload({
3491
+ schemaVersion: resourceSchemaVersion,
3492
+ stability: "stable",
3493
+ payload
3494
+ })
3495
+ );
3496
+ }
3497
+ );
3498
+ server.registerResource(
3499
+ "compat_diff_template",
3500
+ new ResourceTemplate(RESOURCE_TEMPLATES.compatDiff, { list: void 0 }),
3501
+ {
3502
+ title: "Compatibility Diff Template",
3503
+ description: "Returns client-specific compatibility guidance and recommended settings.",
3504
+ mimeType: "application/json"
3505
+ },
3506
+ (uri, variables) => {
3507
+ const clientRaw = extractSingleVariable(variables.client) ?? extractSingleVariable(uri.searchParams.get("client"));
3508
+ const clientFingerprint = (clientRaw ?? "unknown").trim();
3509
+ const key = clientFingerprint.toLowerCase();
3510
+ const profile = (() => {
3511
+ if (key.includes("codex")) {
3512
+ return {
3513
+ clientFamily: "codex",
3514
+ detectedMismatches: [],
3515
+ recommendations: [
3516
+ "Prefer responseMode='delta_compact' for fast status loops.",
3517
+ "Enable pollOptions.includeTools=true when exact runtime tool names are required."
3518
+ ]
3519
+ };
3520
+ }
3521
+ if (key.includes("claude")) {
3522
+ return {
3523
+ clientFamily: "claude",
3524
+ detectedMismatches: [],
3525
+ recommendations: [
3526
+ "Use resources and resource templates for low-latency diagnostics.",
3527
+ "Use allowedTools/disallowedTools with exact runtime names.",
3528
+ "Enable strictAllowedTools when running in locked-down governance mode."
3529
+ ]
3530
+ };
3531
+ }
3532
+ if (key.includes("cursor")) {
3533
+ return {
3534
+ clientFamily: "cursor",
3535
+ detectedMismatches: [
3536
+ "Verify that resources/list and resourceTemplates/list are refreshed after list_changed notifications."
2518
3537
  ],
2519
- toolCatalogCount: deps.toolCache.getTools().length,
2520
- runtimeWarnings
2521
- },
2522
- null,
2523
- 2
2524
- ),
2525
- "application/json"
3538
+ recommendations: [
3539
+ "Prefer claude_code_check polling as source of truth for runtime state.",
3540
+ "Fallback to tool calls if resource template support is partial."
3541
+ ]
3542
+ };
3543
+ }
3544
+ return {
3545
+ clientFamily: "generic",
3546
+ detectedMismatches: [],
3547
+ recommendations: [
3548
+ "Persist nextCursor and de-duplicate by event.id.",
3549
+ "Use responseMode='delta_compact' for high-frequency polling, full mode only for diagnostics."
3550
+ ]
3551
+ };
3552
+ })();
3553
+ return asJsonResource(
3554
+ uri,
3555
+ asVersionedPayload({
3556
+ schemaVersion: resourceSchemaVersion,
3557
+ stability: "experimental",
3558
+ payload: {
3559
+ clientFingerprint,
3560
+ ...profile,
3561
+ recommendedSettings: {
3562
+ responseMode: "delta_compact",
3563
+ poll: {
3564
+ runningMs: 3e3,
3565
+ waitingPermissionMs: 1e3,
3566
+ cursorStrategy: "Persist nextCursor and de-duplicate by event.id."
3567
+ }
3568
+ }
3569
+ }
3570
+ })
2526
3571
  );
2527
3572
  }
2528
3573
  );
2529
3574
  }
2530
3575
 
2531
3576
  // src/server.ts
2532
- var SERVER_VERSION = true ? "2.2.0" : "0.0.0-dev";
3577
+ var SERVER_VERSION = true ? "2.4.0" : "0.0.0-dev";
2533
3578
  function createServerContext(serverCwd) {
2534
3579
  const sessionManager = new SessionManager();
2535
3580
  const server = new McpServer(
@@ -2550,12 +3595,17 @@ function createServerContext(serverCwd) {
2550
3595
  }
2551
3596
  );
2552
3597
  const claudeCodeToolRef = {};
3598
+ const notifyInternalToolsResourceChanged = () => {
3599
+ if (!server.isConnected()) return;
3600
+ server.sendResourceListChanged();
3601
+ };
2553
3602
  const toolCache = new ToolDiscoveryCache(void 0, (tools) => {
2554
3603
  try {
2555
3604
  claudeCodeToolRef.current?.update({ description: buildInternalToolsDescription(tools) });
2556
3605
  if (server.isConnected()) {
2557
3606
  server.sendToolListChanged();
2558
3607
  }
3608
+ notifyInternalToolsResourceChanged();
2559
3609
  } catch {
2560
3610
  }
2561
3611
  });
@@ -2639,6 +3689,7 @@ function createServerContext(serverCwd) {
2639
3689
  cwd: z.string(),
2640
3690
  allowedTools: z.array(z.string()).optional().describe("Default: []"),
2641
3691
  disallowedTools: z.array(z.string()).optional().describe("Default: []"),
3692
+ strictAllowedTools: z.boolean().optional().describe("Default: false"),
2642
3693
  maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
2643
3694
  model: z.string().optional().describe("Default: SDK"),
2644
3695
  systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
@@ -2706,12 +3757,13 @@ function createServerContext(serverCwd) {
2706
3757
  cwd: z.string().optional().describe("Working dir. Default: server cwd"),
2707
3758
  allowedTools: z.array(z.string()).optional().describe("Default: []"),
2708
3759
  disallowedTools: z.array(z.string()).optional().describe("Default: []"),
3760
+ strictAllowedTools: z.boolean().optional().describe("Default: false"),
2709
3761
  maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
2710
3762
  model: z.string().optional().describe("Default: SDK"),
2711
3763
  effort: effortOptionSchema.describe("Default: SDK"),
2712
3764
  thinking: thinkingOptionSchema.describe("Default: SDK"),
2713
3765
  systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
2714
- permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000"),
3766
+ permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000, clamped to 300000"),
2715
3767
  sessionInitTimeoutMs: z.number().int().positive().optional().describe("Deprecated, use advanced.sessionInitTimeoutMs. Default: 10000"),
2716
3768
  advanced: advancedOptionsSchema
2717
3769
  },
@@ -2774,7 +3826,7 @@ function createServerContext(serverCwd) {
2774
3826
  effort: effortOptionSchema.describe("Default: SDK"),
2775
3827
  thinking: thinkingOptionSchema.describe("Default: SDK"),
2776
3828
  sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000"),
2777
- permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000"),
3829
+ permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000, clamped to 300000"),
2778
3830
  diskResumeConfig: diskResumeConfigSchema
2779
3831
  },
2780
3832
  outputSchema: startResultSchema,
@@ -2822,10 +3874,10 @@ function createServerContext(serverCwd) {
2822
3874
  server.registerTool(
2823
3875
  "claude_code_session",
2824
3876
  {
2825
- description: "List, inspect, or cancel sessions.",
3877
+ description: "List, inspect, cancel, or interrupt sessions.",
2826
3878
  inputSchema: {
2827
3879
  action: z.enum(SESSION_ACTIONS),
2828
- sessionId: z.string().optional().describe("Required for get/cancel"),
3880
+ sessionId: z.string().optional().describe("Required for get/cancel/interrupt"),
2829
3881
  includeSensitive: z.boolean().optional().describe("Default: false")
2830
3882
  },
2831
3883
  outputSchema: sessionResultSchema,
@@ -2877,10 +3929,10 @@ function createServerContext(serverCwd) {
2877
3929
  action: z.enum(CHECK_ACTIONS),
2878
3930
  sessionId: z.string().describe("Session ID"),
2879
3931
  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"),
3932
+ responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Default: 'minimal'. Use 'delta_compact' for lightweight polling."),
3933
+ maxEvents: z.number().int().positive().optional().describe("Default: 200 (minimal), unlimited (full/delta_compact)"),
3934
+ requestId: z.string().optional().describe("Default: none"),
3935
+ decision: z.enum(["allow", "deny", "allow_for_session"]).optional().describe("Default: none"),
2884
3936
  denyMessage: z.string().optional().describe("Default: 'Permission denied by caller'"),
2885
3937
  interrupt: z.boolean().optional().describe("Default: false"),
2886
3938
  pollOptions: z.object({
@@ -2888,11 +3940,11 @@ function createServerContext(serverCwd) {
2888
3940
  includeEvents: z.boolean().optional().describe("Default: true"),
2889
3941
  includeActions: z.boolean().optional().describe("Default: true"),
2890
3942
  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"),
3943
+ includeUsage: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3944
+ includeModelUsage: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3945
+ includeStructuredOutput: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3946
+ includeTerminalEvents: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
3947
+ includeProgressEvents: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
2896
3948
  maxBytes: z.number().int().positive().optional().describe("Default: unlimited")
2897
3949
  }).optional().describe("Default: none"),
2898
3950
  permissionOptions: z.object({
@@ -2944,7 +3996,7 @@ function createServerContext(serverCwd) {
2944
3996
  }
2945
3997
  }
2946
3998
  );
2947
- registerResources(server, { toolCache });
3999
+ registerResources(server, { toolCache, version: SERVER_VERSION, sessionManager });
2948
4000
  return { server, sessionManager, toolCache };
2949
4001
  }
2950
4002