@leo000001/claude-code-mcp 2.2.0 → 2.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -1
- package/CONTRIBUTING.md +7 -0
- package/README.md +80 -66
- package/dist/index.js +1224 -187
- package/dist/index.js.map +1 -1
- package/package.json +1 -3
- package/dist/index.d.ts +0 -2
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
|
|
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 =
|
|
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:
|
|
177
|
-
hardMaxSize:
|
|
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
|
|
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
|
-
|
|
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 '${
|
|
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
|
-
|
|
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
|
-
|
|
409
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
if (
|
|
615
|
-
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
829
|
+
if (dropped > 0) {
|
|
830
|
+
remaining -= dropped;
|
|
831
|
+
}
|
|
832
|
+
return dropped;
|
|
833
|
+
};
|
|
834
|
+
const isDroppablePermissionEvent = (event) => {
|
|
835
|
+
if (event.type === "permission_result") return true;
|
|
836
|
+
if (event.type !== "permission_request") return false;
|
|
837
|
+
const requestId = event.data?.requestId;
|
|
838
|
+
if (typeof requestId !== "string") return true;
|
|
839
|
+
return isActivePermissionRequest ? !isActivePermissionRequest(requestId) : true;
|
|
840
|
+
};
|
|
841
|
+
let toDropForSoftLimit = remaining - buffer.maxSize;
|
|
842
|
+
if (toDropForSoftLimit > 0) {
|
|
843
|
+
toDropForSoftLimit -= dropOldestMatching(toDropForSoftLimit, (event) => !event.pinned);
|
|
844
|
+
}
|
|
845
|
+
if (toDropForSoftLimit > 0) {
|
|
846
|
+
toDropForSoftLimit -= dropOldestMatching(toDropForSoftLimit, isDroppablePermissionEvent);
|
|
847
|
+
}
|
|
848
|
+
let toDropForHardLimit = remaining - buffer.hardMaxSize;
|
|
849
|
+
if (toDropForHardLimit > 0) {
|
|
850
|
+
toDropForHardLimit -= dropOldestMatching(toDropForHardLimit, isDroppablePermissionEvent);
|
|
851
|
+
}
|
|
852
|
+
if (droppedIds.size > 0) {
|
|
853
|
+
buffer.events = buffer.events.filter((event) => !droppedIds.has(event.id));
|
|
629
854
|
}
|
|
630
|
-
|
|
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
|
|
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
|
-
|
|
1010
|
-
|
|
1287
|
+
const disallowedTools = normalizePolicyToolNames(sessionInfo.disallowedTools);
|
|
1288
|
+
const allowedTools = normalizePolicyToolNames(sessionInfo.allowedTools);
|
|
1289
|
+
if (normalizedToolName !== "" && disallowedTools.includes(normalizedToolName)) {
|
|
1290
|
+
return {
|
|
1291
|
+
behavior: "deny",
|
|
1292
|
+
message: `Tool '${normalizedToolName}' is disallowed by session policy.`
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
if (sessionInfo.strictAllowedTools === true && normalizedToolName !== "" && allowedTools.length > 0 && !allowedTools.includes(normalizedToolName)) {
|
|
1296
|
+
return {
|
|
1297
|
+
behavior: "deny",
|
|
1298
|
+
message: `Tool '${normalizedToolName}' is not in allowedTools under strictAllowedTools policy.`,
|
|
1299
|
+
interrupt: false
|
|
1300
|
+
};
|
|
1011
1301
|
}
|
|
1012
|
-
if (!options2.blockedPath &&
|
|
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, {
|
|
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
|
-
|
|
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
|
|
1765
|
+
const cwdProvided = input.cwd !== void 0;
|
|
1766
|
+
const cwd = cwdProvided ? input.cwd : serverCwd;
|
|
1458
1767
|
if (typeof cwd !== "string" || cwd.trim() === "") {
|
|
1459
1768
|
return {
|
|
1460
1769
|
sessionId: "",
|
|
@@ -1462,6 +1771,32 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1462
1771
|
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a non-empty string.`
|
|
1463
1772
|
};
|
|
1464
1773
|
}
|
|
1774
|
+
const normalizedCwd = normalizeWindowsPathLike(cwd);
|
|
1775
|
+
if (cwdProvided && !existsSync2(normalizedCwd)) {
|
|
1776
|
+
return {
|
|
1777
|
+
sessionId: "",
|
|
1778
|
+
status: "error",
|
|
1779
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd path does not exist: ${normalizedCwd}`
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
if (cwdProvided) {
|
|
1783
|
+
try {
|
|
1784
|
+
if (!statSync(normalizedCwd).isDirectory()) {
|
|
1785
|
+
return {
|
|
1786
|
+
sessionId: "",
|
|
1787
|
+
status: "error",
|
|
1788
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a directory: ${normalizedCwd}`
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
} catch (err) {
|
|
1792
|
+
const detail = err instanceof Error ? ` (${err.message})` : "";
|
|
1793
|
+
return {
|
|
1794
|
+
sessionId: "",
|
|
1795
|
+
status: "error",
|
|
1796
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd is not accessible: ${normalizedCwd}${detail}`
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1465
1800
|
if (!sessionManager.hasCapacityFor(1)) {
|
|
1466
1801
|
return {
|
|
1467
1802
|
sessionId: "",
|
|
@@ -1472,28 +1807,18 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1472
1807
|
const abortController = new AbortController();
|
|
1473
1808
|
const adv = input.advanced ?? {};
|
|
1474
1809
|
const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
|
|
1475
|
-
const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ??
|
|
1476
|
-
const compatWarnings = [];
|
|
1477
|
-
if (input.sessionInitTimeoutMs !== void 0) {
|
|
1478
|
-
compatWarnings.push(
|
|
1479
|
-
"Top-level sessionInitTimeoutMs for claude_code is a compatibility alias; prefer advanced.sessionInitTimeoutMs."
|
|
1480
|
-
);
|
|
1481
|
-
}
|
|
1482
|
-
if (input.sessionInitTimeoutMs !== void 0 && adv.sessionInitTimeoutMs !== void 0 && input.sessionInitTimeoutMs !== adv.sessionInitTimeoutMs) {
|
|
1483
|
-
compatWarnings.push(
|
|
1484
|
-
`Both advanced.sessionInitTimeoutMs (${adv.sessionInitTimeoutMs}) and top-level sessionInitTimeoutMs (${input.sessionInitTimeoutMs}) were provided; using advanced.sessionInitTimeoutMs.`
|
|
1485
|
-
);
|
|
1486
|
-
}
|
|
1810
|
+
const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ?? 1e4;
|
|
1487
1811
|
const flat = {
|
|
1488
|
-
cwd,
|
|
1812
|
+
cwd: normalizedCwd,
|
|
1489
1813
|
allowedTools: input.allowedTools,
|
|
1490
1814
|
disallowedTools: input.disallowedTools,
|
|
1815
|
+
strictAllowedTools: input.strictAllowedTools ?? adv.strictAllowedTools,
|
|
1491
1816
|
maxTurns: input.maxTurns,
|
|
1492
1817
|
model: input.model,
|
|
1493
1818
|
systemPrompt: input.systemPrompt,
|
|
1494
1819
|
...adv,
|
|
1495
|
-
effort: input.effort
|
|
1496
|
-
thinking: input.thinking
|
|
1820
|
+
effort: input.effort,
|
|
1821
|
+
thinking: input.thinking
|
|
1497
1822
|
};
|
|
1498
1823
|
const normalizedFlat = {
|
|
1499
1824
|
...flat,
|
|
@@ -1519,7 +1844,10 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1519
1844
|
sessionId: init.session_id,
|
|
1520
1845
|
source: normalizedFlat,
|
|
1521
1846
|
permissionMode: "default",
|
|
1522
|
-
abortController
|
|
1847
|
+
abortController,
|
|
1848
|
+
queryInterrupt: () => {
|
|
1849
|
+
handle.interrupt();
|
|
1850
|
+
}
|
|
1523
1851
|
})
|
|
1524
1852
|
);
|
|
1525
1853
|
}
|
|
@@ -1534,8 +1862,7 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1534
1862
|
sessionId,
|
|
1535
1863
|
status: "running",
|
|
1536
1864
|
pollInterval: 3e3,
|
|
1537
|
-
resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0
|
|
1538
|
-
compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0
|
|
1865
|
+
resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0
|
|
1539
1866
|
};
|
|
1540
1867
|
} catch (err) {
|
|
1541
1868
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1548,6 +1875,42 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1548
1875
|
}
|
|
1549
1876
|
|
|
1550
1877
|
// src/tools/claude-code-reply.ts
|
|
1878
|
+
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
1879
|
+
import os2 from "os";
|
|
1880
|
+
import path3 from "path";
|
|
1881
|
+
function normalizeAndAssertCwd(cwd, contextLabel) {
|
|
1882
|
+
const normalizedCwd = normalizeWindowsPathLike(cwd);
|
|
1883
|
+
const resolvedCwd = resolvePortableTmpAlias(normalizedCwd);
|
|
1884
|
+
if (!existsSync3(resolvedCwd)) {
|
|
1885
|
+
throw new Error(
|
|
1886
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} path does not exist: ${resolvedCwd}`
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
try {
|
|
1890
|
+
const stat = statSync2(resolvedCwd);
|
|
1891
|
+
if (!stat.isDirectory()) {
|
|
1892
|
+
throw new Error(
|
|
1893
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} must be a directory: ${resolvedCwd}`
|
|
1894
|
+
);
|
|
1895
|
+
}
|
|
1896
|
+
} catch (err) {
|
|
1897
|
+
if (err instanceof Error && err.message.includes("Error [")) throw err;
|
|
1898
|
+
const detail = err instanceof Error ? ` (${err.message})` : "";
|
|
1899
|
+
throw new Error(
|
|
1900
|
+
`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${contextLabel} is not accessible: ${resolvedCwd}${detail}`
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
return resolvedCwd;
|
|
1904
|
+
}
|
|
1905
|
+
function resolvePortableTmpAlias(cwd) {
|
|
1906
|
+
if (process.platform !== "win32") return cwd;
|
|
1907
|
+
const normalized = cwd.replace(/\\/g, "/");
|
|
1908
|
+
if (normalized === "/tmp") return os2.tmpdir();
|
|
1909
|
+
if (normalized.startsWith("/tmp/")) {
|
|
1910
|
+
return path3.join(os2.tmpdir(), normalized.slice("/tmp/".length));
|
|
1911
|
+
}
|
|
1912
|
+
return cwd;
|
|
1913
|
+
}
|
|
1551
1914
|
function toStartError(sessionId, err) {
|
|
1552
1915
|
const message = err instanceof Error ? err.message : String(err);
|
|
1553
1916
|
const errorText = message.includes("Error [") ? message : `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`;
|
|
@@ -1567,7 +1930,11 @@ function buildOptionsFromDiskResume(dr) {
|
|
|
1567
1930
|
if (dr.cwd === void 0 || typeof dr.cwd !== "string" || dr.cwd.trim() === "") {
|
|
1568
1931
|
throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be provided for disk resume.`);
|
|
1569
1932
|
}
|
|
1570
|
-
|
|
1933
|
+
const normalizedCwd = normalizeAndAssertCwd(dr.cwd, "disk resume cwd");
|
|
1934
|
+
return buildOptions({
|
|
1935
|
+
...dr,
|
|
1936
|
+
cwd: normalizedCwd
|
|
1937
|
+
});
|
|
1571
1938
|
}
|
|
1572
1939
|
async function executeClaudeCodeReply(input, sessionManager, toolCache, requestSignal) {
|
|
1573
1940
|
const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
|
|
@@ -1638,7 +2005,7 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1638
2005
|
})
|
|
1639
2006
|
);
|
|
1640
2007
|
try {
|
|
1641
|
-
consumeQuery({
|
|
2008
|
+
const handle = consumeQuery({
|
|
1642
2009
|
mode: "disk-resume",
|
|
1643
2010
|
sessionId: input.sessionId,
|
|
1644
2011
|
prompt: input.prompt,
|
|
@@ -1649,6 +2016,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1649
2016
|
sessionManager,
|
|
1650
2017
|
toolCache
|
|
1651
2018
|
});
|
|
2019
|
+
sessionManager.update(input.sessionId, {
|
|
2020
|
+
queryInterrupt: () => {
|
|
2021
|
+
handle.interrupt();
|
|
2022
|
+
}
|
|
2023
|
+
});
|
|
1652
2024
|
} catch (err) {
|
|
1653
2025
|
const { agentResult, errorText } = toStartError(input.sessionId, err);
|
|
1654
2026
|
sessionManager.setResult(input.sessionId, {
|
|
@@ -1661,7 +2033,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1661
2033
|
data: agentResult,
|
|
1662
2034
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1663
2035
|
});
|
|
1664
|
-
sessionManager.update(input.sessionId, {
|
|
2036
|
+
sessionManager.update(input.sessionId, {
|
|
2037
|
+
status: "error",
|
|
2038
|
+
abortController: void 0,
|
|
2039
|
+
queryInterrupt: void 0
|
|
2040
|
+
});
|
|
1665
2041
|
return { sessionId: input.sessionId, status: "error", error: errorText };
|
|
1666
2042
|
}
|
|
1667
2043
|
return {
|
|
@@ -1683,7 +2059,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1683
2059
|
data: agentResult,
|
|
1684
2060
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1685
2061
|
});
|
|
1686
|
-
sessionManager.update(input.sessionId, {
|
|
2062
|
+
sessionManager.update(input.sessionId, {
|
|
2063
|
+
status: "error",
|
|
2064
|
+
abortController: void 0,
|
|
2065
|
+
queryInterrupt: void 0
|
|
2066
|
+
});
|
|
1687
2067
|
}
|
|
1688
2068
|
return {
|
|
1689
2069
|
sessionId: input.sessionId,
|
|
@@ -1718,7 +2098,9 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1718
2098
|
};
|
|
1719
2099
|
}
|
|
1720
2100
|
const session = acquired;
|
|
2101
|
+
const normalizedCwd = normalizeAndAssertCwd(session.cwd, "session cwd");
|
|
1721
2102
|
const options = buildOptions(session);
|
|
2103
|
+
options.cwd = normalizedCwd;
|
|
1722
2104
|
if (input.forkSession) options.forkSession = true;
|
|
1723
2105
|
if (input.forkSession && !sessionManager.hasCapacityFor(1)) {
|
|
1724
2106
|
sessionManager.update(input.sessionId, { status: originalStatus, abortController: void 0 });
|
|
@@ -1757,7 +2139,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1757
2139
|
if (init.session_id === input.sessionId) return;
|
|
1758
2140
|
sessionManager.update(input.sessionId, {
|
|
1759
2141
|
status: originalStatus,
|
|
1760
|
-
abortController: void 0
|
|
2142
|
+
abortController: void 0,
|
|
2143
|
+
queryInterrupt: void 0
|
|
1761
2144
|
});
|
|
1762
2145
|
if (!sessionManager.get(init.session_id)) {
|
|
1763
2146
|
sessionManager.create(
|
|
@@ -1765,12 +2148,22 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1765
2148
|
sessionId: init.session_id,
|
|
1766
2149
|
source: { ...session, ...sourceOverrides },
|
|
1767
2150
|
permissionMode: "default",
|
|
1768
|
-
abortController
|
|
2151
|
+
abortController,
|
|
2152
|
+
queryInterrupt: () => {
|
|
2153
|
+
handle.interrupt();
|
|
2154
|
+
}
|
|
1769
2155
|
})
|
|
1770
2156
|
);
|
|
1771
2157
|
}
|
|
1772
2158
|
}
|
|
1773
2159
|
});
|
|
2160
|
+
if (!input.forkSession) {
|
|
2161
|
+
sessionManager.update(input.sessionId, {
|
|
2162
|
+
queryInterrupt: () => {
|
|
2163
|
+
handle.interrupt();
|
|
2164
|
+
}
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
1774
2167
|
const sessionId = input.forkSession ? await raceWithAbort(
|
|
1775
2168
|
handle.sdkSessionIdPromise,
|
|
1776
2169
|
requestSignal,
|
|
@@ -1795,7 +2188,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1795
2188
|
if (input.forkSession) {
|
|
1796
2189
|
sessionManager.update(input.sessionId, {
|
|
1797
2190
|
status: originalStatus,
|
|
1798
|
-
abortController: void 0
|
|
2191
|
+
abortController: void 0,
|
|
2192
|
+
queryInterrupt: void 0
|
|
1799
2193
|
});
|
|
1800
2194
|
} else {
|
|
1801
2195
|
sessionManager.setResult(input.sessionId, {
|
|
@@ -1808,7 +2202,11 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1808
2202
|
data: agentResult,
|
|
1809
2203
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1810
2204
|
});
|
|
1811
|
-
sessionManager.update(input.sessionId, {
|
|
2205
|
+
sessionManager.update(input.sessionId, {
|
|
2206
|
+
status: "error",
|
|
2207
|
+
abortController: void 0,
|
|
2208
|
+
queryInterrupt: void 0
|
|
2209
|
+
});
|
|
1812
2210
|
}
|
|
1813
2211
|
return {
|
|
1814
2212
|
sessionId: input.sessionId,
|
|
@@ -1819,14 +2217,18 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1819
2217
|
}
|
|
1820
2218
|
|
|
1821
2219
|
// src/tools/tool-discovery.ts
|
|
2220
|
+
var DEFAULT_PERMISSION_MODEL = "policy_controlled";
|
|
2221
|
+
var DEFAULT_SCHEMA_AVAILABILITY = "none";
|
|
1822
2222
|
var TOOL_CATALOG = {
|
|
1823
2223
|
Bash: {
|
|
1824
2224
|
description: "Run shell commands",
|
|
1825
|
-
category: "execute"
|
|
2225
|
+
category: "execute",
|
|
2226
|
+
notes: ["Execution may require permission approval depending on session policy."]
|
|
1826
2227
|
},
|
|
1827
2228
|
Read: {
|
|
1828
2229
|
description: "Read file contents (large files: use offset/limit or Grep)",
|
|
1829
|
-
category: "file_read"
|
|
2230
|
+
category: "file_read",
|
|
2231
|
+
notes: ["Large reads are often capped by the backend; use offset/limit when needed."]
|
|
1830
2232
|
},
|
|
1831
2233
|
Write: {
|
|
1832
2234
|
description: "Create or overwrite files",
|
|
@@ -1855,7 +2257,8 @@ var TOOL_CATALOG = {
|
|
|
1855
2257
|
WebSearch: { description: "Web search", category: "network" },
|
|
1856
2258
|
Task: {
|
|
1857
2259
|
description: "Spawn subagent (must be in allowedTools)",
|
|
1858
|
-
category: "agent"
|
|
2260
|
+
category: "agent",
|
|
2261
|
+
availabilityConditions: ["Requires Task to be visible and approved by session policy."]
|
|
1859
2262
|
},
|
|
1860
2263
|
TaskOutput: { description: "Get subagent output", category: "agent" },
|
|
1861
2264
|
TaskStop: { description: "Cancel subagent", category: "agent" },
|
|
@@ -1869,22 +2272,35 @@ var TOOL_CATALOG = {
|
|
|
1869
2272
|
},
|
|
1870
2273
|
TeamDelete: {
|
|
1871
2274
|
description: "Delete team (may need shutdown_approved first)",
|
|
1872
|
-
category: "agent"
|
|
2275
|
+
category: "agent",
|
|
2276
|
+
notes: ["Team cleanup can be asynchronous during shutdown."]
|
|
1873
2277
|
}
|
|
1874
2278
|
};
|
|
1875
2279
|
function uniq(items) {
|
|
1876
2280
|
return Array.from(new Set(items));
|
|
1877
2281
|
}
|
|
2282
|
+
function withToolDefaults(tool) {
|
|
2283
|
+
return {
|
|
2284
|
+
...tool,
|
|
2285
|
+
permissionModel: tool.permissionModel ?? DEFAULT_PERMISSION_MODEL,
|
|
2286
|
+
schemaAvailability: tool.schemaAvailability ?? DEFAULT_SCHEMA_AVAILABILITY
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
1878
2289
|
function discoverToolsFromInit(initTools) {
|
|
1879
2290
|
const names = uniq(initTools.filter((t) => typeof t === "string" && t.trim() !== ""));
|
|
1880
|
-
return names.map(
|
|
1881
|
-
name
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
2291
|
+
return names.map(
|
|
2292
|
+
(name) => withToolDefaults({
|
|
2293
|
+
name,
|
|
2294
|
+
description: TOOL_CATALOG[name]?.description ?? name,
|
|
2295
|
+
category: TOOL_CATALOG[name]?.category,
|
|
2296
|
+
availabilityConditions: TOOL_CATALOG[name]?.availabilityConditions,
|
|
2297
|
+
platformConstraints: TOOL_CATALOG[name]?.platformConstraints,
|
|
2298
|
+
notes: TOOL_CATALOG[name]?.notes
|
|
2299
|
+
})
|
|
2300
|
+
);
|
|
1885
2301
|
}
|
|
1886
2302
|
function defaultCatalogTools() {
|
|
1887
|
-
return Object.keys(TOOL_CATALOG).sort((a, b) => a.localeCompare(b)).map((name) => ({ name, ...TOOL_CATALOG[name] }));
|
|
2303
|
+
return Object.keys(TOOL_CATALOG).sort((a, b) => a.localeCompare(b)).map((name) => withToolDefaults({ name, ...TOOL_CATALOG[name] }));
|
|
1888
2304
|
}
|
|
1889
2305
|
var ToolDiscoveryCache = class {
|
|
1890
2306
|
cached;
|
|
@@ -1953,7 +2369,7 @@ function pollIntervalForStatus(status) {
|
|
|
1953
2369
|
return void 0;
|
|
1954
2370
|
}
|
|
1955
2371
|
function toPermissionResult(params) {
|
|
1956
|
-
if (params.decision === "allow") {
|
|
2372
|
+
if (params.decision === "allow" || params.decision === "allow_for_session") {
|
|
1957
2373
|
return {
|
|
1958
2374
|
behavior: "allow",
|
|
1959
2375
|
updatedInput: params.updatedInput,
|
|
@@ -1966,6 +2382,19 @@ function toPermissionResult(params) {
|
|
|
1966
2382
|
interrupt: params.interrupt
|
|
1967
2383
|
};
|
|
1968
2384
|
}
|
|
2385
|
+
function appendAllowForSessionUpdate(updates, toolName) {
|
|
2386
|
+
const normalizedToolName = toolName?.trim();
|
|
2387
|
+
if (!normalizedToolName) return updates;
|
|
2388
|
+
return [
|
|
2389
|
+
...updates ?? [],
|
|
2390
|
+
{
|
|
2391
|
+
type: "addRules",
|
|
2392
|
+
behavior: "allow",
|
|
2393
|
+
destination: "session",
|
|
2394
|
+
rules: [{ toolName: normalizedToolName }]
|
|
2395
|
+
}
|
|
2396
|
+
];
|
|
2397
|
+
}
|
|
1969
2398
|
function slimAssistantData(data) {
|
|
1970
2399
|
if (!data || typeof data !== "object") return data;
|
|
1971
2400
|
const d = data;
|
|
@@ -2026,10 +2455,23 @@ function detectPathCompatibilityWarnings(session) {
|
|
|
2026
2455
|
`cwd '${cwd}' looks like a Windows UNC path on ${process.platform}. Confirm MCP client/server run on the same platform.`
|
|
2027
2456
|
);
|
|
2028
2457
|
}
|
|
2458
|
+
if (process.platform === "win32" && Array.isArray(session.additionalDirectories)) {
|
|
2459
|
+
for (const dir of session.additionalDirectories) {
|
|
2460
|
+
if (typeof dir === "string" && dir.startsWith("/") && !dir.startsWith("//")) {
|
|
2461
|
+
warnings.push(
|
|
2462
|
+
`additionalDirectories contains POSIX-style path '${dir}' on Windows. Consider using a Windows path to avoid path compatibility issues.`
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2029
2467
|
return warnings;
|
|
2030
2468
|
}
|
|
2031
2469
|
function isPosixHomePath(value) {
|
|
2032
|
-
return /^\/home\/[
|
|
2470
|
+
return /^\/home\/[^/\s]+(?:\/|$)/.test(value);
|
|
2471
|
+
}
|
|
2472
|
+
function extractPosixHomePath(value) {
|
|
2473
|
+
const match = value.match(/\/home\/[^/\s"'`]+(?:\/[^\s"'`]*)?/);
|
|
2474
|
+
return match?.[0];
|
|
2033
2475
|
}
|
|
2034
2476
|
function detectPendingPermissionPathWarnings(pending, session) {
|
|
2035
2477
|
if (process.platform !== "win32" || pending.length === 0) return [];
|
|
@@ -2041,6 +2483,25 @@ function detectPendingPermissionPathWarnings(pending, session) {
|
|
|
2041
2483
|
if (typeof req.blockedPath === "string") candidates.push(req.blockedPath);
|
|
2042
2484
|
const filePath = req.input.file_path;
|
|
2043
2485
|
if (typeof filePath === "string") candidates.push(filePath);
|
|
2486
|
+
const pathFields = [
|
|
2487
|
+
"path",
|
|
2488
|
+
"directory",
|
|
2489
|
+
"folder",
|
|
2490
|
+
"cwd",
|
|
2491
|
+
"dest",
|
|
2492
|
+
"destination",
|
|
2493
|
+
"source",
|
|
2494
|
+
"target"
|
|
2495
|
+
];
|
|
2496
|
+
for (const field of pathFields) {
|
|
2497
|
+
const val = req.input[field];
|
|
2498
|
+
if (typeof val === "string") candidates.push(val);
|
|
2499
|
+
}
|
|
2500
|
+
const command = req.input.command;
|
|
2501
|
+
if (typeof command === "string") {
|
|
2502
|
+
const embeddedPath = extractPosixHomePath(command);
|
|
2503
|
+
if (embeddedPath) candidates.push(embeddedPath);
|
|
2504
|
+
}
|
|
2044
2505
|
const badPath = candidates.find((p) => isPosixHomePath(p));
|
|
2045
2506
|
if (!badPath) continue;
|
|
2046
2507
|
warnings.push(
|
|
@@ -2053,8 +2514,14 @@ function computeToolValidation(session, initTools) {
|
|
|
2053
2514
|
if (!session) return { summary: void 0, warnings: [] };
|
|
2054
2515
|
const allowedTools = uniqSorted(session.allowedTools);
|
|
2055
2516
|
const disallowedTools = uniqSorted(session.disallowedTools);
|
|
2517
|
+
const warnings = [];
|
|
2518
|
+
if (allowedTools.length > 0 && session.strictAllowedTools !== true) {
|
|
2519
|
+
warnings.push(
|
|
2520
|
+
"allowedTools currently acts as pre-approval only. Set strictAllowedTools=true to enforce a strict allowlist."
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2056
2523
|
if (allowedTools.length === 0 && disallowedTools.length === 0) {
|
|
2057
|
-
return { summary: void 0, warnings
|
|
2524
|
+
return { summary: void 0, warnings };
|
|
2058
2525
|
}
|
|
2059
2526
|
if (!Array.isArray(initTools) || initTools.length === 0) {
|
|
2060
2527
|
return {
|
|
@@ -2064,6 +2531,7 @@ function computeToolValidation(session, initTools) {
|
|
|
2064
2531
|
unknownDisallowedTools: []
|
|
2065
2532
|
},
|
|
2066
2533
|
warnings: [
|
|
2534
|
+
...warnings,
|
|
2067
2535
|
"Runtime tool list is not available yet; unknown allowedTools/disallowedTools names cannot be validated until system/init tools arrive."
|
|
2068
2536
|
]
|
|
2069
2537
|
};
|
|
@@ -2073,7 +2541,6 @@ function computeToolValidation(session, initTools) {
|
|
|
2073
2541
|
);
|
|
2074
2542
|
const unknownAllowedTools = allowedTools.filter((name) => !runtime.has(name));
|
|
2075
2543
|
const unknownDisallowedTools = disallowedTools.filter((name) => !runtime.has(name));
|
|
2076
|
-
const warnings = [];
|
|
2077
2544
|
if (unknownAllowedTools.length > 0) {
|
|
2078
2545
|
warnings.push(
|
|
2079
2546
|
`Unknown allowedTools (not present in runtime tools): ${unknownAllowedTools.join(", ")}.`
|
|
@@ -2114,11 +2581,12 @@ function capEventsByBytes(events, maxBytes) {
|
|
|
2114
2581
|
}
|
|
2115
2582
|
function buildResult(sessionManager, toolCache, input) {
|
|
2116
2583
|
const responseMode = input.responseMode ?? "minimal";
|
|
2584
|
+
const compactMode = responseMode === "delta_compact";
|
|
2117
2585
|
const po = input.pollOptions ?? {};
|
|
2118
2586
|
const includeTools = po.includeTools;
|
|
2119
|
-
const includeEvents = po.includeEvents ??
|
|
2587
|
+
const includeEvents = po.includeEvents ?? !compactMode;
|
|
2120
2588
|
const includeActions = po.includeActions ?? true;
|
|
2121
|
-
const includeResult = po.includeResult ??
|
|
2589
|
+
const includeResult = po.includeResult ?? !compactMode;
|
|
2122
2590
|
const includeUsage = po.includeUsage ?? responseMode === "full";
|
|
2123
2591
|
const includeModelUsage = po.includeModelUsage ?? responseMode === "full";
|
|
2124
2592
|
const includeStructuredOutput = po.includeStructuredOutput ?? responseMode === "full";
|
|
@@ -2174,7 +2642,7 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
2174
2642
|
includeUsage,
|
|
2175
2643
|
includeModelUsage,
|
|
2176
2644
|
includeStructuredOutput,
|
|
2177
|
-
slim: responseMode === "minimal"
|
|
2645
|
+
slim: responseMode === "minimal" || responseMode === "delta_compact"
|
|
2178
2646
|
});
|
|
2179
2647
|
const cappedEvents = capEventsByBytes(shapedEvents, maxBytes);
|
|
2180
2648
|
if (cappedEvents.truncated) {
|
|
@@ -2219,7 +2687,7 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
2219
2687
|
includeUsage,
|
|
2220
2688
|
includeModelUsage,
|
|
2221
2689
|
includeStructuredOutput,
|
|
2222
|
-
slim: responseMode === "minimal"
|
|
2690
|
+
slim: responseMode === "minimal" || responseMode === "delta_compact"
|
|
2223
2691
|
}) : void 0,
|
|
2224
2692
|
cancelledAt: session?.cancelledAt,
|
|
2225
2693
|
cancelledReason: session?.cancelledReason,
|
|
@@ -2286,20 +2754,25 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache, requestSignal)
|
|
|
2286
2754
|
isError: true
|
|
2287
2755
|
};
|
|
2288
2756
|
}
|
|
2289
|
-
if (input.decision !== "allow" && input.decision !== "deny") {
|
|
2757
|
+
if (input.decision !== "allow" && input.decision !== "deny" && input.decision !== "allow_for_session") {
|
|
2290
2758
|
return {
|
|
2291
2759
|
sessionId: input.sessionId,
|
|
2292
|
-
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision must be 'allow' or '
|
|
2760
|
+
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision must be 'allow', 'deny', or 'allow_for_session'.`,
|
|
2293
2761
|
isError: true
|
|
2294
2762
|
};
|
|
2295
2763
|
}
|
|
2764
|
+
const pendingRequest = input.decision === "allow_for_session" ? sessionManager.getPendingPermission(input.sessionId, input.requestId) : void 0;
|
|
2765
|
+
const updatedPermissions = input.decision === "allow_for_session" ? appendAllowForSessionUpdate(
|
|
2766
|
+
input.permissionOptions?.updatedPermissions,
|
|
2767
|
+
pendingRequest?.toolName
|
|
2768
|
+
) : input.permissionOptions?.updatedPermissions;
|
|
2296
2769
|
const ok = sessionManager.finishRequest(
|
|
2297
2770
|
input.sessionId,
|
|
2298
2771
|
input.requestId,
|
|
2299
2772
|
toPermissionResult({
|
|
2300
2773
|
decision: input.decision,
|
|
2301
2774
|
updatedInput: input.permissionOptions?.updatedInput,
|
|
2302
|
-
updatedPermissions
|
|
2775
|
+
updatedPermissions,
|
|
2303
2776
|
denyMessage: input.denyMessage,
|
|
2304
2777
|
interrupt: input.interrupt
|
|
2305
2778
|
}),
|
|
@@ -2312,10 +2785,38 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache, requestSignal)
|
|
|
2312
2785
|
isError: true
|
|
2313
2786
|
};
|
|
2314
2787
|
}
|
|
2788
|
+
if (input.decision === "allow_for_session" && pendingRequest?.toolName) {
|
|
2789
|
+
sessionManager.allowToolForSession(input.sessionId, pendingRequest.toolName);
|
|
2790
|
+
}
|
|
2315
2791
|
return buildResult(sessionManager, toolCache, input);
|
|
2316
2792
|
}
|
|
2317
2793
|
|
|
2318
2794
|
// src/tools/claude-code-session.ts
|
|
2795
|
+
var ALWAYS_REDACTED_FIELDS = [
|
|
2796
|
+
"env",
|
|
2797
|
+
"mcpServers",
|
|
2798
|
+
"sandbox",
|
|
2799
|
+
"debugFile",
|
|
2800
|
+
"pathToClaudeCodeExecutable"
|
|
2801
|
+
];
|
|
2802
|
+
var CONDITIONAL_REDACTED_FIELDS = [
|
|
2803
|
+
"cwd",
|
|
2804
|
+
"systemPrompt",
|
|
2805
|
+
"agents",
|
|
2806
|
+
"additionalDirectories"
|
|
2807
|
+
];
|
|
2808
|
+
function buildRedactions(includeSensitive) {
|
|
2809
|
+
const redactions = [];
|
|
2810
|
+
for (const field of ALWAYS_REDACTED_FIELDS) {
|
|
2811
|
+
redactions?.push({ field, reason: "secret_or_internal" });
|
|
2812
|
+
}
|
|
2813
|
+
if (!includeSensitive) {
|
|
2814
|
+
for (const field of CONDITIONAL_REDACTED_FIELDS) {
|
|
2815
|
+
redactions?.push({ field, reason: "sensitive_by_default" });
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
return redactions;
|
|
2819
|
+
}
|
|
2319
2820
|
function executeClaudeCodeSession(input, sessionManager, requestSignal) {
|
|
2320
2821
|
if (requestSignal?.aborted) {
|
|
2321
2822
|
return {
|
|
@@ -2324,7 +2825,23 @@ function executeClaudeCodeSession(input, sessionManager, requestSignal) {
|
|
|
2324
2825
|
isError: true
|
|
2325
2826
|
};
|
|
2326
2827
|
}
|
|
2327
|
-
const toSessionJson = (s) =>
|
|
2828
|
+
const toSessionJson = (s) => {
|
|
2829
|
+
const base = input.includeSensitive ? sessionManager.toSensitiveJSON(s) : sessionManager.toPublicJSON(s);
|
|
2830
|
+
const stored = sessionManager.getResult(s.sessionId);
|
|
2831
|
+
const lastError = stored?.type === "error" ? stored.result.result : void 0;
|
|
2832
|
+
const lastErrorAt = stored?.type === "error" ? stored.createdAt : void 0;
|
|
2833
|
+
return {
|
|
2834
|
+
...base,
|
|
2835
|
+
pendingPermissionCount: sessionManager.getPendingPermissionCount(s.sessionId),
|
|
2836
|
+
eventCount: sessionManager.getEventCount(s.sessionId),
|
|
2837
|
+
currentCursor: sessionManager.getCurrentCursor(s.sessionId),
|
|
2838
|
+
lastEventId: sessionManager.getLastEventId(s.sessionId),
|
|
2839
|
+
ttlMs: sessionManager.getRemainingTtlMs(s.sessionId),
|
|
2840
|
+
lastError,
|
|
2841
|
+
lastErrorAt,
|
|
2842
|
+
redactions: buildRedactions(input.includeSensitive)
|
|
2843
|
+
};
|
|
2844
|
+
};
|
|
2328
2845
|
switch (input.action) {
|
|
2329
2846
|
case "list": {
|
|
2330
2847
|
const sessions = sessionManager.list().map((s) => toSessionJson(s));
|
|
@@ -2381,23 +2898,65 @@ function executeClaudeCodeSession(input, sessionManager, requestSignal) {
|
|
|
2381
2898
|
message: `Session '${input.sessionId}' cancelled.`
|
|
2382
2899
|
};
|
|
2383
2900
|
}
|
|
2901
|
+
case "interrupt": {
|
|
2902
|
+
if (!input.sessionId) {
|
|
2903
|
+
return {
|
|
2904
|
+
sessions: [],
|
|
2905
|
+
message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: sessionId is required for 'interrupt' action.`,
|
|
2906
|
+
isError: true
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2909
|
+
const interrupted = sessionManager.interrupt(input.sessionId, {
|
|
2910
|
+
reason: "Interrupted by caller",
|
|
2911
|
+
source: "claude_code_session"
|
|
2912
|
+
});
|
|
2913
|
+
if (!interrupted) {
|
|
2914
|
+
const session = sessionManager.get(input.sessionId);
|
|
2915
|
+
if (!session) {
|
|
2916
|
+
return {
|
|
2917
|
+
sessions: [],
|
|
2918
|
+
message: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found.`,
|
|
2919
|
+
isError: true
|
|
2920
|
+
};
|
|
2921
|
+
}
|
|
2922
|
+
return {
|
|
2923
|
+
sessions: [toSessionJson(session)],
|
|
2924
|
+
message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Session '${input.sessionId}' is not running (status: ${session.status}).`,
|
|
2925
|
+
isError: true
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
const updated = sessionManager.get(input.sessionId);
|
|
2929
|
+
return {
|
|
2930
|
+
sessions: updated ? [toSessionJson(updated)] : [],
|
|
2931
|
+
message: `Session '${input.sessionId}' interrupted.`
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2384
2934
|
default:
|
|
2385
2935
|
return {
|
|
2386
2936
|
sessions: [],
|
|
2387
|
-
message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown action '${input.action}'. Use 'list', 'get', or '
|
|
2937
|
+
message: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown action '${input.action}'. Use 'list', 'get', 'cancel', or 'interrupt'.`,
|
|
2388
2938
|
isError: true
|
|
2389
2939
|
};
|
|
2390
2940
|
}
|
|
2391
2941
|
}
|
|
2392
2942
|
|
|
2393
2943
|
// src/resources/register-resources.ts
|
|
2944
|
+
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2945
|
+
import { createHash } from "crypto";
|
|
2394
2946
|
var RESOURCE_SCHEME = "claude-code-mcp";
|
|
2395
2947
|
var RESOURCE_URIS = {
|
|
2396
2948
|
serverInfo: `${RESOURCE_SCHEME}:///server-info`,
|
|
2397
2949
|
internalTools: `${RESOURCE_SCHEME}:///internal-tools`,
|
|
2398
2950
|
gotchas: `${RESOURCE_SCHEME}:///gotchas`,
|
|
2951
|
+
quickstart: `${RESOURCE_SCHEME}:///quickstart`,
|
|
2952
|
+
errors: `${RESOURCE_SCHEME}:///errors`,
|
|
2399
2953
|
compatReport: `${RESOURCE_SCHEME}:///compat-report`
|
|
2400
2954
|
};
|
|
2955
|
+
var RESOURCE_TEMPLATES = {
|
|
2956
|
+
sessionById: `${RESOURCE_SCHEME}:///session/{sessionId}`,
|
|
2957
|
+
runtimeTools: `${RESOURCE_SCHEME}:///tools/runtime{?sessionId}`,
|
|
2958
|
+
compatDiff: `${RESOURCE_SCHEME}:///compat/diff{?client}`
|
|
2959
|
+
};
|
|
2401
2960
|
function asTextResource(uri, text, mimeType) {
|
|
2402
2961
|
return {
|
|
2403
2962
|
contents: [
|
|
@@ -2409,7 +2968,136 @@ function asTextResource(uri, text, mimeType) {
|
|
|
2409
2968
|
]
|
|
2410
2969
|
};
|
|
2411
2970
|
}
|
|
2971
|
+
function asJsonResource(uri, value) {
|
|
2972
|
+
return asTextResource(uri, JSON.stringify(value, null, 2), "application/json");
|
|
2973
|
+
}
|
|
2974
|
+
function serializeLimit(limit) {
|
|
2975
|
+
return Number.isFinite(limit) ? limit : "unlimited";
|
|
2976
|
+
}
|
|
2977
|
+
function computeEtag(value) {
|
|
2978
|
+
return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
|
|
2979
|
+
}
|
|
2980
|
+
function extractSingleVariable(value) {
|
|
2981
|
+
if (typeof value === "string" && value.trim() !== "") return value;
|
|
2982
|
+
if (Array.isArray(value) && typeof value[0] === "string" && value[0].trim() !== "")
|
|
2983
|
+
return value[0];
|
|
2984
|
+
return void 0;
|
|
2985
|
+
}
|
|
2986
|
+
function buildSessionRedactions(includeSensitive) {
|
|
2987
|
+
const redactions = [
|
|
2988
|
+
{ field: "env", reason: "secret_or_internal" },
|
|
2989
|
+
{ field: "mcpServers", reason: "secret_or_internal" },
|
|
2990
|
+
{ field: "sandbox", reason: "secret_or_internal" },
|
|
2991
|
+
{ field: "debugFile", reason: "secret_or_internal" },
|
|
2992
|
+
{ field: "pathToClaudeCodeExecutable", reason: "secret_or_internal" }
|
|
2993
|
+
];
|
|
2994
|
+
if (!includeSensitive) {
|
|
2995
|
+
redactions.push(
|
|
2996
|
+
{ field: "cwd", reason: "sensitive_by_default" },
|
|
2997
|
+
{ field: "systemPrompt", reason: "sensitive_by_default" },
|
|
2998
|
+
{ field: "agents", reason: "sensitive_by_default" },
|
|
2999
|
+
{ field: "additionalDirectories", reason: "sensitive_by_default" }
|
|
3000
|
+
);
|
|
3001
|
+
}
|
|
3002
|
+
return redactions;
|
|
3003
|
+
}
|
|
3004
|
+
function buildGotchasEntries() {
|
|
3005
|
+
return [
|
|
3006
|
+
{
|
|
3007
|
+
id: "permission-timeout",
|
|
3008
|
+
title: "Permission requests auto-deny on timeout",
|
|
3009
|
+
severity: "high",
|
|
3010
|
+
appliesTo: ["claude_code_check.actions", "permission workflow"],
|
|
3011
|
+
symptom: "Session waits for approval, then tool call is denied without explicit caller decision.",
|
|
3012
|
+
detection: "Observe actions[].expiresAt/remainingMs and permission_result with source=timeout.",
|
|
3013
|
+
remedy: "Poll more frequently and respond before timeout; increase permissionRequestTimeoutMs if needed.",
|
|
3014
|
+
example: "Default timeout is 60000ms and server-clamped to 300000ms."
|
|
3015
|
+
},
|
|
3016
|
+
{
|
|
3017
|
+
id: "read-size-cap",
|
|
3018
|
+
title: "Read may cap response size",
|
|
3019
|
+
severity: "medium",
|
|
3020
|
+
appliesTo: ["Read tool"],
|
|
3021
|
+
symptom: "Large file reads are truncated or fail.",
|
|
3022
|
+
detection: "Read responses are incomplete for large files.",
|
|
3023
|
+
remedy: "Use offset/limit paging or chunk with Grep."
|
|
3024
|
+
},
|
|
3025
|
+
{
|
|
3026
|
+
id: "edit-replace-all",
|
|
3027
|
+
title: "Edit replace_all uses substring matching",
|
|
3028
|
+
severity: "medium",
|
|
3029
|
+
appliesTo: ["Edit tool"],
|
|
3030
|
+
symptom: "replace_all=true fails unexpectedly.",
|
|
3031
|
+
detection: "Tool returns error when no exact substring match is found.",
|
|
3032
|
+
remedy: "Validate exact match text first; prefer smaller targeted replacements."
|
|
3033
|
+
},
|
|
3034
|
+
{
|
|
3035
|
+
id: "windows-path-normalization",
|
|
3036
|
+
title: "Windows path normalization applies to common MSYS-style paths",
|
|
3037
|
+
severity: "medium",
|
|
3038
|
+
appliesTo: ["Windows", "cwd", "additionalDirectories", "file_path"],
|
|
3039
|
+
symptom: "Permission/path behavior differs from raw input path text.",
|
|
3040
|
+
detection: "Compare submitted path with effective path in logs/permission prompts.",
|
|
3041
|
+
remedy: "Use absolute native Windows paths when possible; avoid relying on implicit conversion behavior.",
|
|
3042
|
+
example: "/d/... /mnt/c/... /cygdrive/c/... //server/share/... are normalized on Windows."
|
|
3043
|
+
},
|
|
3044
|
+
{
|
|
3045
|
+
id: "team-delete-async-cleanup",
|
|
3046
|
+
title: "TeamDelete cleanup can be asynchronous",
|
|
3047
|
+
severity: "low",
|
|
3048
|
+
appliesTo: ["TeamDelete"],
|
|
3049
|
+
symptom: "Immediate follow-up calls still see active members.",
|
|
3050
|
+
detection: "TeamDelete reports shutdown_approved or active member transitions.",
|
|
3051
|
+
remedy: "Retry after short delay; wait for shutdown state to settle."
|
|
3052
|
+
},
|
|
3053
|
+
{
|
|
3054
|
+
id: "skills-late-availability",
|
|
3055
|
+
title: "Skills may become available later in a session",
|
|
3056
|
+
severity: "low",
|
|
3057
|
+
appliesTo: ["Skills"],
|
|
3058
|
+
symptom: "Early calls show unknown skill/tool errors.",
|
|
3059
|
+
detection: "Later calls in same session succeed without config changes.",
|
|
3060
|
+
remedy: "Retry after initialization events are complete."
|
|
3061
|
+
},
|
|
3062
|
+
{
|
|
3063
|
+
id: "tool-count-sources-differ",
|
|
3064
|
+
title: "toolCatalogCount and availableTools have different sources",
|
|
3065
|
+
severity: "medium",
|
|
3066
|
+
appliesTo: ["internal-tools", "claude_code_check.availableTools", "compat-report"],
|
|
3067
|
+
symptom: "Tool counts appear inconsistent (for example 15 vs 28).",
|
|
3068
|
+
detection: "Compare compat-report.toolCounts and session-level availableTools.",
|
|
3069
|
+
remedy: "Treat catalog count as server-known baseline and availableTools as session runtime view from system/init.tools."
|
|
3070
|
+
},
|
|
3071
|
+
{
|
|
3072
|
+
id: "available-tools-not-exhaustive",
|
|
3073
|
+
title: "availableTools may omit internal features",
|
|
3074
|
+
severity: "low",
|
|
3075
|
+
appliesTo: ["claude_code_check.availableTools"],
|
|
3076
|
+
symptom: "A known feature is callable but not listed in availableTools.",
|
|
3077
|
+
detection: "Feature works while missing from availableTools list.",
|
|
3078
|
+
remedy: "Use availableTools as runtime hint, not exhaustive capability proof.",
|
|
3079
|
+
example: "Some internal features (e.g. ToolSearch) may not appear."
|
|
3080
|
+
}
|
|
3081
|
+
];
|
|
3082
|
+
}
|
|
3083
|
+
function asVersionedPayload(params) {
|
|
3084
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3085
|
+
return {
|
|
3086
|
+
...params.payload,
|
|
3087
|
+
schemaVersion: params.schemaVersion,
|
|
3088
|
+
updatedAt,
|
|
3089
|
+
etag: computeEtag(params.payload),
|
|
3090
|
+
stability: params.stability
|
|
3091
|
+
};
|
|
3092
|
+
}
|
|
2412
3093
|
function registerResources(server, deps) {
|
|
3094
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3095
|
+
const resourceSchemaVersion = "1.3";
|
|
3096
|
+
const mcpProtocolVersion = "2025-03-26";
|
|
3097
|
+
const gotchasEntries = buildGotchasEntries();
|
|
3098
|
+
const catalogToolNames = new Set(defaultCatalogTools().map((tool) => tool.name));
|
|
3099
|
+
const staticResourceUris = Object.values(RESOURCE_URIS);
|
|
3100
|
+
const templateUris = Object.values(RESOURCE_TEMPLATES);
|
|
2413
3101
|
const serverInfoUri = new URL(RESOURCE_URIS.serverInfo);
|
|
2414
3102
|
server.registerResource(
|
|
2415
3103
|
"server_info",
|
|
@@ -2419,21 +3107,50 @@ function registerResources(server, deps) {
|
|
|
2419
3107
|
description: "Server metadata (version/platform/runtime).",
|
|
2420
3108
|
mimeType: "application/json"
|
|
2421
3109
|
},
|
|
2422
|
-
() =>
|
|
3110
|
+
() => asJsonResource(
|
|
2423
3111
|
serverInfoUri,
|
|
2424
|
-
|
|
2425
|
-
{
|
|
3112
|
+
(() => {
|
|
3113
|
+
const base = {
|
|
2426
3114
|
name: "claude-code-mcp",
|
|
3115
|
+
version: deps.version,
|
|
2427
3116
|
node: process.version,
|
|
2428
3117
|
platform: process.platform,
|
|
2429
3118
|
arch: process.arch,
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
3119
|
+
mcpProtocolVersion,
|
|
3120
|
+
startedAt,
|
|
3121
|
+
uptimeSec: Math.floor(process.uptime()),
|
|
3122
|
+
resources: staticResourceUris,
|
|
3123
|
+
resourceTemplates: templateUris,
|
|
3124
|
+
toolCatalogCount: deps.toolCache.getTools().length,
|
|
3125
|
+
capabilities: {
|
|
3126
|
+
resources: true,
|
|
3127
|
+
toolsListChanged: true,
|
|
3128
|
+
resourcesListChanged: true,
|
|
3129
|
+
prompts: false,
|
|
3130
|
+
completions: false
|
|
3131
|
+
},
|
|
3132
|
+
limits: {
|
|
3133
|
+
maxSessions: serializeLimit(deps.sessionManager.getMaxSessions()),
|
|
3134
|
+
maxPendingPermissionsPerSession: serializeLimit(
|
|
3135
|
+
deps.sessionManager.getMaxPendingPermissionsPerSession()
|
|
3136
|
+
),
|
|
3137
|
+
eventBuffer: deps.sessionManager.getEventBufferConfig(),
|
|
3138
|
+
pollDefaults: {
|
|
3139
|
+
runningMs: 3e3,
|
|
3140
|
+
waitingPermissionMs: 1e3
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3144
|
+
if (typeof process.env.CLAUDE_CODE_MCP_BUILD_COMMIT === "string") {
|
|
3145
|
+
const commit = process.env.CLAUDE_CODE_MCP_BUILD_COMMIT.trim();
|
|
3146
|
+
if (commit !== "") base.buildCommit = commit;
|
|
3147
|
+
}
|
|
3148
|
+
return asVersionedPayload({
|
|
3149
|
+
schemaVersion: resourceSchemaVersion,
|
|
3150
|
+
stability: "stable",
|
|
3151
|
+
payload: base
|
|
3152
|
+
});
|
|
3153
|
+
})()
|
|
2437
3154
|
)
|
|
2438
3155
|
);
|
|
2439
3156
|
const toolsUri = new URL(RESOURCE_URIS.internalTools);
|
|
@@ -2445,10 +3162,19 @@ function registerResources(server, deps) {
|
|
|
2445
3162
|
description: "Claude Code internal tool catalog (runtime-aware).",
|
|
2446
3163
|
mimeType: "application/json"
|
|
2447
3164
|
},
|
|
2448
|
-
() =>
|
|
3165
|
+
() => asJsonResource(
|
|
2449
3166
|
toolsUri,
|
|
2450
|
-
|
|
2451
|
-
|
|
3167
|
+
(() => {
|
|
3168
|
+
const base = {
|
|
3169
|
+
tools: deps.toolCache.getTools(),
|
|
3170
|
+
toolCatalogCount: deps.toolCache.getTools().length
|
|
3171
|
+
};
|
|
3172
|
+
return asVersionedPayload({
|
|
3173
|
+
schemaVersion: resourceSchemaVersion,
|
|
3174
|
+
stability: "stable",
|
|
3175
|
+
payload: base
|
|
3176
|
+
});
|
|
3177
|
+
})()
|
|
2452
3178
|
)
|
|
2453
3179
|
);
|
|
2454
3180
|
const gotchasUri = new URL(RESOURCE_URIS.gotchas);
|
|
@@ -2465,19 +3191,75 @@ function registerResources(server, deps) {
|
|
|
2465
3191
|
[
|
|
2466
3192
|
"# claude-code-mcp: gotchas",
|
|
2467
3193
|
"",
|
|
2468
|
-
|
|
2469
|
-
"- `Read` has a per-call size cap in practice (often ~256KB); for large files use `offset`/`limit` or chunk with `Grep`.",
|
|
2470
|
-
"- `Edit` with `replace_all=true` is substring replacement; if no match is found the tool returns an error.",
|
|
2471
|
-
"- On Windows, this server normalizes common MSYS-style paths (e.g. `/d/...`, `/mnt/c/...`, `/cygdrive/c/...`, `//server/share/...`) for `cwd`, `additionalDirectories`, and tool inputs that include `file_path`.",
|
|
2472
|
-
"- `TeamDelete` may require members to reach `shutdown_approved`; cleanup can be asynchronous during shutdown.",
|
|
2473
|
-
'- Skills may become available later in the same session (early calls may show "Unknown").',
|
|
2474
|
-
"- Some internal features (e.g. ToolSearch) may not appear in `availableTools` because it is derived from SDK `system/init.tools`.",
|
|
3194
|
+
...gotchasEntries.map((entry) => `- ${entry.title}. ${entry.remedy}`),
|
|
2475
3195
|
""
|
|
2476
3196
|
].join("\n"),
|
|
2477
3197
|
"text/markdown"
|
|
2478
3198
|
)
|
|
2479
3199
|
);
|
|
2480
3200
|
const compatReportUri = new URL(RESOURCE_URIS.compatReport);
|
|
3201
|
+
const quickstartUri = new URL(RESOURCE_URIS.quickstart);
|
|
3202
|
+
server.registerResource(
|
|
3203
|
+
"quickstart",
|
|
3204
|
+
quickstartUri.toString(),
|
|
3205
|
+
{
|
|
3206
|
+
title: "Quickstart",
|
|
3207
|
+
description: "Minimal async polling flow for claude_code / claude_code_check.",
|
|
3208
|
+
mimeType: "text/markdown"
|
|
3209
|
+
},
|
|
3210
|
+
() => asTextResource(
|
|
3211
|
+
quickstartUri,
|
|
3212
|
+
[
|
|
3213
|
+
"# claude-code-mcp quickstart",
|
|
3214
|
+
"",
|
|
3215
|
+
"1. Call `claude_code` with `{ prompt }` and keep `sessionId`.",
|
|
3216
|
+
"2. Poll with `claude_code_check(action='poll')` using `nextCursor`.",
|
|
3217
|
+
"3. If actions are returned, respond with `claude_code_check(action='respond_permission')`.",
|
|
3218
|
+
"4. Continue polling until status becomes `idle` / `error` / `cancelled`.",
|
|
3219
|
+
"",
|
|
3220
|
+
"Notes:",
|
|
3221
|
+
"- `respond_user_input` is not supported on this backend.",
|
|
3222
|
+
"- `allowedTools` is pre-approval by default; set `strictAllowedTools=true` for strict allowlist behavior.",
|
|
3223
|
+
"- Prefer `responseMode='delta_compact'` for high-frequency polling."
|
|
3224
|
+
].join("\n"),
|
|
3225
|
+
"text/markdown"
|
|
3226
|
+
)
|
|
3227
|
+
);
|
|
3228
|
+
const errorsUri = new URL(RESOURCE_URIS.errors);
|
|
3229
|
+
server.registerResource(
|
|
3230
|
+
"errors",
|
|
3231
|
+
errorsUri.toString(),
|
|
3232
|
+
{
|
|
3233
|
+
title: "Errors",
|
|
3234
|
+
description: "Structured error codes and remediation hints.",
|
|
3235
|
+
mimeType: "application/json"
|
|
3236
|
+
},
|
|
3237
|
+
() => {
|
|
3238
|
+
const codes = Object.values(ErrorCode);
|
|
3239
|
+
const hints = {
|
|
3240
|
+
["INVALID_ARGUMENT" /* INVALID_ARGUMENT */]: "Validate required fields and enum values.",
|
|
3241
|
+
["SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */]: "Session may be expired or server-restarted.",
|
|
3242
|
+
["SESSION_BUSY" /* SESSION_BUSY */]: "Wait for running/waiting_permission session to settle.",
|
|
3243
|
+
["PERMISSION_REQUEST_NOT_FOUND" /* PERMISSION_REQUEST_NOT_FOUND */]: "The permission request was already finished/expired.",
|
|
3244
|
+
["PERMISSION_DENIED" /* PERMISSION_DENIED */]: "Check token/secrets/policy restrictions.",
|
|
3245
|
+
["RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */]: "Reduce session count or increase server limits.",
|
|
3246
|
+
["TIMEOUT" /* TIMEOUT */]: "Increase timeout or poll/respond more frequently.",
|
|
3247
|
+
["CANCELLED" /* CANCELLED */]: "Request/session was cancelled by caller or shutdown.",
|
|
3248
|
+
["INTERNAL" /* INTERNAL */]: "Inspect server logs and runtime environment."
|
|
3249
|
+
};
|
|
3250
|
+
return asJsonResource(
|
|
3251
|
+
errorsUri,
|
|
3252
|
+
asVersionedPayload({
|
|
3253
|
+
schemaVersion: resourceSchemaVersion,
|
|
3254
|
+
stability: "stable",
|
|
3255
|
+
payload: {
|
|
3256
|
+
codes,
|
|
3257
|
+
hints
|
|
3258
|
+
}
|
|
3259
|
+
})
|
|
3260
|
+
);
|
|
3261
|
+
}
|
|
3262
|
+
);
|
|
2481
3263
|
server.registerResource(
|
|
2482
3264
|
"compat_report",
|
|
2483
3265
|
compatReportUri.toString(),
|
|
@@ -2493,43 +3275,294 @@ function registerResources(server, deps) {
|
|
|
2493
3275
|
"CLAUDE_CODE_GIT_BASH_PATH is not set. Auto-detection exists, but explicit path is more reliable for GUI-launched MCP clients."
|
|
2494
3276
|
);
|
|
2495
3277
|
}
|
|
2496
|
-
|
|
3278
|
+
const diskResumeEnabled = process.env.CLAUDE_CODE_MCP_ALLOW_DISK_RESUME === "1";
|
|
3279
|
+
const resumeSecretConfigured = typeof process.env.CLAUDE_CODE_MCP_RESUME_SECRET === "string" && process.env.CLAUDE_CODE_MCP_RESUME_SECRET.trim() !== "";
|
|
3280
|
+
const runtimeToolStats = deps.sessionManager.getRuntimeToolStats();
|
|
3281
|
+
const toolCatalogCount = deps.toolCache.getTools().length;
|
|
3282
|
+
const detectedMismatches = [];
|
|
3283
|
+
if (runtimeToolStats.sessionsWithInitTools > 0 && runtimeToolStats.runtimeDiscoveredUniqueCount < toolCatalogCount) {
|
|
3284
|
+
detectedMismatches.push(
|
|
3285
|
+
"Runtime discovered tools are fewer than catalog tools; some features may be hidden or not surfaced in system/init.tools."
|
|
3286
|
+
);
|
|
3287
|
+
}
|
|
3288
|
+
const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3289
|
+
const base = {
|
|
3290
|
+
transport: "stdio",
|
|
3291
|
+
samePlatformRequired: true,
|
|
3292
|
+
packageVersion: deps.version,
|
|
3293
|
+
runtime: {
|
|
3294
|
+
node: process.version,
|
|
3295
|
+
platform: process.platform,
|
|
3296
|
+
arch: process.arch
|
|
3297
|
+
},
|
|
3298
|
+
limits: {
|
|
3299
|
+
maxSessions: serializeLimit(deps.sessionManager.getMaxSessions()),
|
|
3300
|
+
maxPendingPermissionsPerSession: serializeLimit(
|
|
3301
|
+
deps.sessionManager.getMaxPendingPermissionsPerSession()
|
|
3302
|
+
),
|
|
3303
|
+
eventBuffer: deps.sessionManager.getEventBufferConfig()
|
|
3304
|
+
},
|
|
3305
|
+
diskResume: {
|
|
3306
|
+
enabled: diskResumeEnabled,
|
|
3307
|
+
resumeSecretConfigured
|
|
3308
|
+
},
|
|
3309
|
+
features: {
|
|
3310
|
+
resources: true,
|
|
3311
|
+
resourceTemplates: true,
|
|
3312
|
+
toolsListChanged: true,
|
|
3313
|
+
resourcesListChanged: true,
|
|
3314
|
+
sessionInterrupt: true,
|
|
3315
|
+
allowForSessionDecision: true,
|
|
3316
|
+
respondUserInput: false,
|
|
3317
|
+
prompts: false,
|
|
3318
|
+
completions: false
|
|
3319
|
+
},
|
|
3320
|
+
recommendedSettings: {
|
|
3321
|
+
responseMode: "delta_compact",
|
|
3322
|
+
poll: {
|
|
3323
|
+
runningMs: 3e3,
|
|
3324
|
+
waitingPermissionMs: 1e3,
|
|
3325
|
+
cursorStrategy: "Persist nextCursor and de-duplicate by event.id."
|
|
3326
|
+
},
|
|
3327
|
+
timeouts: {
|
|
3328
|
+
sessionInitTimeoutMs: 1e4,
|
|
3329
|
+
permissionRequestTimeoutMs: 6e4,
|
|
3330
|
+
permissionRequestTimeoutMaxMs: 3e5
|
|
3331
|
+
}
|
|
3332
|
+
},
|
|
3333
|
+
guidance: [
|
|
3334
|
+
"Some clients cache tool descriptions at connect time. Prefer claude_code_check(pollOptions.includeTools=true) for runtime-authoritative tool lists.",
|
|
3335
|
+
"Use allowedTools/disallowedTools only with exact runtime tool names.",
|
|
3336
|
+
"Set strictAllowedTools=true when you need allowedTools to behave as a strict allowlist.",
|
|
3337
|
+
"This server assumes MCP client and server run on the same machine/platform.",
|
|
3338
|
+
"For high-frequency status checks, prefer responseMode='delta_compact'.",
|
|
3339
|
+
"respond_user_input is not supported on this backend; use poll/respond_permission flow."
|
|
3340
|
+
],
|
|
3341
|
+
toolCounts: {
|
|
3342
|
+
catalogCount: toolCatalogCount,
|
|
3343
|
+
sessionsWithInitTools: runtimeToolStats.sessionsWithInitTools,
|
|
3344
|
+
runtimeDiscoveredUniqueCount: runtimeToolStats.runtimeDiscoveredUniqueCount,
|
|
3345
|
+
explain: "catalogCount is server catalog size; runtimeDiscoveredUniqueCount is union of system/init.tools across active sessions."
|
|
3346
|
+
},
|
|
3347
|
+
toolCatalogCount,
|
|
3348
|
+
detectedMismatches,
|
|
3349
|
+
runtimeWarnings,
|
|
3350
|
+
resourceTemplates: templateUris
|
|
3351
|
+
};
|
|
3352
|
+
const healthScore = Math.max(
|
|
3353
|
+
0,
|
|
3354
|
+
100 - runtimeWarnings.length * 10 - detectedMismatches.length * 15
|
|
3355
|
+
);
|
|
3356
|
+
return asJsonResource(
|
|
2497
3357
|
compatReportUri,
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
3358
|
+
asVersionedPayload({
|
|
3359
|
+
schemaVersion: resourceSchemaVersion,
|
|
3360
|
+
stability: "stable",
|
|
3361
|
+
payload: {
|
|
3362
|
+
...base,
|
|
3363
|
+
healthScore,
|
|
3364
|
+
updatedAt
|
|
3365
|
+
}
|
|
3366
|
+
})
|
|
3367
|
+
);
|
|
3368
|
+
}
|
|
3369
|
+
);
|
|
3370
|
+
server.registerResource(
|
|
3371
|
+
"session_snapshot_template",
|
|
3372
|
+
new ResourceTemplate(RESOURCE_TEMPLATES.sessionById, { list: void 0 }),
|
|
3373
|
+
{
|
|
3374
|
+
title: "Session Snapshot Template",
|
|
3375
|
+
description: "Read lightweight session diagnostics by sessionId.",
|
|
3376
|
+
mimeType: "application/json"
|
|
3377
|
+
},
|
|
3378
|
+
(uri, variables) => {
|
|
3379
|
+
const sessionId = extractSingleVariable(variables.sessionId) ?? extractSingleVariable(uri.searchParams.get("sessionId"));
|
|
3380
|
+
const payload = typeof sessionId !== "string" || sessionId.trim() === "" ? {
|
|
3381
|
+
found: false,
|
|
3382
|
+
message: "sessionId is required in URI template variable."
|
|
3383
|
+
} : (() => {
|
|
3384
|
+
const session = deps.sessionManager.get(sessionId);
|
|
3385
|
+
if (!session) {
|
|
3386
|
+
return {
|
|
3387
|
+
sessionId,
|
|
3388
|
+
found: false,
|
|
3389
|
+
message: `Session '${sessionId}' not found.`
|
|
3390
|
+
};
|
|
3391
|
+
}
|
|
3392
|
+
const base = deps.sessionManager.toPublicJSON(session);
|
|
3393
|
+
const stored = deps.sessionManager.getResult(sessionId);
|
|
3394
|
+
return {
|
|
3395
|
+
sessionId,
|
|
3396
|
+
found: true,
|
|
3397
|
+
session: {
|
|
3398
|
+
...base,
|
|
3399
|
+
pendingPermissionCount: deps.sessionManager.getPendingPermissionCount(sessionId),
|
|
3400
|
+
eventCount: deps.sessionManager.getEventCount(sessionId),
|
|
3401
|
+
currentCursor: deps.sessionManager.getCurrentCursor(sessionId),
|
|
3402
|
+
lastEventId: deps.sessionManager.getLastEventId(sessionId),
|
|
3403
|
+
ttlMs: deps.sessionManager.getRemainingTtlMs(sessionId),
|
|
3404
|
+
lastError: stored?.type === "error" ? stored.result.result : void 0,
|
|
3405
|
+
lastErrorAt: stored?.type === "error" ? stored.createdAt : void 0,
|
|
3406
|
+
redactions: buildSessionRedactions(false)
|
|
3407
|
+
}
|
|
3408
|
+
};
|
|
3409
|
+
})();
|
|
3410
|
+
return asJsonResource(
|
|
3411
|
+
uri,
|
|
3412
|
+
asVersionedPayload({
|
|
3413
|
+
schemaVersion: resourceSchemaVersion,
|
|
3414
|
+
stability: "stable",
|
|
3415
|
+
payload
|
|
3416
|
+
})
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
);
|
|
3420
|
+
server.registerResource(
|
|
3421
|
+
"runtime_tools_template",
|
|
3422
|
+
new ResourceTemplate(RESOURCE_TEMPLATES.runtimeTools, { list: void 0 }),
|
|
3423
|
+
{
|
|
3424
|
+
title: "Runtime Tools Template",
|
|
3425
|
+
description: "Read runtime tool view globally or for a specific sessionId.",
|
|
3426
|
+
mimeType: "application/json"
|
|
3427
|
+
},
|
|
3428
|
+
(uri, variables) => {
|
|
3429
|
+
const sessionId = extractSingleVariable(variables.sessionId) ?? extractSingleVariable(uri.searchParams.get("sessionId"));
|
|
3430
|
+
const internalToolCount = catalogToolNames.size;
|
|
3431
|
+
const payload = (() => {
|
|
3432
|
+
if (typeof sessionId === "string" && sessionId.trim() !== "") {
|
|
3433
|
+
const session = deps.sessionManager.get(sessionId);
|
|
3434
|
+
if (!session) {
|
|
3435
|
+
return {
|
|
3436
|
+
sessionId,
|
|
3437
|
+
source: "session_not_found",
|
|
3438
|
+
availableTools: [],
|
|
3439
|
+
toolCounts: {
|
|
3440
|
+
internalToolCount,
|
|
3441
|
+
runtimeAvailableCount: 0,
|
|
3442
|
+
sessionAugmentedToolCount: 0
|
|
3443
|
+
}
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3446
|
+
const initTools = deps.sessionManager.getInitTools(sessionId) ?? [];
|
|
3447
|
+
const discovered = discoverToolsFromInit(initTools);
|
|
3448
|
+
const sessionAugmentedToolCount2 = discovered.filter(
|
|
3449
|
+
(tool) => !catalogToolNames.has(tool.name)
|
|
3450
|
+
).length;
|
|
3451
|
+
return {
|
|
3452
|
+
sessionId,
|
|
3453
|
+
source: initTools.length > 0 ? "session_runtime" : "session_without_init",
|
|
3454
|
+
availableTools: discovered,
|
|
3455
|
+
toolCounts: {
|
|
3456
|
+
internalToolCount,
|
|
3457
|
+
runtimeAvailableCount: discovered.length,
|
|
3458
|
+
sessionAugmentedToolCount: sessionAugmentedToolCount2
|
|
3459
|
+
}
|
|
3460
|
+
};
|
|
3461
|
+
}
|
|
3462
|
+
const catalog = deps.toolCache.getTools();
|
|
3463
|
+
const sessionAugmentedToolCount = catalog.filter(
|
|
3464
|
+
(tool) => !catalogToolNames.has(tool.name)
|
|
3465
|
+
).length;
|
|
3466
|
+
return {
|
|
3467
|
+
source: "catalog",
|
|
3468
|
+
availableTools: catalog,
|
|
3469
|
+
toolCounts: {
|
|
3470
|
+
internalToolCount,
|
|
3471
|
+
runtimeAvailableCount: catalog.length,
|
|
3472
|
+
sessionAugmentedToolCount
|
|
3473
|
+
}
|
|
3474
|
+
};
|
|
3475
|
+
})();
|
|
3476
|
+
return asJsonResource(
|
|
3477
|
+
uri,
|
|
3478
|
+
asVersionedPayload({
|
|
3479
|
+
schemaVersion: resourceSchemaVersion,
|
|
3480
|
+
stability: "stable",
|
|
3481
|
+
payload
|
|
3482
|
+
})
|
|
3483
|
+
);
|
|
3484
|
+
}
|
|
3485
|
+
);
|
|
3486
|
+
server.registerResource(
|
|
3487
|
+
"compat_diff_template",
|
|
3488
|
+
new ResourceTemplate(RESOURCE_TEMPLATES.compatDiff, { list: void 0 }),
|
|
3489
|
+
{
|
|
3490
|
+
title: "Compatibility Diff Template",
|
|
3491
|
+
description: "Returns client-specific compatibility guidance and recommended settings.",
|
|
3492
|
+
mimeType: "application/json"
|
|
3493
|
+
},
|
|
3494
|
+
(uri, variables) => {
|
|
3495
|
+
const clientRaw = extractSingleVariable(variables.client) ?? extractSingleVariable(uri.searchParams.get("client"));
|
|
3496
|
+
const clientFingerprint = (clientRaw ?? "unknown").trim();
|
|
3497
|
+
const key = clientFingerprint.toLowerCase();
|
|
3498
|
+
const profile = (() => {
|
|
3499
|
+
if (key.includes("codex")) {
|
|
3500
|
+
return {
|
|
3501
|
+
clientFamily: "codex",
|
|
3502
|
+
detectedMismatches: [],
|
|
3503
|
+
recommendations: [
|
|
3504
|
+
"Prefer responseMode='delta_compact' for fast status loops.",
|
|
3505
|
+
"Enable pollOptions.includeTools=true when exact runtime tool names are required."
|
|
3506
|
+
]
|
|
3507
|
+
};
|
|
3508
|
+
}
|
|
3509
|
+
if (key.includes("claude")) {
|
|
3510
|
+
return {
|
|
3511
|
+
clientFamily: "claude",
|
|
3512
|
+
detectedMismatches: [],
|
|
3513
|
+
recommendations: [
|
|
3514
|
+
"Use resources and resource templates for low-latency diagnostics.",
|
|
3515
|
+
"Use allowedTools/disallowedTools with exact runtime names.",
|
|
3516
|
+
"Enable strictAllowedTools when running in locked-down governance mode."
|
|
3517
|
+
]
|
|
3518
|
+
};
|
|
3519
|
+
}
|
|
3520
|
+
if (key.includes("cursor")) {
|
|
3521
|
+
return {
|
|
3522
|
+
clientFamily: "cursor",
|
|
3523
|
+
detectedMismatches: [
|
|
3524
|
+
"Verify that resources/list and resourceTemplates/list are refreshed after list_changed notifications."
|
|
2518
3525
|
],
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
3526
|
+
recommendations: [
|
|
3527
|
+
"Prefer claude_code_check polling as source of truth for runtime state.",
|
|
3528
|
+
"Fallback to tool calls if resource template support is partial."
|
|
3529
|
+
]
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
return {
|
|
3533
|
+
clientFamily: "generic",
|
|
3534
|
+
detectedMismatches: [],
|
|
3535
|
+
recommendations: [
|
|
3536
|
+
"Persist nextCursor and de-duplicate by event.id.",
|
|
3537
|
+
"Use responseMode='delta_compact' for high-frequency polling, full mode only for diagnostics."
|
|
3538
|
+
]
|
|
3539
|
+
};
|
|
3540
|
+
})();
|
|
3541
|
+
return asJsonResource(
|
|
3542
|
+
uri,
|
|
3543
|
+
asVersionedPayload({
|
|
3544
|
+
schemaVersion: resourceSchemaVersion,
|
|
3545
|
+
stability: "experimental",
|
|
3546
|
+
payload: {
|
|
3547
|
+
clientFingerprint,
|
|
3548
|
+
...profile,
|
|
3549
|
+
recommendedSettings: {
|
|
3550
|
+
responseMode: "delta_compact",
|
|
3551
|
+
poll: {
|
|
3552
|
+
runningMs: 3e3,
|
|
3553
|
+
waitingPermissionMs: 1e3,
|
|
3554
|
+
cursorStrategy: "Persist nextCursor and de-duplicate by event.id."
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
})
|
|
2526
3559
|
);
|
|
2527
3560
|
}
|
|
2528
3561
|
);
|
|
2529
3562
|
}
|
|
2530
3563
|
|
|
2531
3564
|
// src/server.ts
|
|
2532
|
-
var SERVER_VERSION = true ? "2.
|
|
3565
|
+
var SERVER_VERSION = true ? "2.4.3" : "0.0.0-dev";
|
|
2533
3566
|
function createServerContext(serverCwd) {
|
|
2534
3567
|
const sessionManager = new SessionManager();
|
|
2535
3568
|
const server = new McpServer(
|
|
@@ -2550,12 +3583,17 @@ function createServerContext(serverCwd) {
|
|
|
2550
3583
|
}
|
|
2551
3584
|
);
|
|
2552
3585
|
const claudeCodeToolRef = {};
|
|
3586
|
+
const notifyInternalToolsResourceChanged = () => {
|
|
3587
|
+
if (!server.isConnected()) return;
|
|
3588
|
+
server.sendResourceListChanged();
|
|
3589
|
+
};
|
|
2553
3590
|
const toolCache = new ToolDiscoveryCache(void 0, (tools) => {
|
|
2554
3591
|
try {
|
|
2555
3592
|
claudeCodeToolRef.current?.update({ description: buildInternalToolsDescription(tools) });
|
|
2556
3593
|
if (server.isConnected()) {
|
|
2557
3594
|
server.sendToolListChanged();
|
|
2558
3595
|
}
|
|
3596
|
+
notifyInternalToolsResourceChanged();
|
|
2559
3597
|
} catch {
|
|
2560
3598
|
}
|
|
2561
3599
|
});
|
|
@@ -2621,9 +3659,7 @@ function createServerContext(serverCwd) {
|
|
|
2621
3659
|
env: z.record(z.string(), z.string().optional()).optional().describe("Default: none")
|
|
2622
3660
|
};
|
|
2623
3661
|
const advancedOptionFieldsSchemaShape = {
|
|
2624
|
-
...sharedOptionFieldsSchemaShape
|
|
2625
|
-
effort: effortOptionSchema.describe("Deprecated, use top-level. Default: SDK"),
|
|
2626
|
-
thinking: thinkingOptionSchema.describe("Deprecated, use top-level. Default: SDK")
|
|
3662
|
+
...sharedOptionFieldsSchemaShape
|
|
2627
3663
|
};
|
|
2628
3664
|
const diskResumeOptionFieldsSchemaShape = {
|
|
2629
3665
|
...sharedOptionFieldsSchemaShape,
|
|
@@ -2639,6 +3675,7 @@ function createServerContext(serverCwd) {
|
|
|
2639
3675
|
cwd: z.string(),
|
|
2640
3676
|
allowedTools: z.array(z.string()).optional().describe("Default: []"),
|
|
2641
3677
|
disallowedTools: z.array(z.string()).optional().describe("Default: []"),
|
|
3678
|
+
strictAllowedTools: z.boolean().optional().describe("Default: false"),
|
|
2642
3679
|
maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
|
|
2643
3680
|
model: z.string().optional().describe("Default: SDK"),
|
|
2644
3681
|
systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
|
|
@@ -2706,13 +3743,13 @@ function createServerContext(serverCwd) {
|
|
|
2706
3743
|
cwd: z.string().optional().describe("Working dir. Default: server cwd"),
|
|
2707
3744
|
allowedTools: z.array(z.string()).optional().describe("Default: []"),
|
|
2708
3745
|
disallowedTools: z.array(z.string()).optional().describe("Default: []"),
|
|
3746
|
+
strictAllowedTools: z.boolean().optional().describe("Default: false"),
|
|
2709
3747
|
maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
|
|
2710
3748
|
model: z.string().optional().describe("Default: SDK"),
|
|
2711
3749
|
effort: effortOptionSchema.describe("Default: SDK"),
|
|
2712
3750
|
thinking: thinkingOptionSchema.describe("Default: SDK"),
|
|
2713
3751
|
systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
|
|
2714
|
-
permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000"),
|
|
2715
|
-
sessionInitTimeoutMs: z.number().int().positive().optional().describe("Deprecated, use advanced.sessionInitTimeoutMs. Default: 10000"),
|
|
3752
|
+
permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000, clamped to 300000"),
|
|
2716
3753
|
advanced: advancedOptionsSchema
|
|
2717
3754
|
},
|
|
2718
3755
|
outputSchema: startResultSchema,
|
|
@@ -2774,7 +3811,7 @@ function createServerContext(serverCwd) {
|
|
|
2774
3811
|
effort: effortOptionSchema.describe("Default: SDK"),
|
|
2775
3812
|
thinking: thinkingOptionSchema.describe("Default: SDK"),
|
|
2776
3813
|
sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000"),
|
|
2777
|
-
permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000"),
|
|
3814
|
+
permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000, clamped to 300000"),
|
|
2778
3815
|
diskResumeConfig: diskResumeConfigSchema
|
|
2779
3816
|
},
|
|
2780
3817
|
outputSchema: startResultSchema,
|
|
@@ -2822,10 +3859,10 @@ function createServerContext(serverCwd) {
|
|
|
2822
3859
|
server.registerTool(
|
|
2823
3860
|
"claude_code_session",
|
|
2824
3861
|
{
|
|
2825
|
-
description: "List, inspect, or
|
|
3862
|
+
description: "List, inspect, cancel, or interrupt sessions.",
|
|
2826
3863
|
inputSchema: {
|
|
2827
3864
|
action: z.enum(SESSION_ACTIONS),
|
|
2828
|
-
sessionId: z.string().optional().describe("Required for get/cancel"),
|
|
3865
|
+
sessionId: z.string().optional().describe("Required for get/cancel/interrupt"),
|
|
2829
3866
|
includeSensitive: z.boolean().optional().describe("Default: false")
|
|
2830
3867
|
},
|
|
2831
3868
|
outputSchema: sessionResultSchema,
|
|
@@ -2877,10 +3914,10 @@ function createServerContext(serverCwd) {
|
|
|
2877
3914
|
action: z.enum(CHECK_ACTIONS),
|
|
2878
3915
|
sessionId: z.string().describe("Session ID"),
|
|
2879
3916
|
cursor: z.number().int().nonnegative().optional().describe("Default: 0"),
|
|
2880
|
-
responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Default: 'minimal'"),
|
|
2881
|
-
maxEvents: z.number().int().positive().optional().describe("Default: 200 (minimal), unlimited (full)"),
|
|
2882
|
-
requestId: z.string().optional().describe("
|
|
2883
|
-
decision: z.enum(["allow", "deny"]).optional().describe("
|
|
3917
|
+
responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Default: 'minimal'. Use 'delta_compact' for lightweight polling."),
|
|
3918
|
+
maxEvents: z.number().int().positive().optional().describe("Default: 200 (minimal), unlimited (full/delta_compact)"),
|
|
3919
|
+
requestId: z.string().optional().describe("Default: none"),
|
|
3920
|
+
decision: z.enum(["allow", "deny", "allow_for_session"]).optional().describe("Default: none"),
|
|
2884
3921
|
denyMessage: z.string().optional().describe("Default: 'Permission denied by caller'"),
|
|
2885
3922
|
interrupt: z.boolean().optional().describe("Default: false"),
|
|
2886
3923
|
pollOptions: z.object({
|
|
@@ -2888,11 +3925,11 @@ function createServerContext(serverCwd) {
|
|
|
2888
3925
|
includeEvents: z.boolean().optional().describe("Default: true"),
|
|
2889
3926
|
includeActions: z.boolean().optional().describe("Default: true"),
|
|
2890
3927
|
includeResult: z.boolean().optional().describe("Default: true"),
|
|
2891
|
-
includeUsage: z.boolean().optional().describe("Default: full=true, minimal=false"),
|
|
2892
|
-
includeModelUsage: z.boolean().optional().describe("Default: full=true, minimal=false"),
|
|
2893
|
-
includeStructuredOutput: z.boolean().optional().describe("Default: full=true, minimal=false"),
|
|
2894
|
-
includeTerminalEvents: z.boolean().optional().describe("Default: full=true, minimal=false"),
|
|
2895
|
-
includeProgressEvents: z.boolean().optional().describe("Default: full=true, minimal=false"),
|
|
3928
|
+
includeUsage: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
|
|
3929
|
+
includeModelUsage: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
|
|
3930
|
+
includeStructuredOutput: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
|
|
3931
|
+
includeTerminalEvents: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
|
|
3932
|
+
includeProgressEvents: z.boolean().optional().describe("Default: full=true, minimal/delta_compact=false"),
|
|
2896
3933
|
maxBytes: z.number().int().positive().optional().describe("Default: unlimited")
|
|
2897
3934
|
}).optional().describe("Default: none"),
|
|
2898
3935
|
permissionOptions: z.object({
|
|
@@ -2944,7 +3981,7 @@ function createServerContext(serverCwd) {
|
|
|
2944
3981
|
}
|
|
2945
3982
|
}
|
|
2946
3983
|
);
|
|
2947
|
-
registerResources(server, { toolCache });
|
|
3984
|
+
registerResources(server, { toolCache, version: SERVER_VERSION, sessionManager });
|
|
2948
3985
|
return { server, sessionManager, toolCache };
|
|
2949
3986
|
}
|
|
2950
3987
|
|