@leo000001/claude-code-mcp 2.0.1 → 2.2.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 +146 -83
- package/CONTRIBUTING.md +10 -1
- package/NOTICE.md +27 -0
- package/README.md +563 -486
- package/SECURITY.md +4 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1398 -379
- package/dist/index.js.map +1 -1
- package/package.json +14 -2
package/dist/index.js
CHANGED
|
@@ -7,24 +7,125 @@ 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
|
+
// src/utils/normalize-windows-path.ts
|
|
19
|
+
import path from "path";
|
|
20
|
+
import os from "os";
|
|
21
|
+
function maybeAddWindowsLongPathPrefix(value) {
|
|
22
|
+
if (value.startsWith("\\\\?\\") || value.startsWith("\\\\.\\") || value.length < 240)
|
|
23
|
+
return value;
|
|
24
|
+
return path.win32.toNamespacedPath(value);
|
|
25
|
+
}
|
|
26
|
+
function expandTilde(value, platform) {
|
|
27
|
+
if (value === "~") return os.homedir();
|
|
28
|
+
if (!value.startsWith("~")) return value;
|
|
29
|
+
const next = value[1];
|
|
30
|
+
if (next !== "/" && next !== "\\") return value;
|
|
31
|
+
const rest = value.slice(2);
|
|
32
|
+
const join2 = platform === "win32" ? path.win32.join : path.posix.join;
|
|
33
|
+
return join2(os.homedir(), rest);
|
|
34
|
+
}
|
|
35
|
+
function normalizeMsysToWindowsPathInner(rawPath) {
|
|
36
|
+
if (/^[a-zA-Z]:[\\/]/.test(rawPath) || rawPath.startsWith("\\\\")) return void 0;
|
|
37
|
+
if (rawPath.startsWith("//")) {
|
|
38
|
+
const m2 = rawPath.match(/^\/\/([^/]{2,})\/(.*)$/);
|
|
39
|
+
if (m2) {
|
|
40
|
+
const host = m2[1];
|
|
41
|
+
const rest2 = m2[2] ?? "";
|
|
42
|
+
return `\\\\${host}\\${rest2.replace(/\//g, "\\")}`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const cyg = rawPath.match(/^\/cygdrive\/([a-zA-Z])\/(.*)$/);
|
|
46
|
+
if (cyg) {
|
|
47
|
+
const drive2 = cyg[1].toUpperCase();
|
|
48
|
+
const rest2 = cyg[2] ?? "";
|
|
49
|
+
return `${drive2}:\\${rest2.replace(/\//g, "\\")}`;
|
|
50
|
+
}
|
|
51
|
+
const m = rawPath.match(/^\/(?:mnt\/)?([a-zA-Z])\/(.*)$/);
|
|
52
|
+
if (!m) return void 0;
|
|
53
|
+
const drive = m[1].toUpperCase();
|
|
54
|
+
const rest = m[2] ?? "";
|
|
55
|
+
return `${drive}:\\${rest.replace(/\//g, "\\")}`;
|
|
56
|
+
}
|
|
57
|
+
function normalizeMsysToWindowsPath(rawPath) {
|
|
58
|
+
const converted = normalizeMsysToWindowsPathInner(rawPath);
|
|
59
|
+
if (!converted) return void 0;
|
|
60
|
+
return path.win32.normalize(converted);
|
|
61
|
+
}
|
|
62
|
+
function normalizeWindowsPathLike(value, platform = process.platform) {
|
|
63
|
+
const expanded = expandTilde(value, platform);
|
|
64
|
+
if (platform !== "win32") return expanded;
|
|
65
|
+
const converted = normalizeMsysToWindowsPath(expanded);
|
|
66
|
+
if (!converted && expanded.startsWith("/")) return expanded;
|
|
67
|
+
const normalized = path.win32.normalize(converted ?? expanded);
|
|
68
|
+
return maybeAddWindowsLongPathPrefix(normalized);
|
|
69
|
+
}
|
|
70
|
+
function normalizeWindowsPathArray(values, platform = process.platform) {
|
|
71
|
+
return values.map((v) => normalizeWindowsPathLike(v, platform));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/utils/normalize-tool-input.ts
|
|
75
|
+
function normalizeToolInput(_toolName, input, platform = process.platform) {
|
|
76
|
+
if (platform !== "win32") return input;
|
|
77
|
+
const filePath = input.file_path;
|
|
78
|
+
if (typeof filePath !== "string") return input;
|
|
79
|
+
const normalized = normalizeWindowsPathLike(filePath, platform);
|
|
80
|
+
return normalized === filePath ? input : { ...input, file_path: normalized };
|
|
81
|
+
}
|
|
82
|
+
|
|
10
83
|
// src/session/manager.ts
|
|
11
84
|
var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
|
|
12
85
|
var DEFAULT_RUNNING_SESSION_MAX_MS = 4 * 60 * 60 * 1e3;
|
|
13
86
|
var DEFAULT_CLEANUP_INTERVAL_MS = 6e4;
|
|
14
87
|
var DEFAULT_EVENT_BUFFER_MAX_SIZE = 1e3;
|
|
15
88
|
var DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE = 2e3;
|
|
89
|
+
var DEFAULT_MAX_SESSIONS = 128;
|
|
90
|
+
var DEFAULT_MAX_PENDING_PERMISSIONS_PER_SESSION = 64;
|
|
16
91
|
var SessionManager = class _SessionManager {
|
|
17
92
|
sessions = /* @__PURE__ */ new Map();
|
|
18
93
|
runtime = /* @__PURE__ */ new Map();
|
|
94
|
+
drainingSessions = /* @__PURE__ */ new Map();
|
|
19
95
|
cleanupTimer;
|
|
20
96
|
sessionTtlMs = DEFAULT_SESSION_TTL_MS;
|
|
21
97
|
runningSessionMaxMs = DEFAULT_RUNNING_SESSION_MAX_MS;
|
|
22
|
-
|
|
98
|
+
platform;
|
|
99
|
+
maxSessions;
|
|
100
|
+
maxPendingPermissionsPerSession;
|
|
101
|
+
constructor(opts) {
|
|
102
|
+
this.platform = opts?.platform ?? process.platform;
|
|
103
|
+
const envRaw = process.env.CLAUDE_CODE_MCP_MAX_SESSIONS;
|
|
104
|
+
const envParsed = typeof envRaw === "string" && envRaw.trim() !== "" ? Number.parseInt(envRaw, 10) : NaN;
|
|
105
|
+
const configured = opts?.maxSessions ?? (Number.isFinite(envParsed) ? envParsed : void 0);
|
|
106
|
+
this.maxSessions = typeof configured === "number" ? configured <= 0 ? Number.POSITIVE_INFINITY : configured : DEFAULT_MAX_SESSIONS;
|
|
107
|
+
const maxPendingEnvRaw = process.env.CLAUDE_CODE_MCP_MAX_PENDING_PERMISSIONS;
|
|
108
|
+
const maxPendingEnvParsed = typeof maxPendingEnvRaw === "string" && maxPendingEnvRaw.trim() !== "" ? Number.parseInt(maxPendingEnvRaw, 10) : NaN;
|
|
109
|
+
const maxPendingConfigured = opts?.maxPendingPermissionsPerSession ?? (Number.isFinite(maxPendingEnvParsed) ? maxPendingEnvParsed : void 0);
|
|
110
|
+
this.maxPendingPermissionsPerSession = typeof maxPendingConfigured === "number" ? maxPendingConfigured <= 0 ? Number.POSITIVE_INFINITY : maxPendingConfigured : DEFAULT_MAX_PENDING_PERMISSIONS_PER_SESSION;
|
|
23
111
|
this.cleanupTimer = setInterval(() => this.cleanup(), DEFAULT_CLEANUP_INTERVAL_MS);
|
|
24
112
|
if (this.cleanupTimer.unref) {
|
|
25
113
|
this.cleanupTimer.unref();
|
|
26
114
|
}
|
|
27
115
|
}
|
|
116
|
+
getSessionCount() {
|
|
117
|
+
return this.sessions.size;
|
|
118
|
+
}
|
|
119
|
+
getMaxSessions() {
|
|
120
|
+
return this.maxSessions;
|
|
121
|
+
}
|
|
122
|
+
getMaxPendingPermissionsPerSession() {
|
|
123
|
+
return this.maxPendingPermissionsPerSession;
|
|
124
|
+
}
|
|
125
|
+
hasCapacityFor(additionalSessions) {
|
|
126
|
+
if (additionalSessions <= 0) return true;
|
|
127
|
+
return this.sessions.size + additionalSessions <= this.maxSessions;
|
|
128
|
+
}
|
|
28
129
|
create(params) {
|
|
29
130
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30
131
|
const existing = this.sessions.get(params.sessionId);
|
|
@@ -117,7 +218,10 @@ var SessionManager = class _SessionManager {
|
|
|
117
218
|
if (info.status === "waiting_permission") {
|
|
118
219
|
this.finishAllPending(
|
|
119
220
|
sessionId,
|
|
120
|
-
|
|
221
|
+
// `cancel()` already aborts the running query below. Avoid sending an additional
|
|
222
|
+
// interrupt=true control response while the stream is being torn down, which can race
|
|
223
|
+
// into SDK-level "Operation aborted" write errors on stdio clients.
|
|
224
|
+
{ behavior: "deny", message: "Session cancelled", interrupt: false },
|
|
121
225
|
"cancel"
|
|
122
226
|
);
|
|
123
227
|
}
|
|
@@ -135,8 +239,10 @@ var SessionManager = class _SessionManager {
|
|
|
135
239
|
this.finishAllPending(
|
|
136
240
|
sessionId,
|
|
137
241
|
{ behavior: "deny", message: "Session deleted", interrupt: true },
|
|
138
|
-
"cleanup"
|
|
242
|
+
"cleanup",
|
|
243
|
+
{ restoreRunning: false }
|
|
139
244
|
);
|
|
245
|
+
this.drainingSessions.delete(sessionId);
|
|
140
246
|
this.runtime.delete(sessionId);
|
|
141
247
|
return this.sessions.delete(sessionId);
|
|
142
248
|
}
|
|
@@ -192,11 +298,63 @@ var SessionManager = class _SessionManager {
|
|
|
192
298
|
const state = this.runtime.get(sessionId);
|
|
193
299
|
const info = this.sessions.get(sessionId);
|
|
194
300
|
if (!state || !info) return false;
|
|
301
|
+
if (info.status !== "running" && info.status !== "waiting_permission") {
|
|
302
|
+
try {
|
|
303
|
+
finish({
|
|
304
|
+
behavior: "deny",
|
|
305
|
+
message: `Session is not accepting permission requests (status: ${info.status}).`,
|
|
306
|
+
interrupt: true
|
|
307
|
+
});
|
|
308
|
+
} catch {
|
|
309
|
+
}
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
if (this.isDraining(sessionId)) {
|
|
313
|
+
try {
|
|
314
|
+
finish({
|
|
315
|
+
behavior: "deny",
|
|
316
|
+
message: "Session is finishing pending permission requests.",
|
|
317
|
+
interrupt: true
|
|
318
|
+
});
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
195
323
|
if (!state.pendingPermissions.has(req.requestId)) {
|
|
324
|
+
if (state.pendingPermissions.size >= this.maxPendingPermissionsPerSession) {
|
|
325
|
+
const deny = {
|
|
326
|
+
behavior: "deny",
|
|
327
|
+
message: "Too many pending permission requests for this session.",
|
|
328
|
+
interrupt: false
|
|
329
|
+
};
|
|
330
|
+
this.pushEvent(sessionId, {
|
|
331
|
+
type: "permission_result",
|
|
332
|
+
data: {
|
|
333
|
+
requestId: req.requestId,
|
|
334
|
+
toolName: req.toolName,
|
|
335
|
+
behavior: "deny",
|
|
336
|
+
source: "policy",
|
|
337
|
+
message: deny.message,
|
|
338
|
+
interrupt: deny.interrupt
|
|
339
|
+
},
|
|
340
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
341
|
+
});
|
|
342
|
+
try {
|
|
343
|
+
finish(deny);
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
const inferredExpiresAt = new Date(Date.now() + timeoutMs).toISOString();
|
|
349
|
+
const record = {
|
|
350
|
+
...req,
|
|
351
|
+
timeoutMs,
|
|
352
|
+
expiresAt: inferredExpiresAt
|
|
353
|
+
};
|
|
196
354
|
const timeoutId = setTimeout(() => {
|
|
197
355
|
this.finishRequest(
|
|
198
356
|
sessionId,
|
|
199
|
-
|
|
357
|
+
record.requestId,
|
|
200
358
|
{
|
|
201
359
|
behavior: "deny",
|
|
202
360
|
message: `Permission request timed out after ${timeoutMs}ms.`,
|
|
@@ -205,12 +363,12 @@ var SessionManager = class _SessionManager {
|
|
|
205
363
|
"timeout"
|
|
206
364
|
);
|
|
207
365
|
}, timeoutMs);
|
|
208
|
-
state.pendingPermissions.set(
|
|
366
|
+
state.pendingPermissions.set(record.requestId, { record, finish, timeoutId });
|
|
209
367
|
info.status = "waiting_permission";
|
|
210
368
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
211
369
|
this.pushEvent(sessionId, {
|
|
212
370
|
type: "permission_request",
|
|
213
|
-
data:
|
|
371
|
+
data: record,
|
|
214
372
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
215
373
|
});
|
|
216
374
|
return true;
|
|
@@ -242,28 +400,86 @@ var SessionManager = class _SessionManager {
|
|
|
242
400
|
};
|
|
243
401
|
}
|
|
244
402
|
}
|
|
403
|
+
if (finalResult.behavior === "allow") {
|
|
404
|
+
const updatedInput = finalResult.updatedInput;
|
|
405
|
+
const validRecord = updatedInput !== null && updatedInput !== void 0 && typeof updatedInput === "object" && !Array.isArray(updatedInput);
|
|
406
|
+
if (!validRecord) {
|
|
407
|
+
finalResult = {
|
|
408
|
+
...finalResult,
|
|
409
|
+
updatedInput: normalizePermissionUpdatedInput(pending.record.input)
|
|
410
|
+
};
|
|
411
|
+
} else {
|
|
412
|
+
finalResult = {
|
|
413
|
+
...finalResult,
|
|
414
|
+
updatedInput: normalizeToolInput(
|
|
415
|
+
pending.record.toolName,
|
|
416
|
+
updatedInput,
|
|
417
|
+
this.platform
|
|
418
|
+
)
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
}
|
|
245
422
|
if (pending.timeoutId) clearTimeout(pending.timeoutId);
|
|
246
423
|
state.pendingPermissions.delete(requestId);
|
|
424
|
+
const eventData = {
|
|
425
|
+
requestId,
|
|
426
|
+
toolName: pending.record.toolName,
|
|
427
|
+
behavior: finalResult.behavior,
|
|
428
|
+
source
|
|
429
|
+
};
|
|
430
|
+
if (finalResult.behavior === "deny") {
|
|
431
|
+
eventData.message = finalResult.message;
|
|
432
|
+
eventData.interrupt = finalResult.interrupt;
|
|
433
|
+
} else {
|
|
434
|
+
const allow = finalResult;
|
|
435
|
+
if (allow.updatedInput !== void 0) eventData.updatedInput = allow.updatedInput;
|
|
436
|
+
if (allow.updatedPermissions !== void 0)
|
|
437
|
+
eventData.updatedPermissions = allow.updatedPermissions;
|
|
438
|
+
}
|
|
247
439
|
this.pushEvent(sessionId, {
|
|
248
440
|
type: "permission_result",
|
|
249
|
-
data:
|
|
441
|
+
data: eventData,
|
|
250
442
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
251
443
|
});
|
|
252
444
|
try {
|
|
253
445
|
pending.finish(finalResult);
|
|
254
446
|
} catch {
|
|
255
447
|
}
|
|
256
|
-
if (info.status === "waiting_permission" && state.pendingPermissions.size === 0) {
|
|
448
|
+
if (info.status === "waiting_permission" && state.pendingPermissions.size === 0 && !this.isDraining(sessionId)) {
|
|
257
449
|
info.status = "running";
|
|
258
450
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
259
451
|
}
|
|
260
452
|
return true;
|
|
261
453
|
}
|
|
262
|
-
finishAllPending(sessionId, result, source) {
|
|
454
|
+
finishAllPending(sessionId, result, source, opts) {
|
|
263
455
|
const state = this.runtime.get(sessionId);
|
|
264
456
|
if (!state) return;
|
|
265
|
-
|
|
266
|
-
|
|
457
|
+
if (state.pendingPermissions.size === 0) return;
|
|
458
|
+
this.beginDraining(sessionId);
|
|
459
|
+
try {
|
|
460
|
+
while (state.pendingPermissions.size > 0) {
|
|
461
|
+
const next = state.pendingPermissions.keys().next();
|
|
462
|
+
if (next.done || typeof next.value !== "string") break;
|
|
463
|
+
const requestId = next.value;
|
|
464
|
+
const handled = this.finishRequest(sessionId, requestId, result, source);
|
|
465
|
+
if (!handled) {
|
|
466
|
+
const pending = state.pendingPermissions.get(requestId);
|
|
467
|
+
if (pending?.timeoutId) clearTimeout(pending.timeoutId);
|
|
468
|
+
state.pendingPermissions.delete(requestId);
|
|
469
|
+
try {
|
|
470
|
+
pending?.finish(result);
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
} finally {
|
|
476
|
+
this.endDraining(sessionId);
|
|
477
|
+
}
|
|
478
|
+
const restoreRunning = opts?.restoreRunning ?? true;
|
|
479
|
+
const info = this.sessions.get(sessionId);
|
|
480
|
+
if (restoreRunning && info && info.status === "waiting_permission" && state.pendingPermissions.size === 0) {
|
|
481
|
+
info.status = "running";
|
|
482
|
+
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
267
483
|
}
|
|
268
484
|
}
|
|
269
485
|
/** Remove sessions that have been idle for too long, or stuck running too long */
|
|
@@ -275,29 +491,40 @@ var SessionManager = class _SessionManager {
|
|
|
275
491
|
this.finishAllPending(
|
|
276
492
|
id,
|
|
277
493
|
{ behavior: "deny", message: "Session expired", interrupt: true },
|
|
278
|
-
"cleanup"
|
|
494
|
+
"cleanup",
|
|
495
|
+
{ restoreRunning: false }
|
|
279
496
|
);
|
|
497
|
+
this.drainingSessions.delete(id);
|
|
280
498
|
this.sessions.delete(id);
|
|
281
499
|
this.runtime.delete(id);
|
|
282
500
|
} else if (info.status === "running" && now - lastActive > this.runningSessionMaxMs) {
|
|
283
501
|
if (info.abortController) info.abortController.abort();
|
|
502
|
+
info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
503
|
+
info.cancelledReason = info.cancelledReason ?? "Session timed out";
|
|
504
|
+
info.cancelledSource = info.cancelledSource ?? "cleanup";
|
|
284
505
|
info.status = "error";
|
|
285
506
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
286
507
|
} else if (info.status === "waiting_permission" && now - lastActive > this.runningSessionMaxMs) {
|
|
287
508
|
this.finishAllPending(
|
|
288
509
|
id,
|
|
289
510
|
{ behavior: "deny", message: "Session timed out", interrupt: true },
|
|
290
|
-
"cleanup"
|
|
511
|
+
"cleanup",
|
|
512
|
+
{ restoreRunning: false }
|
|
291
513
|
);
|
|
292
514
|
if (info.abortController) info.abortController.abort();
|
|
515
|
+
info.cancelledAt = info.cancelledAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
516
|
+
info.cancelledReason = info.cancelledReason ?? "Session timed out";
|
|
517
|
+
info.cancelledSource = info.cancelledSource ?? "cleanup";
|
|
293
518
|
info.status = "error";
|
|
294
519
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
295
520
|
} else if (info.status !== "running" && info.status !== "waiting_permission" && now - lastActive > this.sessionTtlMs) {
|
|
296
521
|
this.finishAllPending(
|
|
297
522
|
id,
|
|
298
523
|
{ behavior: "deny", message: "Session expired", interrupt: true },
|
|
299
|
-
"cleanup"
|
|
524
|
+
"cleanup",
|
|
525
|
+
{ restoreRunning: false }
|
|
300
526
|
);
|
|
527
|
+
this.drainingSessions.delete(id);
|
|
301
528
|
this.sessions.delete(id);
|
|
302
529
|
this.runtime.delete(id);
|
|
303
530
|
}
|
|
@@ -346,7 +573,8 @@ var SessionManager = class _SessionManager {
|
|
|
346
573
|
this.finishAllPending(
|
|
347
574
|
info.sessionId,
|
|
348
575
|
{ behavior: "deny", message: "Server shutting down", interrupt: true },
|
|
349
|
-
"destroy"
|
|
576
|
+
"destroy",
|
|
577
|
+
{ restoreRunning: false }
|
|
350
578
|
);
|
|
351
579
|
if ((info.status === "running" || info.status === "waiting_permission") && info.abortController) {
|
|
352
580
|
info.abortController.abort();
|
|
@@ -416,6 +644,20 @@ var SessionManager = class _SessionManager {
|
|
|
416
644
|
static clearTerminalEvents(buffer) {
|
|
417
645
|
buffer.events = buffer.events.filter((e) => e.type !== "result" && e.type !== "error");
|
|
418
646
|
}
|
|
647
|
+
isDraining(sessionId) {
|
|
648
|
+
return (this.drainingSessions.get(sessionId) ?? 0) > 0;
|
|
649
|
+
}
|
|
650
|
+
beginDraining(sessionId) {
|
|
651
|
+
this.drainingSessions.set(sessionId, (this.drainingSessions.get(sessionId) ?? 0) + 1);
|
|
652
|
+
}
|
|
653
|
+
endDraining(sessionId) {
|
|
654
|
+
const current = this.drainingSessions.get(sessionId) ?? 0;
|
|
655
|
+
if (current <= 1) {
|
|
656
|
+
this.drainingSessions.delete(sessionId);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
this.drainingSessions.set(sessionId, current - 1);
|
|
660
|
+
}
|
|
419
661
|
};
|
|
420
662
|
|
|
421
663
|
// src/types.ts
|
|
@@ -432,23 +674,98 @@ import { AbortError, query } from "@anthropic-ai/claude-agent-sdk";
|
|
|
432
674
|
// src/utils/windows.ts
|
|
433
675
|
import { existsSync } from "fs";
|
|
434
676
|
import { execSync } from "child_process";
|
|
435
|
-
import
|
|
436
|
-
var { join, dirname, normalize } =
|
|
677
|
+
import path2 from "path";
|
|
678
|
+
var { join, dirname, normalize, isAbsolute } = path2.win32;
|
|
679
|
+
var LONG_PATH_PREFIX = "\\\\?\\";
|
|
680
|
+
var UNC_LONG_PATH_PREFIX = "\\\\?\\UNC\\";
|
|
437
681
|
function isWindows() {
|
|
438
682
|
return process.platform === "win32";
|
|
439
683
|
}
|
|
684
|
+
function normalizeMaybeQuotedPath(raw) {
|
|
685
|
+
return normalize(raw.trim().replace(/^"|"$/g, ""));
|
|
686
|
+
}
|
|
687
|
+
function existsSyncSafe(candidate) {
|
|
688
|
+
try {
|
|
689
|
+
return existsSync(candidate);
|
|
690
|
+
} catch {
|
|
691
|
+
return false;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function ensureLongPathPrefix(normalized) {
|
|
695
|
+
if (!normalized || normalized.startsWith(LONG_PATH_PREFIX)) return normalized;
|
|
696
|
+
if (!isAbsolute(normalized)) return normalized;
|
|
697
|
+
if (normalized.startsWith("\\\\")) {
|
|
698
|
+
return `${UNC_LONG_PATH_PREFIX}${normalized.slice(2)}`;
|
|
699
|
+
}
|
|
700
|
+
return `${LONG_PATH_PREFIX}${normalized}`;
|
|
701
|
+
}
|
|
702
|
+
function existsPath(raw) {
|
|
703
|
+
if (!raw) return false;
|
|
704
|
+
const normalized = normalizeMaybeQuotedPath(raw);
|
|
705
|
+
if (existsSyncSafe(normalized)) return true;
|
|
706
|
+
const longPath = ensureLongPathPrefix(normalized);
|
|
707
|
+
if (longPath === normalized) return false;
|
|
708
|
+
return existsSyncSafe(longPath);
|
|
709
|
+
}
|
|
710
|
+
function tryWhere(exe) {
|
|
711
|
+
try {
|
|
712
|
+
const output = execSync(`where ${exe}`, {
|
|
713
|
+
encoding: "utf8",
|
|
714
|
+
timeout: 2e3,
|
|
715
|
+
windowsHide: true
|
|
716
|
+
});
|
|
717
|
+
return output.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((p) => normalizeMaybeQuotedPath(p));
|
|
718
|
+
} catch {
|
|
719
|
+
return [];
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function pathEntries() {
|
|
723
|
+
const raw = process.env.PATH;
|
|
724
|
+
if (typeof raw !== "string" || raw.trim() === "") return [];
|
|
725
|
+
return raw.split(path2.delimiter).map((p) => p.trim().replace(/^"|"$/g, "")).filter(Boolean).map((p) => normalizeMaybeQuotedPath(p));
|
|
726
|
+
}
|
|
727
|
+
function tryFromPath(exeNames) {
|
|
728
|
+
const results = [];
|
|
729
|
+
for (const dir of pathEntries()) {
|
|
730
|
+
for (const exe of exeNames) {
|
|
731
|
+
const full = normalize(path2.win32.join(dir, exe));
|
|
732
|
+
if (existsPath(full)) results.push(full);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return results;
|
|
736
|
+
}
|
|
737
|
+
function firstExisting(candidates) {
|
|
738
|
+
for (const p of candidates) {
|
|
739
|
+
if (p && existsPath(p)) return p;
|
|
740
|
+
}
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
function isWindowsSystemBash(pathLike) {
|
|
744
|
+
const p = normalizeMaybeQuotedPath(pathLike).toLowerCase();
|
|
745
|
+
return p.endsWith("\\windows\\system32\\bash.exe") || p.endsWith("\\windows\\syswow64\\bash.exe") || p.includes("\\windows\\system32\\bash.exe") || p.includes("\\windows\\syswow64\\bash.exe");
|
|
746
|
+
}
|
|
440
747
|
function findGitBash() {
|
|
441
748
|
const envPathRaw = process.env.CLAUDE_CODE_GIT_BASH_PATH;
|
|
442
749
|
if (envPathRaw && envPathRaw.trim() !== "") {
|
|
443
|
-
const envPath =
|
|
444
|
-
if (
|
|
445
|
-
|
|
446
|
-
|
|
750
|
+
const envPath = normalizeMaybeQuotedPath(envPathRaw);
|
|
751
|
+
if (existsPath(envPath)) return envPath;
|
|
752
|
+
}
|
|
753
|
+
const programFilesRoots = [
|
|
754
|
+
process.env.ProgramW6432,
|
|
755
|
+
process.env.ProgramFiles,
|
|
756
|
+
process.env["ProgramFiles(x86)"]
|
|
757
|
+
].filter((v) => typeof v === "string" && v.trim() !== "");
|
|
758
|
+
const defaultCandidates = [];
|
|
759
|
+
for (const root of programFilesRoots) {
|
|
760
|
+
const base = join(normalizeMaybeQuotedPath(root), "Git");
|
|
761
|
+
defaultCandidates.push(join(base, "bin", "bash.exe"));
|
|
762
|
+
defaultCandidates.push(join(base, "usr", "bin", "bash.exe"));
|
|
763
|
+
}
|
|
764
|
+
const fromDefault = firstExisting(defaultCandidates);
|
|
765
|
+
if (fromDefault) return fromDefault;
|
|
447
766
|
try {
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
for (const gitPathRaw of gitCandidates) {
|
|
451
|
-
const gitPath = normalize(gitPathRaw.replace(/^"|"$/g, ""));
|
|
767
|
+
const gitCandidates = [...tryFromPath(["git.exe", "git.cmd", "git.bat"]), ...tryWhere("git")];
|
|
768
|
+
for (const gitPath of gitCandidates) {
|
|
452
769
|
if (!gitPath) continue;
|
|
453
770
|
const gitDir = dirname(gitPath);
|
|
454
771
|
const gitDirLower = gitDir.toLowerCase();
|
|
@@ -470,19 +787,38 @@ function findGitBash() {
|
|
|
470
787
|
bashCandidates.push(join(root, "mingw64", "bin", "bash.exe"));
|
|
471
788
|
}
|
|
472
789
|
for (const bashPath of bashCandidates) {
|
|
473
|
-
const
|
|
474
|
-
if (
|
|
790
|
+
const normalizedPath = normalize(bashPath);
|
|
791
|
+
if (existsPath(normalizedPath)) return normalizedPath;
|
|
475
792
|
}
|
|
476
793
|
}
|
|
477
794
|
} catch {
|
|
478
795
|
}
|
|
796
|
+
const fromWhereBash = firstExisting(
|
|
797
|
+
[...tryFromPath(["bash.exe"]), ...tryWhere("bash")].filter(
|
|
798
|
+
(p) => p.toLowerCase().endsWith("\\bash.exe") && !isWindowsSystemBash(p)
|
|
799
|
+
)
|
|
800
|
+
);
|
|
801
|
+
if (fromWhereBash) return fromWhereBash;
|
|
479
802
|
return null;
|
|
480
803
|
}
|
|
481
804
|
function checkWindowsBashAvailability() {
|
|
482
805
|
if (!isWindows()) return;
|
|
806
|
+
const envPathRaw = process.env.CLAUDE_CODE_GIT_BASH_PATH;
|
|
807
|
+
const envPath = envPathRaw && envPathRaw.trim() !== "" ? normalizeMaybeQuotedPath(envPathRaw) : null;
|
|
808
|
+
const envValid = !!(envPath && existsPath(envPath));
|
|
483
809
|
const bashPath = findGitBash();
|
|
484
810
|
if (bashPath) {
|
|
485
|
-
|
|
811
|
+
if (!envValid) {
|
|
812
|
+
process.env.CLAUDE_CODE_GIT_BASH_PATH = bashPath;
|
|
813
|
+
if (envPathRaw && envPathRaw.trim() !== "") {
|
|
814
|
+
console.error(
|
|
815
|
+
`[windows] WARNING: CLAUDE_CODE_GIT_BASH_PATH is set to "${envPathRaw}" but the file does not exist.`
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
console.error(`[windows] Git Bash detected: ${bashPath} (set CLAUDE_CODE_GIT_BASH_PATH)`);
|
|
819
|
+
} else {
|
|
820
|
+
console.error(`[windows] Git Bash detected: ${bashPath}`);
|
|
821
|
+
}
|
|
486
822
|
return;
|
|
487
823
|
}
|
|
488
824
|
const hint = process.env.CLAUDE_CODE_GIT_BASH_PATH ? `CLAUDE_CODE_GIT_BASH_PATH is set to "${process.env.CLAUDE_CODE_GIT_BASH_PATH}" but the file does not exist.` : "CLAUDE_CODE_GIT_BASH_PATH is not set and git was not found in PATH.";
|
|
@@ -499,7 +835,9 @@ function checkWindowsBashAvailability() {
|
|
|
499
835
|
var WINDOWS_BASH_HINT = '\n\n[Windows] The Claude Code CLI requires Git Bash. Set CLAUDE_CODE_GIT_BASH_PATH in your MCP server config or system environment. See README.md "Windows Support" section for details.';
|
|
500
836
|
function enhanceWindowsError(errorMessage) {
|
|
501
837
|
if (!isWindows()) return errorMessage;
|
|
502
|
-
|
|
838
|
+
const lower = errorMessage.toLowerCase();
|
|
839
|
+
const looksLikeMissingBash = lower.includes("enoent") && (lower.includes("bash") || lower.includes("git-bash")) || lower.includes("claude code on windows requires git-bash");
|
|
840
|
+
if (looksLikeMissingBash || lower.includes("claude_code_git_bash_path")) {
|
|
503
841
|
return errorMessage + WINDOWS_BASH_HINT;
|
|
504
842
|
}
|
|
505
843
|
return errorMessage;
|
|
@@ -508,6 +846,13 @@ function enhanceWindowsError(errorMessage) {
|
|
|
508
846
|
// src/tools/query-consumer.ts
|
|
509
847
|
var MAX_TRANSIENT_RETRIES = 3;
|
|
510
848
|
var INITIAL_RETRY_DELAY_MS = 1e3;
|
|
849
|
+
var DEFAULT_PERMISSION_REQUEST_TIMEOUT_MS = 6e4;
|
|
850
|
+
var MAX_PERMISSION_REQUEST_TIMEOUT_MS = 5 * 6e4;
|
|
851
|
+
var MAX_PREINIT_BUFFER_MESSAGES = 200;
|
|
852
|
+
function clampPermissionRequestTimeoutMs(ms) {
|
|
853
|
+
if (!Number.isFinite(ms) || ms <= 0) return DEFAULT_PERMISSION_REQUEST_TIMEOUT_MS;
|
|
854
|
+
return Math.min(ms, MAX_PERMISSION_REQUEST_TIMEOUT_MS);
|
|
855
|
+
}
|
|
511
856
|
function classifyError(err, abortSignal) {
|
|
512
857
|
if (abortSignal.aborted) return "abort";
|
|
513
858
|
if (err instanceof AbortError || err instanceof Error && err.name === "AbortError") {
|
|
@@ -532,12 +877,16 @@ function describeTool(toolName, toolCache) {
|
|
|
532
877
|
return found?.description;
|
|
533
878
|
}
|
|
534
879
|
function sdkResultToAgentResult(result) {
|
|
880
|
+
const sessionTotalTurns = result.session_total_turns;
|
|
881
|
+
const sessionTotalCostUsd = result.session_total_cost_usd;
|
|
535
882
|
const base = {
|
|
536
883
|
sessionId: result.session_id,
|
|
537
884
|
durationMs: result.duration_ms,
|
|
538
885
|
durationApiMs: result.duration_api_ms,
|
|
539
886
|
numTurns: result.num_turns,
|
|
540
887
|
totalCostUsd: result.total_cost_usd,
|
|
888
|
+
sessionTotalTurns: typeof sessionTotalTurns === "number" ? sessionTotalTurns : void 0,
|
|
889
|
+
sessionTotalCostUsd: typeof sessionTotalCostUsd === "number" ? sessionTotalCostUsd : void 0,
|
|
541
890
|
stopReason: result.stop_reason,
|
|
542
891
|
usage: result.usage,
|
|
543
892
|
modelUsage: result.modelUsage,
|
|
@@ -649,30 +998,42 @@ function consumeQuery(params) {
|
|
|
649
998
|
return activeSessionId;
|
|
650
999
|
};
|
|
651
1000
|
let initTimeoutId;
|
|
1001
|
+
const permissionRequestTimeoutMs = clampPermissionRequestTimeoutMs(
|
|
1002
|
+
params.permissionRequestTimeoutMs
|
|
1003
|
+
);
|
|
652
1004
|
const canUseTool = async (toolName, input, options2) => {
|
|
653
1005
|
const sessionId = await getSessionId();
|
|
1006
|
+
const normalizedInput = normalizeToolInput(toolName, input, params.platform);
|
|
654
1007
|
const sessionInfo = params.sessionManager.get(sessionId);
|
|
655
1008
|
if (sessionInfo) {
|
|
656
1009
|
if (Array.isArray(sessionInfo.disallowedTools) && sessionInfo.disallowedTools.includes(toolName)) {
|
|
657
1010
|
return { behavior: "deny", message: `Tool '${toolName}' is disallowed by session policy.` };
|
|
658
1011
|
}
|
|
659
1012
|
if (!options2.blockedPath && Array.isArray(sessionInfo.allowedTools) && sessionInfo.allowedTools.includes(toolName)) {
|
|
660
|
-
return {
|
|
1013
|
+
return {
|
|
1014
|
+
behavior: "allow",
|
|
1015
|
+
updatedInput: normalizePermissionUpdatedInput(normalizedInput)
|
|
1016
|
+
};
|
|
661
1017
|
}
|
|
662
1018
|
}
|
|
663
1019
|
const requestId = `${options2.toolUseID}:${toolName}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
|
|
1020
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1021
|
+
const timeoutMs = permissionRequestTimeoutMs;
|
|
1022
|
+
const expiresAt = new Date(Date.now() + timeoutMs).toISOString();
|
|
664
1023
|
const record = {
|
|
665
1024
|
requestId,
|
|
666
1025
|
toolName,
|
|
667
|
-
input,
|
|
668
|
-
summary: summarizePermission(toolName,
|
|
1026
|
+
input: normalizedInput,
|
|
1027
|
+
summary: summarizePermission(toolName, normalizedInput),
|
|
669
1028
|
description: describeTool(toolName, params.toolCache),
|
|
670
1029
|
decisionReason: options2.decisionReason,
|
|
671
1030
|
blockedPath: options2.blockedPath,
|
|
672
1031
|
toolUseID: options2.toolUseID,
|
|
673
1032
|
agentID: options2.agentID,
|
|
674
1033
|
suggestions: options2.suggestions,
|
|
675
|
-
createdAt
|
|
1034
|
+
createdAt,
|
|
1035
|
+
timeoutMs,
|
|
1036
|
+
expiresAt
|
|
676
1037
|
};
|
|
677
1038
|
return await new Promise((resolve) => {
|
|
678
1039
|
let finished = false;
|
|
@@ -694,10 +1055,14 @@ function consumeQuery(params) {
|
|
|
694
1055
|
sessionId,
|
|
695
1056
|
record,
|
|
696
1057
|
finish,
|
|
697
|
-
|
|
1058
|
+
permissionRequestTimeoutMs
|
|
698
1059
|
);
|
|
699
1060
|
if (!registered) {
|
|
700
|
-
finish({
|
|
1061
|
+
finish({
|
|
1062
|
+
behavior: "deny",
|
|
1063
|
+
message: "Session no longer exists.",
|
|
1064
|
+
interrupt: true
|
|
1065
|
+
});
|
|
701
1066
|
return;
|
|
702
1067
|
}
|
|
703
1068
|
options2.signal.addEventListener("abort", abortListener, { once: true });
|
|
@@ -732,6 +1097,7 @@ function consumeQuery(params) {
|
|
|
732
1097
|
};
|
|
733
1098
|
const done = (async () => {
|
|
734
1099
|
const preInit = [];
|
|
1100
|
+
let preInitDropped = 0;
|
|
735
1101
|
if (shouldWaitForInit) {
|
|
736
1102
|
initTimeoutId = setTimeout(() => {
|
|
737
1103
|
close();
|
|
@@ -756,6 +1122,13 @@ function consumeQuery(params) {
|
|
|
756
1122
|
sessionIdResolved = true;
|
|
757
1123
|
resolveSessionId(activeSessionId);
|
|
758
1124
|
if (initTimeoutId) clearTimeout(initTimeoutId);
|
|
1125
|
+
if (preInitDropped > 0) {
|
|
1126
|
+
params.sessionManager.pushEvent(activeSessionId, {
|
|
1127
|
+
type: "progress",
|
|
1128
|
+
data: { type: "pre_init_dropped", dropped: preInitDropped },
|
|
1129
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
759
1132
|
for (const buffered of preInit) {
|
|
760
1133
|
const event2 = messageToEvent(buffered);
|
|
761
1134
|
if (!event2) continue;
|
|
@@ -771,35 +1144,50 @@ function consumeQuery(params) {
|
|
|
771
1144
|
}
|
|
772
1145
|
if (shouldWaitForInit && !sessionIdResolved) {
|
|
773
1146
|
preInit.push(message);
|
|
1147
|
+
if (preInit.length > MAX_PREINIT_BUFFER_MESSAGES) {
|
|
1148
|
+
preInit.shift();
|
|
1149
|
+
preInitDropped++;
|
|
1150
|
+
}
|
|
774
1151
|
continue;
|
|
775
1152
|
}
|
|
776
1153
|
if (message.type === "result") {
|
|
777
1154
|
const sessionId2 = message.session_id ?? await getSessionId();
|
|
778
1155
|
const agentResult = sdkResultToAgentResult(message);
|
|
1156
|
+
const current = params.sessionManager.get(sessionId2);
|
|
1157
|
+
const previousTotalTurns = current?.totalTurns ?? 0;
|
|
1158
|
+
const previousTotalCostUsd = current?.totalCostUsd ?? 0;
|
|
1159
|
+
const computedTotalTurns = previousTotalTurns + agentResult.numTurns;
|
|
1160
|
+
const computedTotalCostUsd = previousTotalCostUsd + agentResult.totalCostUsd;
|
|
1161
|
+
const sessionTotalTurns = typeof agentResult.sessionTotalTurns === "number" ? Math.max(agentResult.sessionTotalTurns, computedTotalTurns) : computedTotalTurns;
|
|
1162
|
+
const sessionTotalCostUsd = typeof agentResult.sessionTotalCostUsd === "number" ? Math.max(agentResult.sessionTotalCostUsd, computedTotalCostUsd) : computedTotalCostUsd;
|
|
1163
|
+
const resultWithSessionTotals = {
|
|
1164
|
+
...agentResult,
|
|
1165
|
+
sessionTotalTurns,
|
|
1166
|
+
sessionTotalCostUsd
|
|
1167
|
+
};
|
|
779
1168
|
const stored = {
|
|
780
1169
|
type: agentResult.isError ? "error" : "result",
|
|
781
|
-
result:
|
|
1170
|
+
result: resultWithSessionTotals,
|
|
782
1171
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
783
1172
|
};
|
|
784
1173
|
params.sessionManager.setResult(sessionId2, stored);
|
|
785
1174
|
params.sessionManager.clearTerminalEvents(sessionId2);
|
|
786
1175
|
params.sessionManager.pushEvent(sessionId2, {
|
|
787
1176
|
type: agentResult.isError ? "error" : "result",
|
|
788
|
-
data:
|
|
1177
|
+
data: resultWithSessionTotals,
|
|
789
1178
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
790
1179
|
});
|
|
791
|
-
const current = params.sessionManager.get(sessionId2);
|
|
792
1180
|
if (current && current.status !== "cancelled") {
|
|
793
1181
|
params.sessionManager.update(sessionId2, {
|
|
794
1182
|
status: agentResult.isError ? "error" : "idle",
|
|
795
|
-
totalTurns:
|
|
796
|
-
totalCostUsd:
|
|
1183
|
+
totalTurns: sessionTotalTurns,
|
|
1184
|
+
totalCostUsd: sessionTotalCostUsd,
|
|
797
1185
|
abortController: void 0
|
|
798
1186
|
});
|
|
799
1187
|
} else if (current) {
|
|
800
1188
|
params.sessionManager.update(sessionId2, {
|
|
801
|
-
totalTurns:
|
|
802
|
-
totalCostUsd:
|
|
1189
|
+
totalTurns: sessionTotalTurns,
|
|
1190
|
+
totalCostUsd: sessionTotalCostUsd,
|
|
803
1191
|
abortController: void 0
|
|
804
1192
|
});
|
|
805
1193
|
}
|
|
@@ -948,15 +1336,23 @@ function consumeQuery(params) {
|
|
|
948
1336
|
|
|
949
1337
|
// src/utils/resume-token.ts
|
|
950
1338
|
import { createHmac } from "crypto";
|
|
1339
|
+
function getResumeSecrets() {
|
|
1340
|
+
const raw = process.env.CLAUDE_CODE_MCP_RESUME_SECRET;
|
|
1341
|
+
if (typeof raw !== "string") return [];
|
|
1342
|
+
return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
1343
|
+
}
|
|
951
1344
|
function getResumeSecret() {
|
|
952
|
-
|
|
953
|
-
if (typeof secret !== "string") return void 0;
|
|
954
|
-
const trimmed = secret.trim();
|
|
955
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
1345
|
+
return getResumeSecrets()[0];
|
|
956
1346
|
}
|
|
957
1347
|
function computeResumeToken(sessionId, secret) {
|
|
958
1348
|
return createHmac("sha256", secret).update(sessionId).digest("base64url");
|
|
959
1349
|
}
|
|
1350
|
+
function isValidResumeToken(sessionId, token, secrets) {
|
|
1351
|
+
for (const secret of secrets) {
|
|
1352
|
+
if (computeResumeToken(sessionId, secret) === token) return true;
|
|
1353
|
+
}
|
|
1354
|
+
return false;
|
|
1355
|
+
}
|
|
960
1356
|
|
|
961
1357
|
// src/utils/race-with-abort.ts
|
|
962
1358
|
function raceWithAbort(promise, signal, onAbort) {
|
|
@@ -983,7 +1379,7 @@ function raceWithAbort(promise, signal, onAbort) {
|
|
|
983
1379
|
|
|
984
1380
|
// src/utils/build-options.ts
|
|
985
1381
|
function buildOptions(src) {
|
|
986
|
-
const opts = { cwd: src.cwd };
|
|
1382
|
+
const opts = { cwd: normalizeWindowsPathLike(src.cwd) };
|
|
987
1383
|
if (src.allowedTools !== void 0) opts.allowedTools = src.allowedTools;
|
|
988
1384
|
if (src.disallowedTools !== void 0) opts.disallowedTools = src.disallowedTools;
|
|
989
1385
|
if (src.tools !== void 0) opts.tools = src.tools;
|
|
@@ -995,13 +1391,13 @@ function buildOptions(src) {
|
|
|
995
1391
|
if (src.effort !== void 0) opts.effort = src.effort;
|
|
996
1392
|
if (src.betas !== void 0) opts.betas = src.betas;
|
|
997
1393
|
if (src.additionalDirectories !== void 0)
|
|
998
|
-
opts.additionalDirectories = src.additionalDirectories;
|
|
1394
|
+
opts.additionalDirectories = normalizeWindowsPathArray(src.additionalDirectories);
|
|
999
1395
|
if (src.outputFormat !== void 0) opts.outputFormat = src.outputFormat;
|
|
1000
1396
|
if (src.thinking !== void 0) opts.thinking = src.thinking;
|
|
1001
1397
|
if (src.persistSession !== void 0) opts.persistSession = src.persistSession;
|
|
1002
1398
|
if (src.resumeSessionAt !== void 0) opts.resumeSessionAt = src.resumeSessionAt;
|
|
1003
1399
|
if (src.pathToClaudeCodeExecutable !== void 0)
|
|
1004
|
-
opts.pathToClaudeCodeExecutable = src.pathToClaudeCodeExecutable;
|
|
1400
|
+
opts.pathToClaudeCodeExecutable = normalizeWindowsPathLike(src.pathToClaudeCodeExecutable);
|
|
1005
1401
|
if (src.agent !== void 0) opts.agent = src.agent;
|
|
1006
1402
|
if (src.mcpServers !== void 0) opts.mcpServers = src.mcpServers;
|
|
1007
1403
|
if (src.sandbox !== void 0) opts.sandbox = src.sandbox;
|
|
@@ -1014,11 +1410,48 @@ function buildOptions(src) {
|
|
|
1014
1410
|
if (src.settingSources !== void 0) opts.settingSources = src.settingSources;
|
|
1015
1411
|
else opts.settingSources = DEFAULT_SETTING_SOURCES;
|
|
1016
1412
|
if (src.debug !== void 0) opts.debug = src.debug;
|
|
1017
|
-
if (src.debugFile !== void 0) opts.debugFile = src.debugFile;
|
|
1413
|
+
if (src.debugFile !== void 0) opts.debugFile = normalizeWindowsPathLike(src.debugFile);
|
|
1018
1414
|
if (src.env !== void 0) opts.env = { ...process.env, ...src.env };
|
|
1019
1415
|
return opts;
|
|
1020
1416
|
}
|
|
1021
1417
|
|
|
1418
|
+
// src/utils/session-create.ts
|
|
1419
|
+
function toSessionCreateParams(input) {
|
|
1420
|
+
const src = input.source;
|
|
1421
|
+
return {
|
|
1422
|
+
sessionId: input.sessionId,
|
|
1423
|
+
cwd: src.cwd,
|
|
1424
|
+
model: src.model,
|
|
1425
|
+
permissionMode: input.permissionMode,
|
|
1426
|
+
allowedTools: src.allowedTools,
|
|
1427
|
+
disallowedTools: src.disallowedTools,
|
|
1428
|
+
tools: src.tools,
|
|
1429
|
+
maxTurns: src.maxTurns,
|
|
1430
|
+
systemPrompt: src.systemPrompt,
|
|
1431
|
+
agents: src.agents,
|
|
1432
|
+
maxBudgetUsd: src.maxBudgetUsd,
|
|
1433
|
+
effort: src.effort,
|
|
1434
|
+
betas: src.betas,
|
|
1435
|
+
additionalDirectories: src.additionalDirectories,
|
|
1436
|
+
outputFormat: src.outputFormat,
|
|
1437
|
+
thinking: src.thinking,
|
|
1438
|
+
persistSession: src.persistSession,
|
|
1439
|
+
pathToClaudeCodeExecutable: src.pathToClaudeCodeExecutable,
|
|
1440
|
+
agent: src.agent,
|
|
1441
|
+
mcpServers: src.mcpServers,
|
|
1442
|
+
sandbox: src.sandbox,
|
|
1443
|
+
fallbackModel: src.fallbackModel,
|
|
1444
|
+
enableFileCheckpointing: src.enableFileCheckpointing,
|
|
1445
|
+
includePartialMessages: src.includePartialMessages,
|
|
1446
|
+
strictMcpConfig: src.strictMcpConfig,
|
|
1447
|
+
settingSources: src.settingSources ?? DEFAULT_SETTING_SOURCES,
|
|
1448
|
+
debug: src.debug,
|
|
1449
|
+
debugFile: src.debugFile,
|
|
1450
|
+
env: src.env,
|
|
1451
|
+
abortController: input.abortController
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1022
1455
|
// src/tools/claude-code.ts
|
|
1023
1456
|
async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, requestSignal) {
|
|
1024
1457
|
const cwd = input.cwd !== void 0 ? input.cwd : serverCwd;
|
|
@@ -1029,10 +1462,28 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1029
1462
|
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a non-empty string.`
|
|
1030
1463
|
};
|
|
1031
1464
|
}
|
|
1465
|
+
if (!sessionManager.hasCapacityFor(1)) {
|
|
1466
|
+
return {
|
|
1467
|
+
sessionId: "",
|
|
1468
|
+
status: "error",
|
|
1469
|
+
error: `Error [${"RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */}]: Too many sessions (limit: ${sessionManager.getMaxSessions()}).`
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1032
1472
|
const abortController = new AbortController();
|
|
1033
1473
|
const adv = input.advanced ?? {};
|
|
1034
1474
|
const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
|
|
1035
|
-
const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ?? 1e4;
|
|
1475
|
+
const sessionInitTimeoutMs = adv.sessionInitTimeoutMs ?? input.sessionInitTimeoutMs ?? 1e4;
|
|
1476
|
+
const compatWarnings = [];
|
|
1477
|
+
if (input.sessionInitTimeoutMs !== void 0) {
|
|
1478
|
+
compatWarnings.push(
|
|
1479
|
+
"Top-level sessionInitTimeoutMs for claude_code is a compatibility alias; prefer advanced.sessionInitTimeoutMs."
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
if (input.sessionInitTimeoutMs !== void 0 && adv.sessionInitTimeoutMs !== void 0 && input.sessionInitTimeoutMs !== adv.sessionInitTimeoutMs) {
|
|
1483
|
+
compatWarnings.push(
|
|
1484
|
+
`Both advanced.sessionInitTimeoutMs (${adv.sessionInitTimeoutMs}) and top-level sessionInitTimeoutMs (${input.sessionInitTimeoutMs}) were provided; using advanced.sessionInitTimeoutMs.`
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1036
1487
|
const flat = {
|
|
1037
1488
|
cwd,
|
|
1038
1489
|
allowedTools: input.allowedTools,
|
|
@@ -1040,52 +1491,37 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1040
1491
|
maxTurns: input.maxTurns,
|
|
1041
1492
|
model: input.model,
|
|
1042
1493
|
systemPrompt: input.systemPrompt,
|
|
1043
|
-
...adv
|
|
1494
|
+
...adv,
|
|
1495
|
+
effort: input.effort ?? adv.effort,
|
|
1496
|
+
thinking: input.thinking ?? adv.thinking
|
|
1497
|
+
};
|
|
1498
|
+
const normalizedFlat = {
|
|
1499
|
+
...flat,
|
|
1500
|
+
cwd: normalizeWindowsPathLike(flat.cwd),
|
|
1501
|
+
additionalDirectories: flat.additionalDirectories !== void 0 ? normalizeWindowsPathArray(flat.additionalDirectories) : void 0,
|
|
1502
|
+
debugFile: flat.debugFile !== void 0 ? normalizeWindowsPathLike(flat.debugFile) : void 0,
|
|
1503
|
+
pathToClaudeCodeExecutable: flat.pathToClaudeCodeExecutable !== void 0 ? normalizeWindowsPathLike(flat.pathToClaudeCodeExecutable) : void 0
|
|
1044
1504
|
};
|
|
1045
1505
|
try {
|
|
1046
1506
|
const handle = consumeQuery({
|
|
1047
1507
|
mode: "start",
|
|
1048
1508
|
prompt: input.prompt,
|
|
1049
1509
|
abortController,
|
|
1050
|
-
options: buildOptions(
|
|
1510
|
+
options: buildOptions(normalizedFlat),
|
|
1051
1511
|
permissionRequestTimeoutMs,
|
|
1052
1512
|
sessionInitTimeoutMs,
|
|
1053
1513
|
sessionManager,
|
|
1054
1514
|
toolCache,
|
|
1055
1515
|
onInit: (init) => {
|
|
1056
1516
|
if (sessionManager.get(init.session_id)) return;
|
|
1057
|
-
sessionManager.create(
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
maxTurns: input.maxTurns,
|
|
1066
|
-
systemPrompt: input.systemPrompt,
|
|
1067
|
-
agents: adv.agents,
|
|
1068
|
-
maxBudgetUsd: adv.maxBudgetUsd,
|
|
1069
|
-
effort: adv.effort,
|
|
1070
|
-
betas: adv.betas,
|
|
1071
|
-
additionalDirectories: adv.additionalDirectories,
|
|
1072
|
-
outputFormat: adv.outputFormat,
|
|
1073
|
-
thinking: adv.thinking,
|
|
1074
|
-
persistSession: adv.persistSession,
|
|
1075
|
-
pathToClaudeCodeExecutable: adv.pathToClaudeCodeExecutable,
|
|
1076
|
-
agent: adv.agent,
|
|
1077
|
-
mcpServers: adv.mcpServers,
|
|
1078
|
-
sandbox: adv.sandbox,
|
|
1079
|
-
fallbackModel: adv.fallbackModel,
|
|
1080
|
-
enableFileCheckpointing: adv.enableFileCheckpointing,
|
|
1081
|
-
includePartialMessages: adv.includePartialMessages,
|
|
1082
|
-
strictMcpConfig: adv.strictMcpConfig,
|
|
1083
|
-
settingSources: adv.settingSources ?? DEFAULT_SETTING_SOURCES,
|
|
1084
|
-
debug: adv.debug,
|
|
1085
|
-
debugFile: adv.debugFile,
|
|
1086
|
-
env: adv.env,
|
|
1087
|
-
abortController
|
|
1088
|
-
});
|
|
1517
|
+
sessionManager.create(
|
|
1518
|
+
toSessionCreateParams({
|
|
1519
|
+
sessionId: init.session_id,
|
|
1520
|
+
source: normalizedFlat,
|
|
1521
|
+
permissionMode: "default",
|
|
1522
|
+
abortController
|
|
1523
|
+
})
|
|
1524
|
+
);
|
|
1089
1525
|
}
|
|
1090
1526
|
});
|
|
1091
1527
|
const sessionId = await raceWithAbort(
|
|
@@ -1098,7 +1534,8 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1098
1534
|
sessionId,
|
|
1099
1535
|
status: "running",
|
|
1100
1536
|
pollInterval: 3e3,
|
|
1101
|
-
resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0
|
|
1537
|
+
resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0,
|
|
1538
|
+
compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0
|
|
1102
1539
|
};
|
|
1103
1540
|
} catch (err) {
|
|
1104
1541
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1137,6 +1574,13 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1137
1574
|
const sessionInitTimeoutMs = input.sessionInitTimeoutMs ?? 1e4;
|
|
1138
1575
|
const existing = sessionManager.get(input.sessionId);
|
|
1139
1576
|
if (!existing) {
|
|
1577
|
+
if (!sessionManager.hasCapacityFor(1)) {
|
|
1578
|
+
return {
|
|
1579
|
+
sessionId: input.sessionId,
|
|
1580
|
+
status: "error",
|
|
1581
|
+
error: `Error [${"RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */}]: Too many sessions (limit: ${sessionManager.getMaxSessions()}).`
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1140
1584
|
const allowDiskResume = process.env.CLAUDE_CODE_MCP_ALLOW_DISK_RESUME === "1";
|
|
1141
1585
|
if (!allowDiskResume) {
|
|
1142
1586
|
return {
|
|
@@ -1145,7 +1589,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1145
1589
|
error: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`
|
|
1146
1590
|
};
|
|
1147
1591
|
}
|
|
1148
|
-
const
|
|
1592
|
+
const resumeSecrets = getResumeSecrets();
|
|
1593
|
+
const resumeSecret = resumeSecrets[0];
|
|
1149
1594
|
if (!resumeSecret) {
|
|
1150
1595
|
return {
|
|
1151
1596
|
sessionId: input.sessionId,
|
|
@@ -1161,8 +1606,7 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1161
1606
|
error: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: resumeToken is required for disk resume fallback.`
|
|
1162
1607
|
};
|
|
1163
1608
|
}
|
|
1164
|
-
|
|
1165
|
-
if (dr.resumeToken !== expectedToken) {
|
|
1609
|
+
if (!isValidResumeToken(input.sessionId, dr.resumeToken, resumeSecrets)) {
|
|
1166
1610
|
return {
|
|
1167
1611
|
sessionId: input.sessionId,
|
|
1168
1612
|
status: "error",
|
|
@@ -1172,38 +1616,27 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1172
1616
|
try {
|
|
1173
1617
|
const abortController2 = new AbortController();
|
|
1174
1618
|
const options2 = buildOptionsFromDiskResume(dr);
|
|
1175
|
-
|
|
1176
|
-
|
|
1619
|
+
if (input.effort !== void 0) options2.effort = input.effort;
|
|
1620
|
+
if (input.thinking !== void 0) options2.thinking = input.thinking;
|
|
1621
|
+
const { resumeToken: _resumeToken, ...rest } = dr;
|
|
1622
|
+
void _resumeToken;
|
|
1623
|
+
const source = {
|
|
1624
|
+
...rest,
|
|
1177
1625
|
cwd: options2.cwd ?? dr.cwd ?? "",
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
persistSession: dr.persistSession,
|
|
1193
|
-
pathToClaudeCodeExecutable: dr.pathToClaudeCodeExecutable,
|
|
1194
|
-
agent: dr.agent,
|
|
1195
|
-
mcpServers: dr.mcpServers,
|
|
1196
|
-
sandbox: dr.sandbox,
|
|
1197
|
-
fallbackModel: dr.fallbackModel,
|
|
1198
|
-
enableFileCheckpointing: dr.enableFileCheckpointing,
|
|
1199
|
-
includePartialMessages: dr.includePartialMessages,
|
|
1200
|
-
strictMcpConfig: dr.strictMcpConfig,
|
|
1201
|
-
settingSources: dr.settingSources ?? DEFAULT_SETTING_SOURCES,
|
|
1202
|
-
debug: dr.debug,
|
|
1203
|
-
debugFile: dr.debugFile,
|
|
1204
|
-
env: dr.env,
|
|
1205
|
-
abortController: abortController2
|
|
1206
|
-
});
|
|
1626
|
+
additionalDirectories: options2.additionalDirectories ?? rest.additionalDirectories,
|
|
1627
|
+
debugFile: options2.debugFile ?? rest.debugFile,
|
|
1628
|
+
pathToClaudeCodeExecutable: options2.pathToClaudeCodeExecutable ?? rest.pathToClaudeCodeExecutable,
|
|
1629
|
+
effort: input.effort ?? rest.effort,
|
|
1630
|
+
thinking: input.thinking ?? rest.thinking
|
|
1631
|
+
};
|
|
1632
|
+
sessionManager.create(
|
|
1633
|
+
toSessionCreateParams({
|
|
1634
|
+
sessionId: input.sessionId,
|
|
1635
|
+
source,
|
|
1636
|
+
permissionMode: "default",
|
|
1637
|
+
abortController: abortController2
|
|
1638
|
+
})
|
|
1639
|
+
);
|
|
1207
1640
|
try {
|
|
1208
1641
|
consumeQuery({
|
|
1209
1642
|
mode: "disk-resume",
|
|
@@ -1284,8 +1717,29 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1284
1717
|
error: current ? `Error [${"SESSION_BUSY" /* SESSION_BUSY */}]: Session is not available (status: ${current.status}).` : `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`
|
|
1285
1718
|
};
|
|
1286
1719
|
}
|
|
1287
|
-
const
|
|
1720
|
+
const session = acquired;
|
|
1721
|
+
const options = buildOptions(session);
|
|
1288
1722
|
if (input.forkSession) options.forkSession = true;
|
|
1723
|
+
if (input.forkSession && !sessionManager.hasCapacityFor(1)) {
|
|
1724
|
+
sessionManager.update(input.sessionId, { status: originalStatus, abortController: void 0 });
|
|
1725
|
+
return {
|
|
1726
|
+
sessionId: input.sessionId,
|
|
1727
|
+
status: "error",
|
|
1728
|
+
error: `Error [${"RESOURCE_EXHAUSTED" /* RESOURCE_EXHAUSTED */}]: Too many sessions (limit: ${sessionManager.getMaxSessions()}).`
|
|
1729
|
+
};
|
|
1730
|
+
}
|
|
1731
|
+
const sourceOverrides = {
|
|
1732
|
+
effort: input.effort ?? session.effort,
|
|
1733
|
+
thinking: input.thinking ?? session.thinking
|
|
1734
|
+
};
|
|
1735
|
+
if (input.effort !== void 0) options.effort = input.effort;
|
|
1736
|
+
if (input.thinking !== void 0) options.thinking = input.thinking;
|
|
1737
|
+
if (!input.forkSession && (input.effort !== void 0 || input.thinking !== void 0)) {
|
|
1738
|
+
const patch = {};
|
|
1739
|
+
if (input.effort !== void 0) patch.effort = input.effort;
|
|
1740
|
+
if (input.thinking !== void 0) patch.thinking = input.thinking;
|
|
1741
|
+
sessionManager.update(input.sessionId, patch);
|
|
1742
|
+
}
|
|
1289
1743
|
try {
|
|
1290
1744
|
const handle = consumeQuery({
|
|
1291
1745
|
mode: "resume",
|
|
@@ -1301,44 +1755,20 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1301
1755
|
onInit: (init) => {
|
|
1302
1756
|
if (!input.forkSession) return;
|
|
1303
1757
|
if (init.session_id === input.sessionId) return;
|
|
1304
|
-
if (!sessionManager.get(init.session_id)) {
|
|
1305
|
-
sessionManager.create({
|
|
1306
|
-
sessionId: init.session_id,
|
|
1307
|
-
cwd: existing.cwd,
|
|
1308
|
-
model: existing.model,
|
|
1309
|
-
permissionMode: "default",
|
|
1310
|
-
allowedTools: existing.allowedTools,
|
|
1311
|
-
disallowedTools: existing.disallowedTools,
|
|
1312
|
-
tools: existing.tools,
|
|
1313
|
-
maxTurns: existing.maxTurns,
|
|
1314
|
-
systemPrompt: existing.systemPrompt,
|
|
1315
|
-
agents: existing.agents,
|
|
1316
|
-
maxBudgetUsd: existing.maxBudgetUsd,
|
|
1317
|
-
effort: existing.effort,
|
|
1318
|
-
betas: existing.betas,
|
|
1319
|
-
additionalDirectories: existing.additionalDirectories,
|
|
1320
|
-
outputFormat: existing.outputFormat,
|
|
1321
|
-
thinking: existing.thinking,
|
|
1322
|
-
persistSession: existing.persistSession,
|
|
1323
|
-
pathToClaudeCodeExecutable: existing.pathToClaudeCodeExecutable,
|
|
1324
|
-
agent: existing.agent,
|
|
1325
|
-
mcpServers: existing.mcpServers,
|
|
1326
|
-
sandbox: existing.sandbox,
|
|
1327
|
-
fallbackModel: existing.fallbackModel,
|
|
1328
|
-
enableFileCheckpointing: existing.enableFileCheckpointing,
|
|
1329
|
-
includePartialMessages: existing.includePartialMessages,
|
|
1330
|
-
strictMcpConfig: existing.strictMcpConfig,
|
|
1331
|
-
settingSources: existing.settingSources ?? DEFAULT_SETTING_SOURCES,
|
|
1332
|
-
debug: existing.debug,
|
|
1333
|
-
debugFile: existing.debugFile,
|
|
1334
|
-
env: existing.env,
|
|
1335
|
-
abortController
|
|
1336
|
-
});
|
|
1337
|
-
}
|
|
1338
1758
|
sessionManager.update(input.sessionId, {
|
|
1339
1759
|
status: originalStatus,
|
|
1340
1760
|
abortController: void 0
|
|
1341
1761
|
});
|
|
1762
|
+
if (!sessionManager.get(init.session_id)) {
|
|
1763
|
+
sessionManager.create(
|
|
1764
|
+
toSessionCreateParams({
|
|
1765
|
+
sessionId: init.session_id,
|
|
1766
|
+
source: { ...session, ...sourceOverrides },
|
|
1767
|
+
permissionMode: "default",
|
|
1768
|
+
abortController
|
|
1769
|
+
})
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1342
1772
|
}
|
|
1343
1773
|
});
|
|
1344
1774
|
const sessionId = input.forkSession ? await raceWithAbort(
|
|
@@ -1391,48 +1821,55 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1391
1821
|
// src/tools/tool-discovery.ts
|
|
1392
1822
|
var TOOL_CATALOG = {
|
|
1393
1823
|
Bash: {
|
|
1394
|
-
description: "Run shell commands
|
|
1824
|
+
description: "Run shell commands",
|
|
1395
1825
|
category: "execute"
|
|
1396
1826
|
},
|
|
1397
|
-
Read: {
|
|
1827
|
+
Read: {
|
|
1828
|
+
description: "Read file contents (large files: use offset/limit or Grep)",
|
|
1829
|
+
category: "file_read"
|
|
1830
|
+
},
|
|
1398
1831
|
Write: {
|
|
1399
|
-
description: "Create
|
|
1832
|
+
description: "Create or overwrite files",
|
|
1400
1833
|
category: "file_write"
|
|
1401
1834
|
},
|
|
1402
1835
|
Edit: {
|
|
1403
|
-
description: "
|
|
1836
|
+
description: "Targeted edits (replace_all is substring-based)",
|
|
1404
1837
|
category: "file_write"
|
|
1405
1838
|
},
|
|
1406
1839
|
Glob: {
|
|
1407
|
-
description: "Find files by
|
|
1840
|
+
description: "Find files by glob pattern",
|
|
1408
1841
|
category: "file_read"
|
|
1409
1842
|
},
|
|
1410
1843
|
Grep: {
|
|
1411
|
-
description: "Search
|
|
1844
|
+
description: "Search file contents (regex)",
|
|
1412
1845
|
category: "file_read"
|
|
1413
1846
|
},
|
|
1414
1847
|
NotebookEdit: {
|
|
1415
|
-
description: "Edit
|
|
1848
|
+
description: "Edit Jupyter notebook cells (Windows paths normalized)",
|
|
1416
1849
|
category: "file_write"
|
|
1417
1850
|
},
|
|
1418
1851
|
WebFetch: {
|
|
1419
|
-
description: "
|
|
1852
|
+
description: "Fetch web page or API content",
|
|
1420
1853
|
category: "network"
|
|
1421
1854
|
},
|
|
1422
|
-
WebSearch: { description: "
|
|
1855
|
+
WebSearch: { description: "Web search", category: "network" },
|
|
1423
1856
|
Task: {
|
|
1424
|
-
description: "Spawn
|
|
1857
|
+
description: "Spawn subagent (must be in allowedTools)",
|
|
1425
1858
|
category: "agent"
|
|
1426
1859
|
},
|
|
1427
|
-
TaskOutput: { description: "Get
|
|
1428
|
-
TaskStop: { description: "Cancel
|
|
1860
|
+
TaskOutput: { description: "Get subagent output", category: "agent" },
|
|
1861
|
+
TaskStop: { description: "Cancel subagent", category: "agent" },
|
|
1429
1862
|
TodoWrite: {
|
|
1430
|
-
description: "
|
|
1863
|
+
description: "Task/todo checklist",
|
|
1431
1864
|
category: "agent"
|
|
1432
1865
|
},
|
|
1433
1866
|
AskUserQuestion: {
|
|
1434
|
-
description: "Ask
|
|
1867
|
+
description: "Ask user a question",
|
|
1435
1868
|
category: "interaction"
|
|
1869
|
+
},
|
|
1870
|
+
TeamDelete: {
|
|
1871
|
+
description: "Delete team (may need shutdown_approved first)",
|
|
1872
|
+
category: "agent"
|
|
1436
1873
|
}
|
|
1437
1874
|
};
|
|
1438
1875
|
function uniq(items) {
|
|
@@ -1451,8 +1888,10 @@ function defaultCatalogTools() {
|
|
|
1451
1888
|
}
|
|
1452
1889
|
var ToolDiscoveryCache = class {
|
|
1453
1890
|
cached;
|
|
1454
|
-
|
|
1891
|
+
onUpdated;
|
|
1892
|
+
constructor(initial, onUpdated) {
|
|
1455
1893
|
this.cached = initial ?? defaultCatalogTools();
|
|
1894
|
+
this.onUpdated = onUpdated;
|
|
1456
1895
|
}
|
|
1457
1896
|
getTools() {
|
|
1458
1897
|
return this.cached;
|
|
@@ -1461,7 +1900,13 @@ var ToolDiscoveryCache = class {
|
|
|
1461
1900
|
const discovered = discoverToolsFromInit(initTools);
|
|
1462
1901
|
const next = mergeToolLists(discovered, defaultCatalogTools());
|
|
1463
1902
|
const updated = JSON.stringify(next) !== JSON.stringify(this.cached);
|
|
1464
|
-
if (updated)
|
|
1903
|
+
if (updated) {
|
|
1904
|
+
this.cached = next;
|
|
1905
|
+
try {
|
|
1906
|
+
this.onUpdated?.(this.cached);
|
|
1907
|
+
} catch {
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1465
1910
|
return { updated, tools: this.cached };
|
|
1466
1911
|
}
|
|
1467
1912
|
};
|
|
@@ -1486,9 +1931,8 @@ function groupByCategory(tools) {
|
|
|
1486
1931
|
function buildInternalToolsDescription(tools) {
|
|
1487
1932
|
const grouped = groupByCategory(tools);
|
|
1488
1933
|
const categories = Object.keys(grouped).sort((a, b) => a.localeCompare(b));
|
|
1489
|
-
let desc =
|
|
1490
|
-
desc += "
|
|
1491
|
-
desc += "Internal tools available to the agent (use allowedTools/disallowedTools to control approval policy; authoritative list returned by claude_code_check with includeTools=true):\n";
|
|
1934
|
+
let desc = "Start a Claude Code session and return sessionId.\nUse claude_code_check to poll events/results and handle permissions.\n\n";
|
|
1935
|
+
desc += "Internal tools (authoritative list: includeTools=true in claude_code_check):\n";
|
|
1492
1936
|
for (const category of categories) {
|
|
1493
1937
|
desc += `
|
|
1494
1938
|
[${category}]
|
|
@@ -1498,7 +1942,7 @@ function buildInternalToolsDescription(tools) {
|
|
|
1498
1942
|
`;
|
|
1499
1943
|
}
|
|
1500
1944
|
}
|
|
1501
|
-
desc +=
|
|
1945
|
+
desc += "\nPermission control: allowedTools auto-approves; disallowedTools always denies; others require approval.\n";
|
|
1502
1946
|
return desc;
|
|
1503
1947
|
}
|
|
1504
1948
|
|
|
@@ -1557,6 +2001,117 @@ function toEvents(events, opts) {
|
|
|
1557
2001
|
return { id: e.id, type: e.type, data: e.data, timestamp: e.timestamp };
|
|
1558
2002
|
});
|
|
1559
2003
|
}
|
|
2004
|
+
function uniqSorted(values) {
|
|
2005
|
+
if (!Array.isArray(values) || values.length === 0) return [];
|
|
2006
|
+
const filtered = values.filter((v) => typeof v === "string").map((v) => v.trim()).filter(Boolean);
|
|
2007
|
+
return Array.from(new Set(filtered)).sort((a, b) => a.localeCompare(b));
|
|
2008
|
+
}
|
|
2009
|
+
function detectPathCompatibilityWarnings(session) {
|
|
2010
|
+
if (!session) return [];
|
|
2011
|
+
const cwd = session.cwd;
|
|
2012
|
+
if (typeof cwd !== "string" || cwd.trim() === "") return [];
|
|
2013
|
+
const warnings = [];
|
|
2014
|
+
if (process.platform === "win32" && cwd.startsWith("/") && !cwd.startsWith("//")) {
|
|
2015
|
+
warnings.push(
|
|
2016
|
+
`cwd '${cwd}' looks POSIX-style on Windows. Consider using a Windows path (e.g. C:\\\\repo) to avoid path compatibility issues.`
|
|
2017
|
+
);
|
|
2018
|
+
}
|
|
2019
|
+
if (process.platform !== "win32" && /^[a-zA-Z]:[\\/]/.test(cwd)) {
|
|
2020
|
+
warnings.push(
|
|
2021
|
+
`cwd '${cwd}' looks Windows-style on ${process.platform}. This may indicate cross-platform path mismatch (for example WSL boundary).`
|
|
2022
|
+
);
|
|
2023
|
+
}
|
|
2024
|
+
if (process.platform !== "win32" && cwd.startsWith("\\\\")) {
|
|
2025
|
+
warnings.push(
|
|
2026
|
+
`cwd '${cwd}' looks like a Windows UNC path on ${process.platform}. Confirm MCP client/server run on the same platform.`
|
|
2027
|
+
);
|
|
2028
|
+
}
|
|
2029
|
+
return warnings;
|
|
2030
|
+
}
|
|
2031
|
+
function isPosixHomePath(value) {
|
|
2032
|
+
return /^\/home\/[^/]+(?:\/|$)/.test(value);
|
|
2033
|
+
}
|
|
2034
|
+
function detectPendingPermissionPathWarnings(pending, session) {
|
|
2035
|
+
if (process.platform !== "win32" || pending.length === 0) return [];
|
|
2036
|
+
const cwd = session?.cwd;
|
|
2037
|
+
const cwdHint = typeof cwd === "string" && cwd.trim() !== "" ? ` under cwd '${cwd}'` : " under the current cwd";
|
|
2038
|
+
const warnings = [];
|
|
2039
|
+
for (const req of pending) {
|
|
2040
|
+
const candidates = [];
|
|
2041
|
+
if (typeof req.blockedPath === "string") candidates.push(req.blockedPath);
|
|
2042
|
+
const filePath = req.input.file_path;
|
|
2043
|
+
if (typeof filePath === "string") candidates.push(filePath);
|
|
2044
|
+
const badPath = candidates.find((p) => isPosixHomePath(p));
|
|
2045
|
+
if (!badPath) continue;
|
|
2046
|
+
warnings.push(
|
|
2047
|
+
`Permission request '${req.requestId}' uses POSIX home path '${badPath}' on Windows. Prefer an absolute Windows path${cwdHint} to avoid out-of-bounds permission prompts.`
|
|
2048
|
+
);
|
|
2049
|
+
}
|
|
2050
|
+
return warnings;
|
|
2051
|
+
}
|
|
2052
|
+
function computeToolValidation(session, initTools) {
|
|
2053
|
+
if (!session) return { summary: void 0, warnings: [] };
|
|
2054
|
+
const allowedTools = uniqSorted(session.allowedTools);
|
|
2055
|
+
const disallowedTools = uniqSorted(session.disallowedTools);
|
|
2056
|
+
if (allowedTools.length === 0 && disallowedTools.length === 0) {
|
|
2057
|
+
return { summary: void 0, warnings: [] };
|
|
2058
|
+
}
|
|
2059
|
+
if (!Array.isArray(initTools) || initTools.length === 0) {
|
|
2060
|
+
return {
|
|
2061
|
+
summary: {
|
|
2062
|
+
runtimeToolsKnown: false,
|
|
2063
|
+
unknownAllowedTools: [],
|
|
2064
|
+
unknownDisallowedTools: []
|
|
2065
|
+
},
|
|
2066
|
+
warnings: [
|
|
2067
|
+
"Runtime tool list is not available yet; unknown allowedTools/disallowedTools names cannot be validated until system/init tools arrive."
|
|
2068
|
+
]
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
const runtime = new Set(
|
|
2072
|
+
initTools.filter((name) => typeof name === "string").map((name) => name.trim()).filter(Boolean)
|
|
2073
|
+
);
|
|
2074
|
+
const unknownAllowedTools = allowedTools.filter((name) => !runtime.has(name));
|
|
2075
|
+
const unknownDisallowedTools = disallowedTools.filter((name) => !runtime.has(name));
|
|
2076
|
+
const warnings = [];
|
|
2077
|
+
if (unknownAllowedTools.length > 0) {
|
|
2078
|
+
warnings.push(
|
|
2079
|
+
`Unknown allowedTools (not present in runtime tools): ${unknownAllowedTools.join(", ")}.`
|
|
2080
|
+
);
|
|
2081
|
+
}
|
|
2082
|
+
if (unknownDisallowedTools.length > 0) {
|
|
2083
|
+
warnings.push(
|
|
2084
|
+
`Unknown disallowedTools (not present in runtime tools): ${unknownDisallowedTools.join(", ")}.`
|
|
2085
|
+
);
|
|
2086
|
+
}
|
|
2087
|
+
return {
|
|
2088
|
+
summary: {
|
|
2089
|
+
runtimeToolsKnown: true,
|
|
2090
|
+
unknownAllowedTools,
|
|
2091
|
+
unknownDisallowedTools
|
|
2092
|
+
},
|
|
2093
|
+
warnings
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
function byteLength(value) {
|
|
2097
|
+
return new TextEncoder().encode(JSON.stringify(value)).length;
|
|
2098
|
+
}
|
|
2099
|
+
function capEventsByBytes(events, maxBytes) {
|
|
2100
|
+
if (!Number.isFinite(maxBytes) || typeof maxBytes !== "number" || maxBytes <= 0) {
|
|
2101
|
+
return { events, truncated: false };
|
|
2102
|
+
}
|
|
2103
|
+
const budget = Math.floor(maxBytes);
|
|
2104
|
+
const kept = [];
|
|
2105
|
+
let bytes = 2;
|
|
2106
|
+
for (const evt of events) {
|
|
2107
|
+
const evtBytes = byteLength(evt);
|
|
2108
|
+
const separatorBytes = kept.length === 0 ? 0 : 1;
|
|
2109
|
+
if (bytes + separatorBytes + evtBytes > budget) break;
|
|
2110
|
+
kept.push(evt);
|
|
2111
|
+
bytes += separatorBytes + evtBytes;
|
|
2112
|
+
}
|
|
2113
|
+
return { events: kept, truncated: kept.length < events.length };
|
|
2114
|
+
}
|
|
1560
2115
|
function buildResult(sessionManager, toolCache, input) {
|
|
1561
2116
|
const responseMode = input.responseMode ?? "minimal";
|
|
1562
2117
|
const po = input.pollOptions ?? {};
|
|
@@ -1569,6 +2124,7 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
1569
2124
|
const includeStructuredOutput = po.includeStructuredOutput ?? responseMode === "full";
|
|
1570
2125
|
const includeTerminalEvents = po.includeTerminalEvents ?? responseMode === "full";
|
|
1571
2126
|
const includeProgressEvents = po.includeProgressEvents ?? responseMode === "full";
|
|
2127
|
+
const maxBytes = po.maxBytes;
|
|
1572
2128
|
const maxEvents = input.maxEvents ?? (responseMode === "minimal" ? 200 : void 0);
|
|
1573
2129
|
const sessionId = input.sessionId;
|
|
1574
2130
|
const session = sessionManager.get(sessionId);
|
|
@@ -1581,7 +2137,7 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
1581
2137
|
let truncated = false;
|
|
1582
2138
|
const truncatedFields = [];
|
|
1583
2139
|
const windowEvents = maxEvents !== void 0 && rawEvents.length > maxEvents ? rawEvents.slice(0, maxEvents) : rawEvents;
|
|
1584
|
-
|
|
2140
|
+
let nextCursor = maxEvents !== void 0 && rawEvents.length > maxEvents ? windowEvents.length > 0 ? windowEvents[windowEvents.length - 1].id + 1 : rawNextCursor : rawNextCursor;
|
|
1585
2141
|
if (maxEvents !== void 0 && rawEvents.length > maxEvents) {
|
|
1586
2142
|
truncated = true;
|
|
1587
2143
|
truncatedFields.push("events");
|
|
@@ -1604,8 +2160,28 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
1604
2160
|
})();
|
|
1605
2161
|
const pending = status === "waiting_permission" ? sessionManager.listPendingPermissions(sessionId) : [];
|
|
1606
2162
|
const stored = status === "idle" || status === "error" ? sessionManager.getResult(sessionId) : void 0;
|
|
1607
|
-
const initTools =
|
|
2163
|
+
const initTools = sessionManager.getInitTools(sessionId);
|
|
1608
2164
|
const availableTools = includeTools && initTools ? discoverToolsFromInit(initTools) : void 0;
|
|
2165
|
+
const toolValidation = computeToolValidation(session, initTools);
|
|
2166
|
+
const compatWarnings = Array.from(
|
|
2167
|
+
/* @__PURE__ */ new Set([
|
|
2168
|
+
...toolValidation.warnings,
|
|
2169
|
+
...detectPathCompatibilityWarnings(session),
|
|
2170
|
+
...detectPendingPermissionPathWarnings(pending, session)
|
|
2171
|
+
])
|
|
2172
|
+
);
|
|
2173
|
+
const shapedEvents = toEvents(outputEvents, {
|
|
2174
|
+
includeUsage,
|
|
2175
|
+
includeModelUsage,
|
|
2176
|
+
includeStructuredOutput,
|
|
2177
|
+
slim: responseMode === "minimal"
|
|
2178
|
+
});
|
|
2179
|
+
const cappedEvents = capEventsByBytes(shapedEvents, maxBytes);
|
|
2180
|
+
if (cappedEvents.truncated) {
|
|
2181
|
+
truncated = true;
|
|
2182
|
+
truncatedFields.push("events_bytes");
|
|
2183
|
+
nextCursor = cappedEvents.events.length > 0 ? cappedEvents.events[cappedEvents.events.length - 1].id + 1 : cursorResetTo ?? input.cursor ?? 0;
|
|
2184
|
+
}
|
|
1609
2185
|
return {
|
|
1610
2186
|
sessionId,
|
|
1611
2187
|
status,
|
|
@@ -1613,28 +2189,32 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
1613
2189
|
cursorResetTo,
|
|
1614
2190
|
truncated: truncated ? true : void 0,
|
|
1615
2191
|
truncatedFields: truncatedFields.length > 0 ? truncatedFields : void 0,
|
|
1616
|
-
events:
|
|
1617
|
-
includeUsage,
|
|
1618
|
-
includeModelUsage,
|
|
1619
|
-
includeStructuredOutput,
|
|
1620
|
-
slim: responseMode === "minimal"
|
|
1621
|
-
}),
|
|
2192
|
+
events: cappedEvents.events,
|
|
1622
2193
|
nextCursor,
|
|
1623
2194
|
availableTools,
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
2195
|
+
toolValidation: toolValidation.summary,
|
|
2196
|
+
compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0,
|
|
2197
|
+
actions: includeActions && status === "waiting_permission" ? pending.map((req) => {
|
|
2198
|
+
const expiresMs = req.expiresAt ? Date.parse(req.expiresAt) : Number.NaN;
|
|
2199
|
+
const remainingMs = Number.isFinite(expiresMs) ? Math.max(0, expiresMs - Date.now()) : void 0;
|
|
2200
|
+
return {
|
|
2201
|
+
type: "permission",
|
|
2202
|
+
requestId: req.requestId,
|
|
2203
|
+
toolName: req.toolName,
|
|
2204
|
+
input: req.input,
|
|
2205
|
+
summary: req.summary,
|
|
2206
|
+
decisionReason: req.decisionReason,
|
|
2207
|
+
blockedPath: req.blockedPath,
|
|
2208
|
+
toolUseID: req.toolUseID,
|
|
2209
|
+
agentID: req.agentID,
|
|
2210
|
+
suggestions: req.suggestions,
|
|
2211
|
+
description: req.description,
|
|
2212
|
+
createdAt: req.createdAt,
|
|
2213
|
+
timeoutMs: req.timeoutMs,
|
|
2214
|
+
expiresAt: req.expiresAt,
|
|
2215
|
+
remainingMs
|
|
2216
|
+
};
|
|
2217
|
+
}) : void 0,
|
|
1638
2218
|
result: includeResult && stored?.result ? redactAgentResult(stored.result, {
|
|
1639
2219
|
includeUsage,
|
|
1640
2220
|
includeModelUsage,
|
|
@@ -1673,7 +2253,14 @@ function redactAgentResult(result, opts) {
|
|
|
1673
2253
|
structuredOutput: opts.includeStructuredOutput ? structuredOutput : void 0
|
|
1674
2254
|
};
|
|
1675
2255
|
}
|
|
1676
|
-
function executeClaudeCodeCheck(input, sessionManager, toolCache) {
|
|
2256
|
+
function executeClaudeCodeCheck(input, sessionManager, toolCache, requestSignal) {
|
|
2257
|
+
if (requestSignal?.aborted) {
|
|
2258
|
+
return {
|
|
2259
|
+
sessionId: input.sessionId ?? "",
|
|
2260
|
+
error: `Error [${"CANCELLED" /* CANCELLED */}]: request was cancelled.`,
|
|
2261
|
+
isError: true
|
|
2262
|
+
};
|
|
2263
|
+
}
|
|
1677
2264
|
if (typeof input.sessionId !== "string" || input.sessionId.trim() === "") {
|
|
1678
2265
|
return {
|
|
1679
2266
|
sessionId: "",
|
|
@@ -1729,7 +2316,14 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache) {
|
|
|
1729
2316
|
}
|
|
1730
2317
|
|
|
1731
2318
|
// src/tools/claude-code-session.ts
|
|
1732
|
-
function executeClaudeCodeSession(input, sessionManager) {
|
|
2319
|
+
function executeClaudeCodeSession(input, sessionManager, requestSignal) {
|
|
2320
|
+
if (requestSignal?.aborted) {
|
|
2321
|
+
return {
|
|
2322
|
+
sessions: [],
|
|
2323
|
+
message: `Error [${"CANCELLED" /* CANCELLED */}]: request was cancelled.`,
|
|
2324
|
+
isError: true
|
|
2325
|
+
};
|
|
2326
|
+
}
|
|
1733
2327
|
const toSessionJson = (s) => input.includeSensitive ? sessionManager.toSensitiveJSON(s) : sessionManager.toPublicJSON(s);
|
|
1734
2328
|
switch (input.action) {
|
|
1735
2329
|
case "list": {
|
|
@@ -1796,32 +2390,192 @@ function executeClaudeCodeSession(input, sessionManager) {
|
|
|
1796
2390
|
}
|
|
1797
2391
|
}
|
|
1798
2392
|
|
|
2393
|
+
// src/resources/register-resources.ts
|
|
2394
|
+
var RESOURCE_SCHEME = "claude-code-mcp";
|
|
2395
|
+
var RESOURCE_URIS = {
|
|
2396
|
+
serverInfo: `${RESOURCE_SCHEME}:///server-info`,
|
|
2397
|
+
internalTools: `${RESOURCE_SCHEME}:///internal-tools`,
|
|
2398
|
+
gotchas: `${RESOURCE_SCHEME}:///gotchas`,
|
|
2399
|
+
compatReport: `${RESOURCE_SCHEME}:///compat-report`
|
|
2400
|
+
};
|
|
2401
|
+
function asTextResource(uri, text, mimeType) {
|
|
2402
|
+
return {
|
|
2403
|
+
contents: [
|
|
2404
|
+
{
|
|
2405
|
+
uri: uri.toString(),
|
|
2406
|
+
text,
|
|
2407
|
+
mimeType
|
|
2408
|
+
}
|
|
2409
|
+
]
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
function registerResources(server, deps) {
|
|
2413
|
+
const serverInfoUri = new URL(RESOURCE_URIS.serverInfo);
|
|
2414
|
+
server.registerResource(
|
|
2415
|
+
"server_info",
|
|
2416
|
+
serverInfoUri.toString(),
|
|
2417
|
+
{
|
|
2418
|
+
title: "Server Info",
|
|
2419
|
+
description: "Server metadata (version/platform/runtime).",
|
|
2420
|
+
mimeType: "application/json"
|
|
2421
|
+
},
|
|
2422
|
+
() => asTextResource(
|
|
2423
|
+
serverInfoUri,
|
|
2424
|
+
JSON.stringify(
|
|
2425
|
+
{
|
|
2426
|
+
name: "claude-code-mcp",
|
|
2427
|
+
node: process.version,
|
|
2428
|
+
platform: process.platform,
|
|
2429
|
+
arch: process.arch,
|
|
2430
|
+
resources: Object.values(RESOURCE_URIS),
|
|
2431
|
+
toolCatalogCount: deps.toolCache.getTools().length
|
|
2432
|
+
},
|
|
2433
|
+
null,
|
|
2434
|
+
2
|
|
2435
|
+
),
|
|
2436
|
+
"application/json"
|
|
2437
|
+
)
|
|
2438
|
+
);
|
|
2439
|
+
const toolsUri = new URL(RESOURCE_URIS.internalTools);
|
|
2440
|
+
server.registerResource(
|
|
2441
|
+
"internal_tools",
|
|
2442
|
+
toolsUri.toString(),
|
|
2443
|
+
{
|
|
2444
|
+
title: "Internal Tools",
|
|
2445
|
+
description: "Claude Code internal tool catalog (runtime-aware).",
|
|
2446
|
+
mimeType: "application/json"
|
|
2447
|
+
},
|
|
2448
|
+
() => asTextResource(
|
|
2449
|
+
toolsUri,
|
|
2450
|
+
JSON.stringify({ tools: deps.toolCache.getTools() }, null, 2),
|
|
2451
|
+
"application/json"
|
|
2452
|
+
)
|
|
2453
|
+
);
|
|
2454
|
+
const gotchasUri = new URL(RESOURCE_URIS.gotchas);
|
|
2455
|
+
server.registerResource(
|
|
2456
|
+
"gotchas",
|
|
2457
|
+
gotchasUri.toString(),
|
|
2458
|
+
{
|
|
2459
|
+
title: "Gotchas",
|
|
2460
|
+
description: "Practical limits and gotchas when using Claude Code via this MCP server.",
|
|
2461
|
+
mimeType: "text/markdown"
|
|
2462
|
+
},
|
|
2463
|
+
() => asTextResource(
|
|
2464
|
+
gotchasUri,
|
|
2465
|
+
[
|
|
2466
|
+
"# claude-code-mcp: gotchas",
|
|
2467
|
+
"",
|
|
2468
|
+
"- Permission approvals have a timeout (default 60s, server-clamped to 5min) and auto-deny (`actions[].expiresAt`/`remainingMs`).",
|
|
2469
|
+
"- `Read` has a per-call size cap in practice (often ~256KB); for large files use `offset`/`limit` or chunk with `Grep`.",
|
|
2470
|
+
"- `Edit` with `replace_all=true` is substring replacement; if no match is found the tool returns an error.",
|
|
2471
|
+
"- On Windows, this server normalizes common MSYS-style paths (e.g. `/d/...`, `/mnt/c/...`, `/cygdrive/c/...`, `//server/share/...`) for `cwd`, `additionalDirectories`, and tool inputs that include `file_path`.",
|
|
2472
|
+
"- `TeamDelete` may require members to reach `shutdown_approved`; cleanup can be asynchronous during shutdown.",
|
|
2473
|
+
'- Skills may become available later in the same session (early calls may show "Unknown").',
|
|
2474
|
+
"- Some internal features (e.g. ToolSearch) may not appear in `availableTools` because it is derived from SDK `system/init.tools`.",
|
|
2475
|
+
""
|
|
2476
|
+
].join("\n"),
|
|
2477
|
+
"text/markdown"
|
|
2478
|
+
)
|
|
2479
|
+
);
|
|
2480
|
+
const compatReportUri = new URL(RESOURCE_URIS.compatReport);
|
|
2481
|
+
server.registerResource(
|
|
2482
|
+
"compat_report",
|
|
2483
|
+
compatReportUri.toString(),
|
|
2484
|
+
{
|
|
2485
|
+
title: "Compatibility Report",
|
|
2486
|
+
description: "Compatibility diagnostics for MCP clients and local runtime assumptions.",
|
|
2487
|
+
mimeType: "application/json"
|
|
2488
|
+
},
|
|
2489
|
+
() => {
|
|
2490
|
+
const runtimeWarnings = [];
|
|
2491
|
+
if (process.platform === "win32" && !process.env.CLAUDE_CODE_GIT_BASH_PATH) {
|
|
2492
|
+
runtimeWarnings.push(
|
|
2493
|
+
"CLAUDE_CODE_GIT_BASH_PATH is not set. Auto-detection exists, but explicit path is more reliable for GUI-launched MCP clients."
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2496
|
+
return asTextResource(
|
|
2497
|
+
compatReportUri,
|
|
2498
|
+
JSON.stringify(
|
|
2499
|
+
{
|
|
2500
|
+
transport: "stdio",
|
|
2501
|
+
samePlatformRequired: true,
|
|
2502
|
+
runtime: {
|
|
2503
|
+
node: process.version,
|
|
2504
|
+
platform: process.platform,
|
|
2505
|
+
arch: process.arch
|
|
2506
|
+
},
|
|
2507
|
+
features: {
|
|
2508
|
+
resources: true,
|
|
2509
|
+
toolsListChanged: true,
|
|
2510
|
+
resourcesListChanged: true,
|
|
2511
|
+
prompts: false,
|
|
2512
|
+
completions: false
|
|
2513
|
+
},
|
|
2514
|
+
guidance: [
|
|
2515
|
+
"Some clients cache tool descriptions at connect time. Prefer claude_code_check(pollOptions.includeTools=true) for runtime-authoritative tool lists.",
|
|
2516
|
+
"Use allowedTools/disallowedTools only with exact runtime tool names.",
|
|
2517
|
+
"This server assumes MCP client and server run on the same machine/platform."
|
|
2518
|
+
],
|
|
2519
|
+
toolCatalogCount: deps.toolCache.getTools().length,
|
|
2520
|
+
runtimeWarnings
|
|
2521
|
+
},
|
|
2522
|
+
null,
|
|
2523
|
+
2
|
|
2524
|
+
),
|
|
2525
|
+
"application/json"
|
|
2526
|
+
);
|
|
2527
|
+
}
|
|
2528
|
+
);
|
|
2529
|
+
}
|
|
2530
|
+
|
|
1799
2531
|
// src/server.ts
|
|
1800
|
-
var SERVER_VERSION = true ? "2.0
|
|
1801
|
-
function
|
|
2532
|
+
var SERVER_VERSION = true ? "2.2.0" : "0.0.0-dev";
|
|
2533
|
+
function createServerContext(serverCwd) {
|
|
1802
2534
|
const sessionManager = new SessionManager();
|
|
1803
|
-
const
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
2535
|
+
const server = new McpServer(
|
|
2536
|
+
{
|
|
2537
|
+
name: "claude-code-mcp",
|
|
2538
|
+
version: SERVER_VERSION,
|
|
2539
|
+
title: "Claude Code MCP",
|
|
2540
|
+
description: "MCP server that runs Claude Code via the Claude Agent SDK with async polling and interactive permissions.",
|
|
2541
|
+
websiteUrl: "https://github.com/xihuai18/claude-code-mcp",
|
|
2542
|
+
icons: []
|
|
2543
|
+
},
|
|
2544
|
+
{
|
|
2545
|
+
capabilities: {
|
|
2546
|
+
logging: {},
|
|
2547
|
+
tools: { listChanged: true },
|
|
2548
|
+
resources: { listChanged: true }
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
);
|
|
2552
|
+
const claudeCodeToolRef = {};
|
|
2553
|
+
const toolCache = new ToolDiscoveryCache(void 0, (tools) => {
|
|
2554
|
+
try {
|
|
2555
|
+
claudeCodeToolRef.current?.update({ description: buildInternalToolsDescription(tools) });
|
|
2556
|
+
if (server.isConnected()) {
|
|
2557
|
+
server.sendToolListChanged();
|
|
2558
|
+
}
|
|
2559
|
+
} catch {
|
|
2560
|
+
}
|
|
1807
2561
|
});
|
|
1808
2562
|
const agentDefinitionSchema = z.object({
|
|
1809
2563
|
description: z.string(),
|
|
1810
2564
|
prompt: z.string(),
|
|
1811
|
-
tools: z.array(z.string()).optional(),
|
|
1812
|
-
disallowedTools: z.array(z.string()).optional(),
|
|
1813
|
-
model: z.enum(AGENT_MODELS).optional(),
|
|
1814
|
-
maxTurns: z.number().int().positive().optional(),
|
|
1815
|
-
mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional(),
|
|
1816
|
-
skills: z.array(z.string()).optional(),
|
|
1817
|
-
criticalSystemReminder_EXPERIMENTAL: z.string().optional()
|
|
2565
|
+
tools: z.array(z.string()).optional().describe("Default: inherit"),
|
|
2566
|
+
disallowedTools: z.array(z.string()).optional().describe("Default: none"),
|
|
2567
|
+
model: z.enum(AGENT_MODELS).optional().describe("Default: inherit"),
|
|
2568
|
+
maxTurns: z.number().int().positive().optional().describe("Default: none"),
|
|
2569
|
+
mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional().describe("Default: inherit"),
|
|
2570
|
+
skills: z.array(z.string()).optional().describe("Default: none"),
|
|
2571
|
+
criticalSystemReminder_EXPERIMENTAL: z.string().optional().describe("Default: none")
|
|
1818
2572
|
});
|
|
1819
2573
|
const systemPromptSchema = z.union([
|
|
1820
2574
|
z.string(),
|
|
1821
2575
|
z.object({
|
|
1822
2576
|
type: z.literal("preset"),
|
|
1823
2577
|
preset: z.literal("claude_code"),
|
|
1824
|
-
append: z.string().optional().describe("
|
|
2578
|
+
append: z.string().optional().describe("Default: none")
|
|
1825
2579
|
})
|
|
1826
2580
|
]);
|
|
1827
2581
|
const toolsConfigSchema = z.union([
|
|
@@ -1839,47 +2593,135 @@ function createServer(serverCwd) {
|
|
|
1839
2593
|
}),
|
|
1840
2594
|
z.object({ type: z.literal("disabled") })
|
|
1841
2595
|
]);
|
|
2596
|
+
const effortOptionSchema = z.enum(EFFORT_LEVELS).optional();
|
|
2597
|
+
const thinkingOptionSchema = thinkingSchema.optional();
|
|
1842
2598
|
const outputFormatSchema = z.object({
|
|
1843
2599
|
type: z.literal("json_schema"),
|
|
1844
2600
|
schema: z.record(z.string(), z.unknown())
|
|
1845
2601
|
});
|
|
1846
|
-
const
|
|
1847
|
-
tools: toolsConfigSchema.optional().describe("
|
|
2602
|
+
const sharedOptionFieldsSchemaShape = {
|
|
2603
|
+
tools: toolsConfigSchema.optional().describe("Tool set. Default: SDK"),
|
|
1848
2604
|
persistSession: z.boolean().optional().describe("Default: true"),
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
agent: z.string().optional().describe("Primary agent name (from 'agents'). Default: none"),
|
|
2605
|
+
agents: z.record(z.string(), agentDefinitionSchema).optional().describe("Default: none"),
|
|
2606
|
+
agent: z.string().optional().describe("Default: none"),
|
|
1852
2607
|
maxBudgetUsd: z.number().positive().optional().describe("Default: none"),
|
|
1853
|
-
effort: z.enum(EFFORT_LEVELS).optional().describe("Default: SDK"),
|
|
1854
2608
|
betas: z.array(z.string()).optional().describe("Default: none"),
|
|
1855
2609
|
additionalDirectories: z.array(z.string()).optional().describe("Default: none"),
|
|
1856
|
-
outputFormat: outputFormatSchema.optional().describe("Default: none
|
|
1857
|
-
thinking: thinkingSchema.optional().describe("Default: SDK"),
|
|
2610
|
+
outputFormat: outputFormatSchema.optional().describe("Default: none"),
|
|
1858
2611
|
pathToClaudeCodeExecutable: z.string().optional().describe("Default: SDK-bundled"),
|
|
1859
2612
|
mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("Default: none"),
|
|
1860
2613
|
sandbox: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
|
|
1861
2614
|
fallbackModel: z.string().optional().describe("Default: none"),
|
|
1862
2615
|
enableFileCheckpointing: z.boolean().optional().describe("Default: false"),
|
|
1863
|
-
includePartialMessages: z.boolean().optional().describe("
|
|
2616
|
+
includePartialMessages: z.boolean().optional().describe("Default: false"),
|
|
1864
2617
|
strictMcpConfig: z.boolean().optional().describe("Default: false"),
|
|
1865
|
-
settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. []
|
|
2618
|
+
settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. []=isolation"),
|
|
1866
2619
|
debug: z.boolean().optional().describe("Default: false"),
|
|
1867
|
-
debugFile: z.string().optional().describe("
|
|
1868
|
-
env: z.record(z.string(), z.string().optional()).optional().describe("
|
|
1869
|
-
}
|
|
1870
|
-
|
|
2620
|
+
debugFile: z.string().optional().describe("Default: none"),
|
|
2621
|
+
env: z.record(z.string(), z.string().optional()).optional().describe("Default: none")
|
|
2622
|
+
};
|
|
2623
|
+
const advancedOptionFieldsSchemaShape = {
|
|
2624
|
+
...sharedOptionFieldsSchemaShape,
|
|
2625
|
+
effort: effortOptionSchema.describe("Deprecated, use top-level. Default: SDK"),
|
|
2626
|
+
thinking: thinkingOptionSchema.describe("Deprecated, use top-level. Default: SDK")
|
|
2627
|
+
};
|
|
2628
|
+
const diskResumeOptionFieldsSchemaShape = {
|
|
2629
|
+
...sharedOptionFieldsSchemaShape,
|
|
2630
|
+
effort: effortOptionSchema.describe("Default: SDK"),
|
|
2631
|
+
thinking: thinkingOptionSchema.describe("Default: SDK")
|
|
2632
|
+
};
|
|
2633
|
+
const advancedOptionsSchema = z.object({
|
|
2634
|
+
...advancedOptionFieldsSchemaShape,
|
|
2635
|
+
sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000")
|
|
2636
|
+
}).optional().describe("Default: none");
|
|
2637
|
+
const diskResumeConfigSchema = z.object({
|
|
2638
|
+
resumeToken: z.string(),
|
|
2639
|
+
cwd: z.string(),
|
|
2640
|
+
allowedTools: z.array(z.string()).optional().describe("Default: []"),
|
|
2641
|
+
disallowedTools: z.array(z.string()).optional().describe("Default: []"),
|
|
2642
|
+
maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
|
|
2643
|
+
model: z.string().optional().describe("Default: SDK"),
|
|
2644
|
+
systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
|
|
2645
|
+
resumeSessionAt: z.string().optional().describe("Default: none"),
|
|
2646
|
+
...diskResumeOptionFieldsSchemaShape
|
|
2647
|
+
}).optional().describe("Default: none");
|
|
2648
|
+
const startResultSchema = z.object({
|
|
2649
|
+
sessionId: z.string(),
|
|
2650
|
+
status: z.enum(["running", "error"]),
|
|
2651
|
+
pollInterval: z.number().optional(),
|
|
2652
|
+
resumeToken: z.string().optional(),
|
|
2653
|
+
compatWarnings: z.array(z.string()).optional(),
|
|
2654
|
+
error: z.string().optional()
|
|
2655
|
+
}).passthrough();
|
|
2656
|
+
const sessionResultSchema = z.object({
|
|
2657
|
+
sessions: z.array(z.record(z.string(), z.unknown())),
|
|
2658
|
+
message: z.string().optional(),
|
|
2659
|
+
isError: z.boolean().optional()
|
|
2660
|
+
}).passthrough();
|
|
2661
|
+
const checkEventSchema = z.object({
|
|
2662
|
+
id: z.number().int().nonnegative(),
|
|
2663
|
+
type: z.string(),
|
|
2664
|
+
data: z.unknown(),
|
|
2665
|
+
timestamp: z.string()
|
|
2666
|
+
}).passthrough();
|
|
2667
|
+
const checkActionSchema = z.object({
|
|
2668
|
+
type: z.string(),
|
|
2669
|
+
requestId: z.string().optional(),
|
|
2670
|
+
toolName: z.string().optional(),
|
|
2671
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
2672
|
+
summary: z.string().optional()
|
|
2673
|
+
}).passthrough();
|
|
2674
|
+
const checkResultSchema = z.object({
|
|
2675
|
+
sessionId: z.string(),
|
|
2676
|
+
status: z.string(),
|
|
2677
|
+
pollInterval: z.number().optional(),
|
|
2678
|
+
cursorResetTo: z.number().optional(),
|
|
2679
|
+
truncated: z.boolean().optional(),
|
|
2680
|
+
truncatedFields: z.array(z.string()).optional(),
|
|
2681
|
+
events: z.array(checkEventSchema),
|
|
2682
|
+
nextCursor: z.number().optional(),
|
|
2683
|
+
availableTools: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
2684
|
+
toolValidation: z.object({
|
|
2685
|
+
runtimeToolsKnown: z.boolean(),
|
|
2686
|
+
unknownAllowedTools: z.array(z.string()),
|
|
2687
|
+
unknownDisallowedTools: z.array(z.string())
|
|
2688
|
+
}).optional(),
|
|
2689
|
+
compatWarnings: z.array(z.string()).optional(),
|
|
2690
|
+
actions: z.array(checkActionSchema).optional(),
|
|
2691
|
+
result: z.unknown().optional(),
|
|
2692
|
+
cancelledAt: z.string().optional(),
|
|
2693
|
+
cancelledReason: z.string().optional(),
|
|
2694
|
+
cancelledSource: z.string().optional(),
|
|
2695
|
+
lastEventId: z.number().optional(),
|
|
2696
|
+
lastToolUseId: z.string().optional(),
|
|
2697
|
+
isError: z.boolean().optional(),
|
|
2698
|
+
error: z.string().optional()
|
|
2699
|
+
}).passthrough();
|
|
2700
|
+
claudeCodeToolRef.current = server.registerTool(
|
|
1871
2701
|
"claude_code",
|
|
1872
|
-
buildInternalToolsDescription(toolCache.getTools()),
|
|
1873
2702
|
{
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
2703
|
+
description: buildInternalToolsDescription(toolCache.getTools()),
|
|
2704
|
+
inputSchema: {
|
|
2705
|
+
prompt: z.string().describe("Prompt"),
|
|
2706
|
+
cwd: z.string().optional().describe("Working dir. Default: server cwd"),
|
|
2707
|
+
allowedTools: z.array(z.string()).optional().describe("Default: []"),
|
|
2708
|
+
disallowedTools: z.array(z.string()).optional().describe("Default: []"),
|
|
2709
|
+
maxTurns: z.number().int().positive().optional().describe("Default: SDK"),
|
|
2710
|
+
model: z.string().optional().describe("Default: SDK"),
|
|
2711
|
+
effort: effortOptionSchema.describe("Default: SDK"),
|
|
2712
|
+
thinking: thinkingOptionSchema.describe("Default: SDK"),
|
|
2713
|
+
systemPrompt: systemPromptSchema.optional().describe("Default: SDK"),
|
|
2714
|
+
permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000"),
|
|
2715
|
+
sessionInitTimeoutMs: z.number().int().positive().optional().describe("Deprecated, use advanced.sessionInitTimeoutMs. Default: 10000"),
|
|
2716
|
+
advanced: advancedOptionsSchema
|
|
2717
|
+
},
|
|
2718
|
+
outputSchema: startResultSchema,
|
|
2719
|
+
annotations: {
|
|
2720
|
+
readOnlyHint: false,
|
|
2721
|
+
destructiveHint: true,
|
|
2722
|
+
idempotentHint: false,
|
|
2723
|
+
openWorldHint: true
|
|
2724
|
+
}
|
|
1883
2725
|
},
|
|
1884
2726
|
async (args, extra) => {
|
|
1885
2727
|
try {
|
|
@@ -1898,76 +2740,50 @@ function createServer(serverCwd) {
|
|
|
1898
2740
|
text: JSON.stringify(result, null, 2)
|
|
1899
2741
|
}
|
|
1900
2742
|
],
|
|
2743
|
+
structuredContent: result,
|
|
1901
2744
|
isError
|
|
1902
2745
|
};
|
|
1903
2746
|
} catch (err) {
|
|
1904
2747
|
const message = err instanceof Error ? err.message : String(err);
|
|
2748
|
+
const errorResult = {
|
|
2749
|
+
sessionId: "",
|
|
2750
|
+
status: "error",
|
|
2751
|
+
error: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
|
|
2752
|
+
};
|
|
1905
2753
|
return {
|
|
1906
2754
|
content: [
|
|
1907
2755
|
{
|
|
1908
2756
|
type: "text",
|
|
1909
|
-
text:
|
|
2757
|
+
text: JSON.stringify(errorResult, null, 2)
|
|
1910
2758
|
}
|
|
1911
2759
|
],
|
|
2760
|
+
structuredContent: errorResult,
|
|
1912
2761
|
isError: true
|
|
1913
2762
|
};
|
|
1914
2763
|
}
|
|
1915
2764
|
}
|
|
1916
2765
|
);
|
|
1917
|
-
server.
|
|
2766
|
+
server.registerTool(
|
|
1918
2767
|
"claude_code_reply",
|
|
1919
|
-
`Send a follow-up message to an existing Claude Code session.
|
|
1920
|
-
|
|
1921
|
-
The agent retains full context from previous turns (files read, code analyzed, conversation history). Returns immediately \u2014 use claude_code_check to poll for the result.
|
|
1922
|
-
|
|
1923
|
-
Supports session forking (forkSession=true) to explore alternative approaches without modifying the original session.
|
|
1924
|
-
|
|
1925
|
-
Defaults:
|
|
1926
|
-
- forkSession: false
|
|
1927
|
-
- sessionInitTimeoutMs: 10000 (only used when forkSession=true)
|
|
1928
|
-
- permissionRequestTimeoutMs: 60000
|
|
1929
|
-
- Disk resume: disabled unless CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1
|
|
1930
|
-
|
|
1931
|
-
Disk resume: If the server restarted and the session is no longer in memory, set CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1 to let the agent resume from its on-disk transcript. Pass diskResumeConfig with resumeToken and session parameters.`,
|
|
1932
2768
|
{
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
effort: z.enum(EFFORT_LEVELS).optional().describe("Default: SDK"),
|
|
1952
|
-
betas: z.array(z.string()).optional().describe("Default: none"),
|
|
1953
|
-
additionalDirectories: z.array(z.string()).optional().describe("Default: none"),
|
|
1954
|
-
outputFormat: outputFormatSchema.optional().describe("Default: none"),
|
|
1955
|
-
thinking: thinkingSchema.optional().describe("Default: SDK"),
|
|
1956
|
-
resumeSessionAt: z.string().optional().describe("Resume to specific message UUID. Default: none"),
|
|
1957
|
-
pathToClaudeCodeExecutable: z.string().optional().describe("Default: SDK-bundled"),
|
|
1958
|
-
mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("Default: none"),
|
|
1959
|
-
sandbox: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
|
|
1960
|
-
fallbackModel: z.string().optional().describe("Default: none"),
|
|
1961
|
-
enableFileCheckpointing: z.boolean().optional().describe("Default: false"),
|
|
1962
|
-
includePartialMessages: z.boolean().optional().describe("Stream events to claude_code_check. Default: false"),
|
|
1963
|
-
strictMcpConfig: z.boolean().optional().describe("Default: false"),
|
|
1964
|
-
settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. [] = isolation mode"),
|
|
1965
|
-
debug: z.boolean().optional().describe("Default: false"),
|
|
1966
|
-
debugFile: z.string().optional().describe("Enables debug. Default: none"),
|
|
1967
|
-
env: z.record(z.string(), z.string().optional()).optional().describe("Default: none")
|
|
1968
|
-
}).optional().describe(
|
|
1969
|
-
"Disk resume config (needs CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1). Requires resumeToken + cwd."
|
|
1970
|
-
)
|
|
2769
|
+
description: "Send a follow-up to an existing session. Returns immediately; use claude_code_check to poll.",
|
|
2770
|
+
inputSchema: {
|
|
2771
|
+
sessionId: z.string().describe("Session ID"),
|
|
2772
|
+
prompt: z.string().describe("Prompt"),
|
|
2773
|
+
forkSession: z.boolean().optional().describe("Default: false"),
|
|
2774
|
+
effort: effortOptionSchema.describe("Default: SDK"),
|
|
2775
|
+
thinking: thinkingOptionSchema.describe("Default: SDK"),
|
|
2776
|
+
sessionInitTimeoutMs: z.number().int().positive().optional().describe("Default: 10000"),
|
|
2777
|
+
permissionRequestTimeoutMs: z.number().int().positive().optional().describe("Default: 60000"),
|
|
2778
|
+
diskResumeConfig: diskResumeConfigSchema
|
|
2779
|
+
},
|
|
2780
|
+
outputSchema: startResultSchema,
|
|
2781
|
+
annotations: {
|
|
2782
|
+
readOnlyHint: false,
|
|
2783
|
+
destructiveHint: true,
|
|
2784
|
+
idempotentHint: false,
|
|
2785
|
+
openWorldHint: true
|
|
2786
|
+
}
|
|
1971
2787
|
},
|
|
1972
2788
|
async (args, extra) => {
|
|
1973
2789
|
try {
|
|
@@ -1980,136 +2796,339 @@ Disk resume: If the server restarted and the session is no longer in memory, set
|
|
|
1980
2796
|
text: JSON.stringify(result, null, 2)
|
|
1981
2797
|
}
|
|
1982
2798
|
],
|
|
2799
|
+
structuredContent: result,
|
|
1983
2800
|
isError
|
|
1984
2801
|
};
|
|
1985
2802
|
} catch (err) {
|
|
1986
2803
|
const message = err instanceof Error ? err.message : String(err);
|
|
2804
|
+
const errorResult = {
|
|
2805
|
+
sessionId: "",
|
|
2806
|
+
status: "error",
|
|
2807
|
+
error: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
|
|
2808
|
+
};
|
|
1987
2809
|
return {
|
|
1988
2810
|
content: [
|
|
1989
2811
|
{
|
|
1990
2812
|
type: "text",
|
|
1991
|
-
text:
|
|
2813
|
+
text: JSON.stringify(errorResult, null, 2)
|
|
1992
2814
|
}
|
|
1993
2815
|
],
|
|
2816
|
+
structuredContent: errorResult,
|
|
1994
2817
|
isError: true
|
|
1995
2818
|
};
|
|
1996
2819
|
}
|
|
1997
2820
|
}
|
|
1998
2821
|
);
|
|
1999
|
-
server.
|
|
2822
|
+
server.registerTool(
|
|
2000
2823
|
"claude_code_session",
|
|
2001
|
-
`List, inspect, or cancel Claude Code sessions.
|
|
2002
|
-
|
|
2003
|
-
- action="list": Get all sessions with their status, cost, turn count, and settings.
|
|
2004
|
-
- action="get": Get full details for one session (pass sessionId). Add includeSensitive=true to also see cwd, systemPrompt, agents, and additionalDirectories.
|
|
2005
|
-
- action="cancel": Stop a running session immediately (pass sessionId).`,
|
|
2006
2824
|
{
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2825
|
+
description: "List, inspect, or cancel sessions.",
|
|
2826
|
+
inputSchema: {
|
|
2827
|
+
action: z.enum(SESSION_ACTIONS),
|
|
2828
|
+
sessionId: z.string().optional().describe("Required for get/cancel"),
|
|
2829
|
+
includeSensitive: z.boolean().optional().describe("Default: false")
|
|
2830
|
+
},
|
|
2831
|
+
outputSchema: sessionResultSchema,
|
|
2832
|
+
annotations: {
|
|
2833
|
+
readOnlyHint: false,
|
|
2834
|
+
destructiveHint: true,
|
|
2835
|
+
idempotentHint: false,
|
|
2836
|
+
openWorldHint: false
|
|
2837
|
+
}
|
|
2010
2838
|
},
|
|
2011
|
-
async (args) => {
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2839
|
+
async (args, extra) => {
|
|
2840
|
+
try {
|
|
2841
|
+
const result = executeClaudeCodeSession(args, sessionManager, extra.signal);
|
|
2842
|
+
return {
|
|
2843
|
+
content: [
|
|
2844
|
+
{
|
|
2845
|
+
type: "text",
|
|
2846
|
+
text: JSON.stringify(result, null, 2)
|
|
2847
|
+
}
|
|
2848
|
+
],
|
|
2849
|
+
structuredContent: result,
|
|
2850
|
+
isError: result.isError ?? false
|
|
2851
|
+
};
|
|
2852
|
+
} catch (err) {
|
|
2853
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2854
|
+
const errorResult = {
|
|
2855
|
+
sessions: [],
|
|
2856
|
+
message: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`,
|
|
2857
|
+
isError: true
|
|
2858
|
+
};
|
|
2859
|
+
return {
|
|
2860
|
+
content: [
|
|
2861
|
+
{
|
|
2862
|
+
type: "text",
|
|
2863
|
+
text: JSON.stringify(errorResult, null, 2)
|
|
2864
|
+
}
|
|
2865
|
+
],
|
|
2866
|
+
structuredContent: errorResult,
|
|
2867
|
+
isError: true
|
|
2868
|
+
};
|
|
2869
|
+
}
|
|
2022
2870
|
}
|
|
2023
2871
|
);
|
|
2024
|
-
server.
|
|
2872
|
+
server.registerTool(
|
|
2025
2873
|
"claude_code_check",
|
|
2026
|
-
`Query a running session for new events, retrieve the final result, or respond to permission requests.
|
|
2027
|
-
|
|
2028
|
-
Two actions:
|
|
2029
|
-
|
|
2030
|
-
Defaults (poll):
|
|
2031
|
-
- responseMode: "minimal"
|
|
2032
|
-
- minimal mode strips verbose fields from assistant messages (usage, model, id, cache_control) and filters out noisy progress events (tool_progress, auth_status)
|
|
2033
|
-
- maxEvents: 200 in minimal mode (unlimited in full mode unless maxEvents is set)
|
|
2034
|
-
|
|
2035
|
-
action="poll" \u2014 Retrieve events since the last poll.
|
|
2036
|
-
Returns events (agent output, progress updates, permission requests, errors, final result).
|
|
2037
|
-
Pass the cursor from the previous poll's nextCursor for incremental updates. Omit cursor to get all buffered events.
|
|
2038
|
-
If the agent is waiting for permission, the response includes an "actions" array with pending requests.
|
|
2039
|
-
|
|
2040
|
-
action="respond_permission" \u2014 Approve or deny a pending permission request.
|
|
2041
|
-
Pass the requestId from the actions array, plus decision="allow" or decision="deny".
|
|
2042
|
-
Approving resumes agent execution. Denying (with optional interrupt=true) can halt the entire session.
|
|
2043
|
-
The response also includes the latest poll state (events, status, etc.), so a separate poll call is not needed.`,
|
|
2044
2874
|
{
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2875
|
+
description: "Poll session events or respond to permission requests.",
|
|
2876
|
+
inputSchema: {
|
|
2877
|
+
action: z.enum(CHECK_ACTIONS),
|
|
2878
|
+
sessionId: z.string().describe("Session ID"),
|
|
2879
|
+
cursor: z.number().int().nonnegative().optional().describe("Default: 0"),
|
|
2880
|
+
responseMode: z.enum(CHECK_RESPONSE_MODES).optional().describe("Default: 'minimal'"),
|
|
2881
|
+
maxEvents: z.number().int().positive().optional().describe("Default: 200 (minimal), unlimited (full)"),
|
|
2882
|
+
requestId: z.string().optional().describe("Permission request ID"),
|
|
2883
|
+
decision: z.enum(["allow", "deny"]).optional().describe("Decision"),
|
|
2884
|
+
denyMessage: z.string().optional().describe("Default: 'Permission denied by caller'"),
|
|
2885
|
+
interrupt: z.boolean().optional().describe("Default: false"),
|
|
2886
|
+
pollOptions: z.object({
|
|
2887
|
+
includeTools: z.boolean().optional().describe("Default: false"),
|
|
2888
|
+
includeEvents: z.boolean().optional().describe("Default: true"),
|
|
2889
|
+
includeActions: z.boolean().optional().describe("Default: true"),
|
|
2890
|
+
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"),
|
|
2896
|
+
maxBytes: z.number().int().positive().optional().describe("Default: unlimited")
|
|
2897
|
+
}).optional().describe("Default: none"),
|
|
2898
|
+
permissionOptions: z.object({
|
|
2899
|
+
updatedInput: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
|
|
2900
|
+
updatedPermissions: z.array(z.record(z.string(), z.unknown())).optional().describe("Default: none")
|
|
2901
|
+
}).optional().describe("Default: none")
|
|
2902
|
+
},
|
|
2903
|
+
outputSchema: checkResultSchema,
|
|
2904
|
+
annotations: {
|
|
2905
|
+
readOnlyHint: false,
|
|
2906
|
+
destructiveHint: true,
|
|
2907
|
+
idempotentHint: false,
|
|
2908
|
+
openWorldHint: false
|
|
2909
|
+
}
|
|
2069
2910
|
},
|
|
2070
|
-
async (args) => {
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2911
|
+
async (args, extra) => {
|
|
2912
|
+
try {
|
|
2913
|
+
const result = executeClaudeCodeCheck(args, sessionManager, toolCache, extra.signal);
|
|
2914
|
+
const isError = result.isError === true;
|
|
2915
|
+
return {
|
|
2916
|
+
content: [
|
|
2917
|
+
{
|
|
2918
|
+
type: "text",
|
|
2919
|
+
text: JSON.stringify(result, null, 2)
|
|
2920
|
+
}
|
|
2921
|
+
],
|
|
2922
|
+
structuredContent: result,
|
|
2923
|
+
isError
|
|
2924
|
+
};
|
|
2925
|
+
} catch (err) {
|
|
2926
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2927
|
+
const errorResult = {
|
|
2928
|
+
sessionId: args.sessionId ?? "",
|
|
2929
|
+
status: "error",
|
|
2930
|
+
events: [],
|
|
2931
|
+
isError: true,
|
|
2932
|
+
error: `Error [${"INTERNAL" /* INTERNAL */}]: ${message}`
|
|
2933
|
+
};
|
|
2934
|
+
return {
|
|
2935
|
+
content: [
|
|
2936
|
+
{
|
|
2937
|
+
type: "text",
|
|
2938
|
+
text: JSON.stringify(errorResult, null, 2)
|
|
2939
|
+
}
|
|
2940
|
+
],
|
|
2941
|
+
structuredContent: errorResult,
|
|
2942
|
+
isError: true
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2082
2945
|
}
|
|
2083
2946
|
);
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2947
|
+
registerResources(server, { toolCache });
|
|
2948
|
+
return { server, sessionManager, toolCache };
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
// src/utils/runtime-errors.ts
|
|
2952
|
+
var BENIGN_CODES = /* @__PURE__ */ new Set([
|
|
2953
|
+
"EPIPE",
|
|
2954
|
+
"ECONNRESET",
|
|
2955
|
+
"ERR_STREAM_WRITE_AFTER_END",
|
|
2956
|
+
"ERR_STREAM_DESTROYED",
|
|
2957
|
+
"ABORT_ERR"
|
|
2958
|
+
]);
|
|
2959
|
+
var BENIGN_MESSAGE_PATTERNS = [
|
|
2960
|
+
"operation aborted",
|
|
2961
|
+
"request was cancelled",
|
|
2962
|
+
"transport closed",
|
|
2963
|
+
"write after end",
|
|
2964
|
+
"stream was destroyed",
|
|
2965
|
+
"cannot call write after a stream was destroyed",
|
|
2966
|
+
"the pipe is being closed"
|
|
2967
|
+
];
|
|
2968
|
+
function isBenignRuntimeError(error) {
|
|
2969
|
+
if (!(error instanceof Error)) return false;
|
|
2970
|
+
const withCode = error;
|
|
2971
|
+
const code = typeof withCode.code === "string" ? withCode.code : void 0;
|
|
2972
|
+
if (code && BENIGN_CODES.has(code)) return true;
|
|
2973
|
+
if (error.name === "AbortError") return true;
|
|
2974
|
+
const message = error.message.toLowerCase();
|
|
2975
|
+
return BENIGN_MESSAGE_PATTERNS.some((pattern) => message.includes(pattern));
|
|
2090
2976
|
}
|
|
2091
2977
|
|
|
2092
2978
|
// src/index.ts
|
|
2979
|
+
var STDIN_SHUTDOWN_CHECK_MS = 750;
|
|
2980
|
+
var STDIN_SHUTDOWN_MAX_WAIT_MS = process.platform === "win32" ? 15e3 : 1e4;
|
|
2981
|
+
function summarizeSessions(ctx) {
|
|
2982
|
+
const sessions = ctx.sessionManager.list();
|
|
2983
|
+
const running = sessions.filter((s) => s.status === "running").length;
|
|
2984
|
+
const waitingPermission = sessions.filter((s) => s.status === "waiting_permission").length;
|
|
2985
|
+
return {
|
|
2986
|
+
total: sessions.length,
|
|
2987
|
+
running,
|
|
2988
|
+
waitingPermission,
|
|
2989
|
+
terminal: sessions.length - running - waitingPermission
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2093
2992
|
async function main() {
|
|
2094
2993
|
const serverCwd = process.cwd();
|
|
2095
|
-
const
|
|
2994
|
+
const ctx = createServerContext(serverCwd);
|
|
2995
|
+
const server = ctx.server;
|
|
2996
|
+
const sessionManager = ctx.sessionManager;
|
|
2096
2997
|
const transport = new StdioServerTransport();
|
|
2097
2998
|
let closing = false;
|
|
2098
|
-
|
|
2999
|
+
let lastExitCode = 0;
|
|
3000
|
+
let stdinClosedAt;
|
|
3001
|
+
let stdinClosedReason;
|
|
3002
|
+
let stdinShutdownTimer;
|
|
3003
|
+
const onStdinEnd = () => handleStdinTerminated("end");
|
|
3004
|
+
const onStdinClose = () => handleStdinTerminated("close");
|
|
3005
|
+
const clearStdinShutdownTimer = () => {
|
|
3006
|
+
if (stdinShutdownTimer) {
|
|
3007
|
+
clearTimeout(stdinShutdownTimer);
|
|
3008
|
+
stdinShutdownTimer = void 0;
|
|
3009
|
+
}
|
|
3010
|
+
};
|
|
3011
|
+
const shutdown = async (reason = "unknown") => {
|
|
2099
3012
|
if (closing) return;
|
|
2100
3013
|
closing = true;
|
|
3014
|
+
clearStdinShutdownTimer();
|
|
3015
|
+
if (typeof process.stdin.off === "function") {
|
|
3016
|
+
process.stdin.off("error", handleStdinError);
|
|
3017
|
+
process.stdin.off("end", onStdinEnd);
|
|
3018
|
+
process.stdin.off("close", onStdinClose);
|
|
3019
|
+
}
|
|
3020
|
+
const forceExitMs = process.platform === "win32" ? 1e4 : 5e3;
|
|
3021
|
+
const forceExitTimer = setTimeout(() => process.exit(lastExitCode), forceExitMs);
|
|
3022
|
+
if (forceExitTimer.unref) forceExitTimer.unref();
|
|
3023
|
+
const sessionSummary = summarizeSessions(ctx);
|
|
2101
3024
|
try {
|
|
3025
|
+
if (server?.isConnected()) {
|
|
3026
|
+
await server.sendLoggingMessage({
|
|
3027
|
+
level: "info",
|
|
3028
|
+
data: { event: "server_stopping", reason, ...sessionSummary }
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
sessionManager.destroy();
|
|
2102
3032
|
await server.close();
|
|
2103
3033
|
} catch {
|
|
2104
3034
|
}
|
|
2105
|
-
process.exitCode =
|
|
2106
|
-
|
|
3035
|
+
process.exitCode = lastExitCode;
|
|
3036
|
+
try {
|
|
3037
|
+
await new Promise((resolve) => process.stderr.write("", () => resolve()));
|
|
3038
|
+
} catch {
|
|
3039
|
+
} finally {
|
|
3040
|
+
clearTimeout(forceExitTimer);
|
|
3041
|
+
}
|
|
3042
|
+
};
|
|
3043
|
+
function handleStdinError(error) {
|
|
3044
|
+
console.error("stdin error:", error);
|
|
3045
|
+
lastExitCode = 1;
|
|
3046
|
+
void shutdown("stdin_error");
|
|
3047
|
+
}
|
|
3048
|
+
function hasActiveSessions() {
|
|
3049
|
+
return sessionManager.list().some((s) => s.status === "running" || s.status === "waiting_permission");
|
|
3050
|
+
}
|
|
3051
|
+
const evaluateStdinTermination = () => {
|
|
3052
|
+
if (closing || stdinClosedAt === void 0) return;
|
|
3053
|
+
const stdinUnavailable = process.stdin.destroyed || process.stdin.readableEnded || !process.stdin.readable;
|
|
3054
|
+
if (!stdinUnavailable) {
|
|
3055
|
+
stdinClosedAt = void 0;
|
|
3056
|
+
stdinClosedReason = void 0;
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
const elapsedMs = Date.now() - stdinClosedAt;
|
|
3060
|
+
const active = hasActiveSessions();
|
|
3061
|
+
const connected = server.isConnected();
|
|
3062
|
+
if (!active && !connected) {
|
|
3063
|
+
void shutdown(`stdin_${stdinClosedReason ?? "closed"}`);
|
|
3064
|
+
return;
|
|
3065
|
+
}
|
|
3066
|
+
if (elapsedMs >= STDIN_SHUTDOWN_MAX_WAIT_MS) {
|
|
3067
|
+
void shutdown(`stdin_${stdinClosedReason ?? "closed"}_timeout`);
|
|
3068
|
+
return;
|
|
3069
|
+
}
|
|
3070
|
+
stdinShutdownTimer = setTimeout(evaluateStdinTermination, STDIN_SHUTDOWN_CHECK_MS);
|
|
3071
|
+
if (stdinShutdownTimer.unref) stdinShutdownTimer.unref();
|
|
3072
|
+
};
|
|
3073
|
+
function handleStdinTerminated(event) {
|
|
3074
|
+
if (closing) return;
|
|
3075
|
+
if (stdinClosedAt === void 0) {
|
|
3076
|
+
stdinClosedAt = Date.now();
|
|
3077
|
+
stdinClosedReason = event;
|
|
3078
|
+
console.error(`[lifecycle] stdin ${event} observed; entering guarded shutdown checks`);
|
|
3079
|
+
}
|
|
3080
|
+
clearStdinShutdownTimer();
|
|
3081
|
+
stdinShutdownTimer = setTimeout(evaluateStdinTermination, STDIN_SHUTDOWN_CHECK_MS);
|
|
3082
|
+
if (stdinShutdownTimer.unref) stdinShutdownTimer.unref();
|
|
3083
|
+
}
|
|
3084
|
+
const handleUnexpectedError = (error) => {
|
|
3085
|
+
if (isBenignRuntimeError(error)) {
|
|
3086
|
+
console.error("Ignored benign runtime abort:", error);
|
|
3087
|
+
return;
|
|
3088
|
+
}
|
|
3089
|
+
console.error("Unhandled runtime error:", error);
|
|
3090
|
+
lastExitCode = 1;
|
|
3091
|
+
void shutdown("runtime_error");
|
|
2107
3092
|
};
|
|
2108
|
-
process.on("SIGINT",
|
|
2109
|
-
|
|
3093
|
+
process.on("SIGINT", () => {
|
|
3094
|
+
void shutdown("SIGINT");
|
|
3095
|
+
});
|
|
3096
|
+
process.on("SIGTERM", () => {
|
|
3097
|
+
void shutdown("SIGTERM");
|
|
3098
|
+
});
|
|
3099
|
+
process.on("SIGHUP", () => {
|
|
3100
|
+
void shutdown("SIGHUP");
|
|
3101
|
+
});
|
|
3102
|
+
process.on("beforeExit", () => {
|
|
3103
|
+
void shutdown("beforeExit");
|
|
3104
|
+
});
|
|
3105
|
+
process.on("uncaughtException", handleUnexpectedError);
|
|
3106
|
+
process.on("unhandledRejection", handleUnexpectedError);
|
|
3107
|
+
if (process.platform === "win32") {
|
|
3108
|
+
process.on("SIGBREAK", () => {
|
|
3109
|
+
void shutdown("SIGBREAK");
|
|
3110
|
+
});
|
|
3111
|
+
}
|
|
3112
|
+
if (typeof process.stdin.resume === "function") {
|
|
3113
|
+
process.stdin.resume();
|
|
3114
|
+
}
|
|
3115
|
+
process.stdin.on("error", handleStdinError);
|
|
3116
|
+
process.stdin.on("end", onStdinEnd);
|
|
3117
|
+
process.stdin.on("close", onStdinClose);
|
|
2110
3118
|
await server.connect(transport);
|
|
3119
|
+
server.sendToolListChanged();
|
|
3120
|
+
server.sendResourceListChanged();
|
|
2111
3121
|
checkWindowsBashAvailability();
|
|
2112
|
-
|
|
3122
|
+
try {
|
|
3123
|
+
if (transport && server) {
|
|
3124
|
+
await server.sendLoggingMessage({
|
|
3125
|
+
level: "info",
|
|
3126
|
+
data: { event: "server_started", cwd: serverCwd }
|
|
3127
|
+
});
|
|
3128
|
+
}
|
|
3129
|
+
} catch {
|
|
3130
|
+
}
|
|
3131
|
+
console.error(`claude-code-mcp server started (transport=stdio, cwd: ${serverCwd})`);
|
|
2113
3132
|
}
|
|
2114
3133
|
main().catch((err) => {
|
|
2115
3134
|
console.error("Fatal error:", err);
|