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