@leo000001/claude-code-mcp 2.0.3 → 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 +53 -3
- package/CONTRIBUTING.md +10 -1
- package/NOTICE.md +27 -0
- package/README.md +68 -17
- package/SECURITY.md +4 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1204 -376
- package/dist/index.js.map +1 -1
- package/package.json +14 -2
package/dist/index.js
CHANGED
|
@@ -15,37 +15,69 @@ function normalizePermissionUpdatedInput(input) {
|
|
|
15
15
|
return { input };
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
// src/utils/normalize-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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,})\/(.*)$/);
|
|
23
39
|
if (m2) {
|
|
24
40
|
const host = m2[1];
|
|
25
41
|
const rest2 = m2[2] ?? "";
|
|
26
42
|
return `\\\\${host}\\${rest2.replace(/\//g, "\\")}`;
|
|
27
43
|
}
|
|
28
44
|
}
|
|
29
|
-
const cyg =
|
|
45
|
+
const cyg = rawPath.match(/^\/cygdrive\/([a-zA-Z])\/(.*)$/);
|
|
30
46
|
if (cyg) {
|
|
31
47
|
const drive2 = cyg[1].toUpperCase();
|
|
32
48
|
const rest2 = cyg[2] ?? "";
|
|
33
49
|
return `${drive2}:\\${rest2.replace(/\//g, "\\")}`;
|
|
34
50
|
}
|
|
35
|
-
const m =
|
|
51
|
+
const m = rawPath.match(/^\/(?:mnt\/)?([a-zA-Z])\/(.*)$/);
|
|
36
52
|
if (!m) return void 0;
|
|
37
53
|
const drive = m[1].toUpperCase();
|
|
38
54
|
const rest = m[2] ?? "";
|
|
39
55
|
return `${drive}:\\${rest.replace(/\//g, "\\")}`;
|
|
40
56
|
}
|
|
41
|
-
function
|
|
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) {
|
|
42
76
|
if (platform !== "win32") return input;
|
|
43
|
-
if (toolName !== "NotebookEdit") return input;
|
|
44
77
|
const filePath = input.file_path;
|
|
45
78
|
if (typeof filePath !== "string") return input;
|
|
46
|
-
const normalized =
|
|
47
|
-
|
|
48
|
-
return { ...input, file_path: normalized };
|
|
79
|
+
const normalized = normalizeWindowsPathLike(filePath, platform);
|
|
80
|
+
return normalized === filePath ? input : { ...input, file_path: normalized };
|
|
49
81
|
}
|
|
50
82
|
|
|
51
83
|
// src/session/manager.ts
|
|
@@ -54,20 +86,46 @@ var DEFAULT_RUNNING_SESSION_MAX_MS = 4 * 60 * 60 * 1e3;
|
|
|
54
86
|
var DEFAULT_CLEANUP_INTERVAL_MS = 6e4;
|
|
55
87
|
var DEFAULT_EVENT_BUFFER_MAX_SIZE = 1e3;
|
|
56
88
|
var DEFAULT_EVENT_BUFFER_HARD_MAX_SIZE = 2e3;
|
|
89
|
+
var DEFAULT_MAX_SESSIONS = 128;
|
|
90
|
+
var DEFAULT_MAX_PENDING_PERMISSIONS_PER_SESSION = 64;
|
|
57
91
|
var SessionManager = class _SessionManager {
|
|
58
92
|
sessions = /* @__PURE__ */ new Map();
|
|
59
93
|
runtime = /* @__PURE__ */ new Map();
|
|
94
|
+
drainingSessions = /* @__PURE__ */ new Map();
|
|
60
95
|
cleanupTimer;
|
|
61
96
|
sessionTtlMs = DEFAULT_SESSION_TTL_MS;
|
|
62
97
|
runningSessionMaxMs = DEFAULT_RUNNING_SESSION_MAX_MS;
|
|
63
98
|
platform;
|
|
99
|
+
maxSessions;
|
|
100
|
+
maxPendingPermissionsPerSession;
|
|
64
101
|
constructor(opts) {
|
|
65
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;
|
|
66
111
|
this.cleanupTimer = setInterval(() => this.cleanup(), DEFAULT_CLEANUP_INTERVAL_MS);
|
|
67
112
|
if (this.cleanupTimer.unref) {
|
|
68
113
|
this.cleanupTimer.unref();
|
|
69
114
|
}
|
|
70
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
|
+
}
|
|
71
129
|
create(params) {
|
|
72
130
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
73
131
|
const existing = this.sessions.get(params.sessionId);
|
|
@@ -160,7 +218,10 @@ var SessionManager = class _SessionManager {
|
|
|
160
218
|
if (info.status === "waiting_permission") {
|
|
161
219
|
this.finishAllPending(
|
|
162
220
|
sessionId,
|
|
163
|
-
|
|
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 },
|
|
164
225
|
"cancel"
|
|
165
226
|
);
|
|
166
227
|
}
|
|
@@ -178,8 +239,10 @@ var SessionManager = class _SessionManager {
|
|
|
178
239
|
this.finishAllPending(
|
|
179
240
|
sessionId,
|
|
180
241
|
{ behavior: "deny", message: "Session deleted", interrupt: true },
|
|
181
|
-
"cleanup"
|
|
242
|
+
"cleanup",
|
|
243
|
+
{ restoreRunning: false }
|
|
182
244
|
);
|
|
245
|
+
this.drainingSessions.delete(sessionId);
|
|
183
246
|
this.runtime.delete(sessionId);
|
|
184
247
|
return this.sessions.delete(sessionId);
|
|
185
248
|
}
|
|
@@ -235,7 +298,53 @@ var SessionManager = class _SessionManager {
|
|
|
235
298
|
const state = this.runtime.get(sessionId);
|
|
236
299
|
const info = this.sessions.get(sessionId);
|
|
237
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
|
+
}
|
|
238
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
|
+
}
|
|
239
348
|
const inferredExpiresAt = new Date(Date.now() + timeoutMs).toISOString();
|
|
240
349
|
const record = {
|
|
241
350
|
...req,
|
|
@@ -321,6 +430,11 @@ var SessionManager = class _SessionManager {
|
|
|
321
430
|
if (finalResult.behavior === "deny") {
|
|
322
431
|
eventData.message = finalResult.message;
|
|
323
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;
|
|
324
438
|
}
|
|
325
439
|
this.pushEvent(sessionId, {
|
|
326
440
|
type: "permission_result",
|
|
@@ -331,17 +445,41 @@ var SessionManager = class _SessionManager {
|
|
|
331
445
|
pending.finish(finalResult);
|
|
332
446
|
} catch {
|
|
333
447
|
}
|
|
334
|
-
if (info.status === "waiting_permission" && state.pendingPermissions.size === 0) {
|
|
448
|
+
if (info.status === "waiting_permission" && state.pendingPermissions.size === 0 && !this.isDraining(sessionId)) {
|
|
335
449
|
info.status = "running";
|
|
336
450
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
337
451
|
}
|
|
338
452
|
return true;
|
|
339
453
|
}
|
|
340
|
-
finishAllPending(sessionId, result, source) {
|
|
454
|
+
finishAllPending(sessionId, result, source, opts) {
|
|
341
455
|
const state = this.runtime.get(sessionId);
|
|
342
456
|
if (!state) return;
|
|
343
|
-
|
|
344
|
-
|
|
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();
|
|
345
483
|
}
|
|
346
484
|
}
|
|
347
485
|
/** Remove sessions that have been idle for too long, or stuck running too long */
|
|
@@ -353,29 +491,40 @@ var SessionManager = class _SessionManager {
|
|
|
353
491
|
this.finishAllPending(
|
|
354
492
|
id,
|
|
355
493
|
{ behavior: "deny", message: "Session expired", interrupt: true },
|
|
356
|
-
"cleanup"
|
|
494
|
+
"cleanup",
|
|
495
|
+
{ restoreRunning: false }
|
|
357
496
|
);
|
|
497
|
+
this.drainingSessions.delete(id);
|
|
358
498
|
this.sessions.delete(id);
|
|
359
499
|
this.runtime.delete(id);
|
|
360
500
|
} else if (info.status === "running" && now - lastActive > this.runningSessionMaxMs) {
|
|
361
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";
|
|
362
505
|
info.status = "error";
|
|
363
506
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
364
507
|
} else if (info.status === "waiting_permission" && now - lastActive > this.runningSessionMaxMs) {
|
|
365
508
|
this.finishAllPending(
|
|
366
509
|
id,
|
|
367
510
|
{ behavior: "deny", message: "Session timed out", interrupt: true },
|
|
368
|
-
"cleanup"
|
|
511
|
+
"cleanup",
|
|
512
|
+
{ restoreRunning: false }
|
|
369
513
|
);
|
|
370
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";
|
|
371
518
|
info.status = "error";
|
|
372
519
|
info.lastActiveAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
373
520
|
} else if (info.status !== "running" && info.status !== "waiting_permission" && now - lastActive > this.sessionTtlMs) {
|
|
374
521
|
this.finishAllPending(
|
|
375
522
|
id,
|
|
376
523
|
{ behavior: "deny", message: "Session expired", interrupt: true },
|
|
377
|
-
"cleanup"
|
|
524
|
+
"cleanup",
|
|
525
|
+
{ restoreRunning: false }
|
|
378
526
|
);
|
|
527
|
+
this.drainingSessions.delete(id);
|
|
379
528
|
this.sessions.delete(id);
|
|
380
529
|
this.runtime.delete(id);
|
|
381
530
|
}
|
|
@@ -424,7 +573,8 @@ var SessionManager = class _SessionManager {
|
|
|
424
573
|
this.finishAllPending(
|
|
425
574
|
info.sessionId,
|
|
426
575
|
{ behavior: "deny", message: "Server shutting down", interrupt: true },
|
|
427
|
-
"destroy"
|
|
576
|
+
"destroy",
|
|
577
|
+
{ restoreRunning: false }
|
|
428
578
|
);
|
|
429
579
|
if ((info.status === "running" || info.status === "waiting_permission") && info.abortController) {
|
|
430
580
|
info.abortController.abort();
|
|
@@ -494,6 +644,20 @@ var SessionManager = class _SessionManager {
|
|
|
494
644
|
static clearTerminalEvents(buffer) {
|
|
495
645
|
buffer.events = buffer.events.filter((e) => e.type !== "result" && e.type !== "error");
|
|
496
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
|
+
}
|
|
497
661
|
};
|
|
498
662
|
|
|
499
663
|
// src/types.ts
|
|
@@ -510,23 +674,98 @@ import { AbortError, query } from "@anthropic-ai/claude-agent-sdk";
|
|
|
510
674
|
// src/utils/windows.ts
|
|
511
675
|
import { existsSync } from "fs";
|
|
512
676
|
import { execSync } from "child_process";
|
|
513
|
-
import
|
|
514
|
-
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\\";
|
|
515
681
|
function isWindows() {
|
|
516
682
|
return process.platform === "win32";
|
|
517
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
|
+
}
|
|
518
747
|
function findGitBash() {
|
|
519
748
|
const envPathRaw = process.env.CLAUDE_CODE_GIT_BASH_PATH;
|
|
520
749
|
if (envPathRaw && envPathRaw.trim() !== "") {
|
|
521
|
-
const envPath =
|
|
522
|
-
if (
|
|
523
|
-
|
|
524
|
-
|
|
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;
|
|
525
766
|
try {
|
|
526
|
-
const
|
|
527
|
-
const
|
|
528
|
-
for (const gitPathRaw of gitCandidates) {
|
|
529
|
-
const gitPath = normalize(gitPathRaw.replace(/^"|"$/g, ""));
|
|
767
|
+
const gitCandidates = [...tryFromPath(["git.exe", "git.cmd", "git.bat"]), ...tryWhere("git")];
|
|
768
|
+
for (const gitPath of gitCandidates) {
|
|
530
769
|
if (!gitPath) continue;
|
|
531
770
|
const gitDir = dirname(gitPath);
|
|
532
771
|
const gitDirLower = gitDir.toLowerCase();
|
|
@@ -548,19 +787,38 @@ function findGitBash() {
|
|
|
548
787
|
bashCandidates.push(join(root, "mingw64", "bin", "bash.exe"));
|
|
549
788
|
}
|
|
550
789
|
for (const bashPath of bashCandidates) {
|
|
551
|
-
const
|
|
552
|
-
if (
|
|
790
|
+
const normalizedPath = normalize(bashPath);
|
|
791
|
+
if (existsPath(normalizedPath)) return normalizedPath;
|
|
553
792
|
}
|
|
554
793
|
}
|
|
555
794
|
} catch {
|
|
556
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;
|
|
557
802
|
return null;
|
|
558
803
|
}
|
|
559
804
|
function checkWindowsBashAvailability() {
|
|
560
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));
|
|
561
809
|
const bashPath = findGitBash();
|
|
562
810
|
if (bashPath) {
|
|
563
|
-
|
|
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
|
+
}
|
|
564
822
|
return;
|
|
565
823
|
}
|
|
566
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.";
|
|
@@ -577,7 +835,9 @@ function checkWindowsBashAvailability() {
|
|
|
577
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.';
|
|
578
836
|
function enhanceWindowsError(errorMessage) {
|
|
579
837
|
if (!isWindows()) return errorMessage;
|
|
580
|
-
|
|
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")) {
|
|
581
841
|
return errorMessage + WINDOWS_BASH_HINT;
|
|
582
842
|
}
|
|
583
843
|
return errorMessage;
|
|
@@ -586,6 +846,13 @@ function enhanceWindowsError(errorMessage) {
|
|
|
586
846
|
// src/tools/query-consumer.ts
|
|
587
847
|
var MAX_TRANSIENT_RETRIES = 3;
|
|
588
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
|
+
}
|
|
589
856
|
function classifyError(err, abortSignal) {
|
|
590
857
|
if (abortSignal.aborted) return "abort";
|
|
591
858
|
if (err instanceof AbortError || err instanceof Error && err.name === "AbortError") {
|
|
@@ -610,12 +877,16 @@ function describeTool(toolName, toolCache) {
|
|
|
610
877
|
return found?.description;
|
|
611
878
|
}
|
|
612
879
|
function sdkResultToAgentResult(result) {
|
|
880
|
+
const sessionTotalTurns = result.session_total_turns;
|
|
881
|
+
const sessionTotalCostUsd = result.session_total_cost_usd;
|
|
613
882
|
const base = {
|
|
614
883
|
sessionId: result.session_id,
|
|
615
884
|
durationMs: result.duration_ms,
|
|
616
885
|
durationApiMs: result.duration_api_ms,
|
|
617
886
|
numTurns: result.num_turns,
|
|
618
887
|
totalCostUsd: result.total_cost_usd,
|
|
888
|
+
sessionTotalTurns: typeof sessionTotalTurns === "number" ? sessionTotalTurns : void 0,
|
|
889
|
+
sessionTotalCostUsd: typeof sessionTotalCostUsd === "number" ? sessionTotalCostUsd : void 0,
|
|
619
890
|
stopReason: result.stop_reason,
|
|
620
891
|
usage: result.usage,
|
|
621
892
|
modelUsage: result.modelUsage,
|
|
@@ -727,6 +998,9 @@ function consumeQuery(params) {
|
|
|
727
998
|
return activeSessionId;
|
|
728
999
|
};
|
|
729
1000
|
let initTimeoutId;
|
|
1001
|
+
const permissionRequestTimeoutMs = clampPermissionRequestTimeoutMs(
|
|
1002
|
+
params.permissionRequestTimeoutMs
|
|
1003
|
+
);
|
|
730
1004
|
const canUseTool = async (toolName, input, options2) => {
|
|
731
1005
|
const sessionId = await getSessionId();
|
|
732
1006
|
const normalizedInput = normalizeToolInput(toolName, input, params.platform);
|
|
@@ -744,7 +1018,7 @@ function consumeQuery(params) {
|
|
|
744
1018
|
}
|
|
745
1019
|
const requestId = `${options2.toolUseID}:${toolName}:${Date.now()}:${Math.random().toString(16).slice(2)}`;
|
|
746
1020
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
747
|
-
const timeoutMs =
|
|
1021
|
+
const timeoutMs = permissionRequestTimeoutMs;
|
|
748
1022
|
const expiresAt = new Date(Date.now() + timeoutMs).toISOString();
|
|
749
1023
|
const record = {
|
|
750
1024
|
requestId,
|
|
@@ -781,10 +1055,14 @@ function consumeQuery(params) {
|
|
|
781
1055
|
sessionId,
|
|
782
1056
|
record,
|
|
783
1057
|
finish,
|
|
784
|
-
|
|
1058
|
+
permissionRequestTimeoutMs
|
|
785
1059
|
);
|
|
786
1060
|
if (!registered) {
|
|
787
|
-
finish({
|
|
1061
|
+
finish({
|
|
1062
|
+
behavior: "deny",
|
|
1063
|
+
message: "Session no longer exists.",
|
|
1064
|
+
interrupt: true
|
|
1065
|
+
});
|
|
788
1066
|
return;
|
|
789
1067
|
}
|
|
790
1068
|
options2.signal.addEventListener("abort", abortListener, { once: true });
|
|
@@ -819,6 +1097,7 @@ function consumeQuery(params) {
|
|
|
819
1097
|
};
|
|
820
1098
|
const done = (async () => {
|
|
821
1099
|
const preInit = [];
|
|
1100
|
+
let preInitDropped = 0;
|
|
822
1101
|
if (shouldWaitForInit) {
|
|
823
1102
|
initTimeoutId = setTimeout(() => {
|
|
824
1103
|
close();
|
|
@@ -843,6 +1122,13 @@ function consumeQuery(params) {
|
|
|
843
1122
|
sessionIdResolved = true;
|
|
844
1123
|
resolveSessionId(activeSessionId);
|
|
845
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
|
+
}
|
|
846
1132
|
for (const buffered of preInit) {
|
|
847
1133
|
const event2 = messageToEvent(buffered);
|
|
848
1134
|
if (!event2) continue;
|
|
@@ -858,35 +1144,50 @@ function consumeQuery(params) {
|
|
|
858
1144
|
}
|
|
859
1145
|
if (shouldWaitForInit && !sessionIdResolved) {
|
|
860
1146
|
preInit.push(message);
|
|
1147
|
+
if (preInit.length > MAX_PREINIT_BUFFER_MESSAGES) {
|
|
1148
|
+
preInit.shift();
|
|
1149
|
+
preInitDropped++;
|
|
1150
|
+
}
|
|
861
1151
|
continue;
|
|
862
1152
|
}
|
|
863
1153
|
if (message.type === "result") {
|
|
864
1154
|
const sessionId2 = message.session_id ?? await getSessionId();
|
|
865
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
|
+
};
|
|
866
1168
|
const stored = {
|
|
867
1169
|
type: agentResult.isError ? "error" : "result",
|
|
868
|
-
result:
|
|
1170
|
+
result: resultWithSessionTotals,
|
|
869
1171
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
870
1172
|
};
|
|
871
1173
|
params.sessionManager.setResult(sessionId2, stored);
|
|
872
1174
|
params.sessionManager.clearTerminalEvents(sessionId2);
|
|
873
1175
|
params.sessionManager.pushEvent(sessionId2, {
|
|
874
1176
|
type: agentResult.isError ? "error" : "result",
|
|
875
|
-
data:
|
|
1177
|
+
data: resultWithSessionTotals,
|
|
876
1178
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
877
1179
|
});
|
|
878
|
-
const current = params.sessionManager.get(sessionId2);
|
|
879
1180
|
if (current && current.status !== "cancelled") {
|
|
880
1181
|
params.sessionManager.update(sessionId2, {
|
|
881
1182
|
status: agentResult.isError ? "error" : "idle",
|
|
882
|
-
totalTurns:
|
|
883
|
-
totalCostUsd:
|
|
1183
|
+
totalTurns: sessionTotalTurns,
|
|
1184
|
+
totalCostUsd: sessionTotalCostUsd,
|
|
884
1185
|
abortController: void 0
|
|
885
1186
|
});
|
|
886
1187
|
} else if (current) {
|
|
887
1188
|
params.sessionManager.update(sessionId2, {
|
|
888
|
-
totalTurns:
|
|
889
|
-
totalCostUsd:
|
|
1189
|
+
totalTurns: sessionTotalTurns,
|
|
1190
|
+
totalCostUsd: sessionTotalCostUsd,
|
|
890
1191
|
abortController: void 0
|
|
891
1192
|
});
|
|
892
1193
|
}
|
|
@@ -1035,15 +1336,23 @@ function consumeQuery(params) {
|
|
|
1035
1336
|
|
|
1036
1337
|
// src/utils/resume-token.ts
|
|
1037
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
|
+
}
|
|
1038
1344
|
function getResumeSecret() {
|
|
1039
|
-
|
|
1040
|
-
if (typeof secret !== "string") return void 0;
|
|
1041
|
-
const trimmed = secret.trim();
|
|
1042
|
-
return trimmed.length > 0 ? trimmed : void 0;
|
|
1345
|
+
return getResumeSecrets()[0];
|
|
1043
1346
|
}
|
|
1044
1347
|
function computeResumeToken(sessionId, secret) {
|
|
1045
1348
|
return createHmac("sha256", secret).update(sessionId).digest("base64url");
|
|
1046
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
|
+
}
|
|
1047
1356
|
|
|
1048
1357
|
// src/utils/race-with-abort.ts
|
|
1049
1358
|
function raceWithAbort(promise, signal, onAbort) {
|
|
@@ -1070,7 +1379,7 @@ function raceWithAbort(promise, signal, onAbort) {
|
|
|
1070
1379
|
|
|
1071
1380
|
// src/utils/build-options.ts
|
|
1072
1381
|
function buildOptions(src) {
|
|
1073
|
-
const opts = { cwd: src.cwd };
|
|
1382
|
+
const opts = { cwd: normalizeWindowsPathLike(src.cwd) };
|
|
1074
1383
|
if (src.allowedTools !== void 0) opts.allowedTools = src.allowedTools;
|
|
1075
1384
|
if (src.disallowedTools !== void 0) opts.disallowedTools = src.disallowedTools;
|
|
1076
1385
|
if (src.tools !== void 0) opts.tools = src.tools;
|
|
@@ -1082,13 +1391,13 @@ function buildOptions(src) {
|
|
|
1082
1391
|
if (src.effort !== void 0) opts.effort = src.effort;
|
|
1083
1392
|
if (src.betas !== void 0) opts.betas = src.betas;
|
|
1084
1393
|
if (src.additionalDirectories !== void 0)
|
|
1085
|
-
opts.additionalDirectories = src.additionalDirectories;
|
|
1394
|
+
opts.additionalDirectories = normalizeWindowsPathArray(src.additionalDirectories);
|
|
1086
1395
|
if (src.outputFormat !== void 0) opts.outputFormat = src.outputFormat;
|
|
1087
1396
|
if (src.thinking !== void 0) opts.thinking = src.thinking;
|
|
1088
1397
|
if (src.persistSession !== void 0) opts.persistSession = src.persistSession;
|
|
1089
1398
|
if (src.resumeSessionAt !== void 0) opts.resumeSessionAt = src.resumeSessionAt;
|
|
1090
1399
|
if (src.pathToClaudeCodeExecutable !== void 0)
|
|
1091
|
-
opts.pathToClaudeCodeExecutable = src.pathToClaudeCodeExecutable;
|
|
1400
|
+
opts.pathToClaudeCodeExecutable = normalizeWindowsPathLike(src.pathToClaudeCodeExecutable);
|
|
1092
1401
|
if (src.agent !== void 0) opts.agent = src.agent;
|
|
1093
1402
|
if (src.mcpServers !== void 0) opts.mcpServers = src.mcpServers;
|
|
1094
1403
|
if (src.sandbox !== void 0) opts.sandbox = src.sandbox;
|
|
@@ -1101,11 +1410,48 @@ function buildOptions(src) {
|
|
|
1101
1410
|
if (src.settingSources !== void 0) opts.settingSources = src.settingSources;
|
|
1102
1411
|
else opts.settingSources = DEFAULT_SETTING_SOURCES;
|
|
1103
1412
|
if (src.debug !== void 0) opts.debug = src.debug;
|
|
1104
|
-
if (src.debugFile !== void 0) opts.debugFile = src.debugFile;
|
|
1413
|
+
if (src.debugFile !== void 0) opts.debugFile = normalizeWindowsPathLike(src.debugFile);
|
|
1105
1414
|
if (src.env !== void 0) opts.env = { ...process.env, ...src.env };
|
|
1106
1415
|
return opts;
|
|
1107
1416
|
}
|
|
1108
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
|
+
|
|
1109
1455
|
// src/tools/claude-code.ts
|
|
1110
1456
|
async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, requestSignal) {
|
|
1111
1457
|
const cwd = input.cwd !== void 0 ? input.cwd : serverCwd;
|
|
@@ -1116,10 +1462,28 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1116
1462
|
error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd must be a non-empty string.`
|
|
1117
1463
|
};
|
|
1118
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
|
+
}
|
|
1119
1472
|
const abortController = new AbortController();
|
|
1120
1473
|
const adv = input.advanced ?? {};
|
|
1121
1474
|
const permissionRequestTimeoutMs = input.permissionRequestTimeoutMs ?? 6e4;
|
|
1122
|
-
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
|
+
}
|
|
1123
1487
|
const flat = {
|
|
1124
1488
|
cwd,
|
|
1125
1489
|
allowedTools: input.allowedTools,
|
|
@@ -1127,52 +1491,37 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1127
1491
|
maxTurns: input.maxTurns,
|
|
1128
1492
|
model: input.model,
|
|
1129
1493
|
systemPrompt: input.systemPrompt,
|
|
1130
|
-
...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
|
|
1131
1504
|
};
|
|
1132
1505
|
try {
|
|
1133
1506
|
const handle = consumeQuery({
|
|
1134
1507
|
mode: "start",
|
|
1135
1508
|
prompt: input.prompt,
|
|
1136
1509
|
abortController,
|
|
1137
|
-
options: buildOptions(
|
|
1510
|
+
options: buildOptions(normalizedFlat),
|
|
1138
1511
|
permissionRequestTimeoutMs,
|
|
1139
1512
|
sessionInitTimeoutMs,
|
|
1140
1513
|
sessionManager,
|
|
1141
1514
|
toolCache,
|
|
1142
1515
|
onInit: (init) => {
|
|
1143
1516
|
if (sessionManager.get(init.session_id)) return;
|
|
1144
|
-
sessionManager.create(
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
maxTurns: input.maxTurns,
|
|
1153
|
-
systemPrompt: input.systemPrompt,
|
|
1154
|
-
agents: adv.agents,
|
|
1155
|
-
maxBudgetUsd: adv.maxBudgetUsd,
|
|
1156
|
-
effort: adv.effort,
|
|
1157
|
-
betas: adv.betas,
|
|
1158
|
-
additionalDirectories: adv.additionalDirectories,
|
|
1159
|
-
outputFormat: adv.outputFormat,
|
|
1160
|
-
thinking: adv.thinking,
|
|
1161
|
-
persistSession: adv.persistSession,
|
|
1162
|
-
pathToClaudeCodeExecutable: adv.pathToClaudeCodeExecutable,
|
|
1163
|
-
agent: adv.agent,
|
|
1164
|
-
mcpServers: adv.mcpServers,
|
|
1165
|
-
sandbox: adv.sandbox,
|
|
1166
|
-
fallbackModel: adv.fallbackModel,
|
|
1167
|
-
enableFileCheckpointing: adv.enableFileCheckpointing,
|
|
1168
|
-
includePartialMessages: adv.includePartialMessages,
|
|
1169
|
-
strictMcpConfig: adv.strictMcpConfig,
|
|
1170
|
-
settingSources: adv.settingSources ?? DEFAULT_SETTING_SOURCES,
|
|
1171
|
-
debug: adv.debug,
|
|
1172
|
-
debugFile: adv.debugFile,
|
|
1173
|
-
env: adv.env,
|
|
1174
|
-
abortController
|
|
1175
|
-
});
|
|
1517
|
+
sessionManager.create(
|
|
1518
|
+
toSessionCreateParams({
|
|
1519
|
+
sessionId: init.session_id,
|
|
1520
|
+
source: normalizedFlat,
|
|
1521
|
+
permissionMode: "default",
|
|
1522
|
+
abortController
|
|
1523
|
+
})
|
|
1524
|
+
);
|
|
1176
1525
|
}
|
|
1177
1526
|
});
|
|
1178
1527
|
const sessionId = await raceWithAbort(
|
|
@@ -1185,7 +1534,8 @@ async function executeClaudeCode(input, sessionManager, serverCwd, toolCache, re
|
|
|
1185
1534
|
sessionId,
|
|
1186
1535
|
status: "running",
|
|
1187
1536
|
pollInterval: 3e3,
|
|
1188
|
-
resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0
|
|
1537
|
+
resumeToken: resumeSecret ? computeResumeToken(sessionId, resumeSecret) : void 0,
|
|
1538
|
+
compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0
|
|
1189
1539
|
};
|
|
1190
1540
|
} catch (err) {
|
|
1191
1541
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1224,6 +1574,13 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1224
1574
|
const sessionInitTimeoutMs = input.sessionInitTimeoutMs ?? 1e4;
|
|
1225
1575
|
const existing = sessionManager.get(input.sessionId);
|
|
1226
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
|
+
}
|
|
1227
1584
|
const allowDiskResume = process.env.CLAUDE_CODE_MCP_ALLOW_DISK_RESUME === "1";
|
|
1228
1585
|
if (!allowDiskResume) {
|
|
1229
1586
|
return {
|
|
@@ -1232,7 +1589,8 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1232
1589
|
error: `Error [${"SESSION_NOT_FOUND" /* SESSION_NOT_FOUND */}]: Session '${input.sessionId}' not found or expired.`
|
|
1233
1590
|
};
|
|
1234
1591
|
}
|
|
1235
|
-
const
|
|
1592
|
+
const resumeSecrets = getResumeSecrets();
|
|
1593
|
+
const resumeSecret = resumeSecrets[0];
|
|
1236
1594
|
if (!resumeSecret) {
|
|
1237
1595
|
return {
|
|
1238
1596
|
sessionId: input.sessionId,
|
|
@@ -1248,8 +1606,7 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1248
1606
|
error: `Error [${"PERMISSION_DENIED" /* PERMISSION_DENIED */}]: resumeToken is required for disk resume fallback.`
|
|
1249
1607
|
};
|
|
1250
1608
|
}
|
|
1251
|
-
|
|
1252
|
-
if (dr.resumeToken !== expectedToken) {
|
|
1609
|
+
if (!isValidResumeToken(input.sessionId, dr.resumeToken, resumeSecrets)) {
|
|
1253
1610
|
return {
|
|
1254
1611
|
sessionId: input.sessionId,
|
|
1255
1612
|
status: "error",
|
|
@@ -1259,38 +1616,27 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1259
1616
|
try {
|
|
1260
1617
|
const abortController2 = new AbortController();
|
|
1261
1618
|
const options2 = buildOptionsFromDiskResume(dr);
|
|
1262
|
-
|
|
1263
|
-
|
|
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,
|
|
1264
1625
|
cwd: options2.cwd ?? dr.cwd ?? "",
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
persistSession: dr.persistSession,
|
|
1280
|
-
pathToClaudeCodeExecutable: dr.pathToClaudeCodeExecutable,
|
|
1281
|
-
agent: dr.agent,
|
|
1282
|
-
mcpServers: dr.mcpServers,
|
|
1283
|
-
sandbox: dr.sandbox,
|
|
1284
|
-
fallbackModel: dr.fallbackModel,
|
|
1285
|
-
enableFileCheckpointing: dr.enableFileCheckpointing,
|
|
1286
|
-
includePartialMessages: dr.includePartialMessages,
|
|
1287
|
-
strictMcpConfig: dr.strictMcpConfig,
|
|
1288
|
-
settingSources: dr.settingSources ?? DEFAULT_SETTING_SOURCES,
|
|
1289
|
-
debug: dr.debug,
|
|
1290
|
-
debugFile: dr.debugFile,
|
|
1291
|
-
env: dr.env,
|
|
1292
|
-
abortController: abortController2
|
|
1293
|
-
});
|
|
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
|
+
);
|
|
1294
1640
|
try {
|
|
1295
1641
|
consumeQuery({
|
|
1296
1642
|
mode: "disk-resume",
|
|
@@ -1371,8 +1717,29 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1371
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.`
|
|
1372
1718
|
};
|
|
1373
1719
|
}
|
|
1374
|
-
const
|
|
1720
|
+
const session = acquired;
|
|
1721
|
+
const options = buildOptions(session);
|
|
1375
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
|
+
}
|
|
1376
1743
|
try {
|
|
1377
1744
|
const handle = consumeQuery({
|
|
1378
1745
|
mode: "resume",
|
|
@@ -1388,44 +1755,20 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1388
1755
|
onInit: (init) => {
|
|
1389
1756
|
if (!input.forkSession) return;
|
|
1390
1757
|
if (init.session_id === input.sessionId) return;
|
|
1391
|
-
if (!sessionManager.get(init.session_id)) {
|
|
1392
|
-
sessionManager.create({
|
|
1393
|
-
sessionId: init.session_id,
|
|
1394
|
-
cwd: existing.cwd,
|
|
1395
|
-
model: existing.model,
|
|
1396
|
-
permissionMode: "default",
|
|
1397
|
-
allowedTools: existing.allowedTools,
|
|
1398
|
-
disallowedTools: existing.disallowedTools,
|
|
1399
|
-
tools: existing.tools,
|
|
1400
|
-
maxTurns: existing.maxTurns,
|
|
1401
|
-
systemPrompt: existing.systemPrompt,
|
|
1402
|
-
agents: existing.agents,
|
|
1403
|
-
maxBudgetUsd: existing.maxBudgetUsd,
|
|
1404
|
-
effort: existing.effort,
|
|
1405
|
-
betas: existing.betas,
|
|
1406
|
-
additionalDirectories: existing.additionalDirectories,
|
|
1407
|
-
outputFormat: existing.outputFormat,
|
|
1408
|
-
thinking: existing.thinking,
|
|
1409
|
-
persistSession: existing.persistSession,
|
|
1410
|
-
pathToClaudeCodeExecutable: existing.pathToClaudeCodeExecutable,
|
|
1411
|
-
agent: existing.agent,
|
|
1412
|
-
mcpServers: existing.mcpServers,
|
|
1413
|
-
sandbox: existing.sandbox,
|
|
1414
|
-
fallbackModel: existing.fallbackModel,
|
|
1415
|
-
enableFileCheckpointing: existing.enableFileCheckpointing,
|
|
1416
|
-
includePartialMessages: existing.includePartialMessages,
|
|
1417
|
-
strictMcpConfig: existing.strictMcpConfig,
|
|
1418
|
-
settingSources: existing.settingSources ?? DEFAULT_SETTING_SOURCES,
|
|
1419
|
-
debug: existing.debug,
|
|
1420
|
-
debugFile: existing.debugFile,
|
|
1421
|
-
env: existing.env,
|
|
1422
|
-
abortController
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
1758
|
sessionManager.update(input.sessionId, {
|
|
1426
1759
|
status: originalStatus,
|
|
1427
1760
|
abortController: void 0
|
|
1428
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
|
+
}
|
|
1429
1772
|
}
|
|
1430
1773
|
});
|
|
1431
1774
|
const sessionId = input.forkSession ? await raceWithAbort(
|
|
@@ -1478,54 +1821,54 @@ async function executeClaudeCodeReply(input, sessionManager, toolCache, requestS
|
|
|
1478
1821
|
// src/tools/tool-discovery.ts
|
|
1479
1822
|
var TOOL_CATALOG = {
|
|
1480
1823
|
Bash: {
|
|
1481
|
-
description: "Run shell commands
|
|
1824
|
+
description: "Run shell commands",
|
|
1482
1825
|
category: "execute"
|
|
1483
1826
|
},
|
|
1484
1827
|
Read: {
|
|
1485
|
-
description: "Read
|
|
1828
|
+
description: "Read file contents (large files: use offset/limit or Grep)",
|
|
1486
1829
|
category: "file_read"
|
|
1487
1830
|
},
|
|
1488
1831
|
Write: {
|
|
1489
|
-
description: "Create
|
|
1832
|
+
description: "Create or overwrite files",
|
|
1490
1833
|
category: "file_write"
|
|
1491
1834
|
},
|
|
1492
1835
|
Edit: {
|
|
1493
|
-
description: "
|
|
1836
|
+
description: "Targeted edits (replace_all is substring-based)",
|
|
1494
1837
|
category: "file_write"
|
|
1495
1838
|
},
|
|
1496
1839
|
Glob: {
|
|
1497
|
-
description: "Find files by
|
|
1840
|
+
description: "Find files by glob pattern",
|
|
1498
1841
|
category: "file_read"
|
|
1499
1842
|
},
|
|
1500
1843
|
Grep: {
|
|
1501
|
-
description: "Search
|
|
1844
|
+
description: "Search file contents (regex)",
|
|
1502
1845
|
category: "file_read"
|
|
1503
1846
|
},
|
|
1504
1847
|
NotebookEdit: {
|
|
1505
|
-
description: "Edit
|
|
1848
|
+
description: "Edit Jupyter notebook cells (Windows paths normalized)",
|
|
1506
1849
|
category: "file_write"
|
|
1507
1850
|
},
|
|
1508
1851
|
WebFetch: {
|
|
1509
|
-
description: "
|
|
1852
|
+
description: "Fetch web page or API content",
|
|
1510
1853
|
category: "network"
|
|
1511
1854
|
},
|
|
1512
|
-
WebSearch: { description: "
|
|
1855
|
+
WebSearch: { description: "Web search", category: "network" },
|
|
1513
1856
|
Task: {
|
|
1514
|
-
description: "Spawn
|
|
1857
|
+
description: "Spawn subagent (must be in allowedTools)",
|
|
1515
1858
|
category: "agent"
|
|
1516
1859
|
},
|
|
1517
|
-
TaskOutput: { description: "Get
|
|
1518
|
-
TaskStop: { description: "Cancel
|
|
1860
|
+
TaskOutput: { description: "Get subagent output", category: "agent" },
|
|
1861
|
+
TaskStop: { description: "Cancel subagent", category: "agent" },
|
|
1519
1862
|
TodoWrite: {
|
|
1520
|
-
description: "
|
|
1863
|
+
description: "Task/todo checklist",
|
|
1521
1864
|
category: "agent"
|
|
1522
1865
|
},
|
|
1523
1866
|
AskUserQuestion: {
|
|
1524
|
-
description: "Ask
|
|
1867
|
+
description: "Ask user a question",
|
|
1525
1868
|
category: "interaction"
|
|
1526
1869
|
},
|
|
1527
1870
|
TeamDelete: {
|
|
1528
|
-
description: "Delete
|
|
1871
|
+
description: "Delete team (may need shutdown_approved first)",
|
|
1529
1872
|
category: "agent"
|
|
1530
1873
|
}
|
|
1531
1874
|
};
|
|
@@ -1545,8 +1888,10 @@ function defaultCatalogTools() {
|
|
|
1545
1888
|
}
|
|
1546
1889
|
var ToolDiscoveryCache = class {
|
|
1547
1890
|
cached;
|
|
1548
|
-
|
|
1891
|
+
onUpdated;
|
|
1892
|
+
constructor(initial, onUpdated) {
|
|
1549
1893
|
this.cached = initial ?? defaultCatalogTools();
|
|
1894
|
+
this.onUpdated = onUpdated;
|
|
1550
1895
|
}
|
|
1551
1896
|
getTools() {
|
|
1552
1897
|
return this.cached;
|
|
@@ -1555,7 +1900,13 @@ var ToolDiscoveryCache = class {
|
|
|
1555
1900
|
const discovered = discoverToolsFromInit(initTools);
|
|
1556
1901
|
const next = mergeToolLists(discovered, defaultCatalogTools());
|
|
1557
1902
|
const updated = JSON.stringify(next) !== JSON.stringify(this.cached);
|
|
1558
|
-
if (updated)
|
|
1903
|
+
if (updated) {
|
|
1904
|
+
this.cached = next;
|
|
1905
|
+
try {
|
|
1906
|
+
this.onUpdated?.(this.cached);
|
|
1907
|
+
} catch {
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1559
1910
|
return { updated, tools: this.cached };
|
|
1560
1911
|
}
|
|
1561
1912
|
};
|
|
@@ -1580,9 +1931,8 @@ function groupByCategory(tools) {
|
|
|
1580
1931
|
function buildInternalToolsDescription(tools) {
|
|
1581
1932
|
const grouped = groupByCategory(tools);
|
|
1582
1933
|
const categories = Object.keys(grouped).sort((a, b) => a.localeCompare(b));
|
|
1583
|
-
let desc =
|
|
1584
|
-
desc += "
|
|
1585
|
-
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";
|
|
1586
1936
|
for (const category of categories) {
|
|
1587
1937
|
desc += `
|
|
1588
1938
|
[${category}]
|
|
@@ -1592,8 +1942,7 @@ function buildInternalToolsDescription(tools) {
|
|
|
1592
1942
|
`;
|
|
1593
1943
|
}
|
|
1594
1944
|
}
|
|
1595
|
-
desc += "\
|
|
1596
|
-
desc += 'Use `allowedTools` to pre-approve tools (no permission prompts). Use `disallowedTools` to permanently block specific tools. Any tool not in either list will pause the session (status: "waiting_permission") until approved or denied via claude_code_check.\n';
|
|
1945
|
+
desc += "\nPermission control: allowedTools auto-approves; disallowedTools always denies; others require approval.\n";
|
|
1597
1946
|
return desc;
|
|
1598
1947
|
}
|
|
1599
1948
|
|
|
@@ -1652,6 +2001,117 @@ function toEvents(events, opts) {
|
|
|
1652
2001
|
return { id: e.id, type: e.type, data: e.data, timestamp: e.timestamp };
|
|
1653
2002
|
});
|
|
1654
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
|
+
}
|
|
1655
2115
|
function buildResult(sessionManager, toolCache, input) {
|
|
1656
2116
|
const responseMode = input.responseMode ?? "minimal";
|
|
1657
2117
|
const po = input.pollOptions ?? {};
|
|
@@ -1664,6 +2124,7 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
1664
2124
|
const includeStructuredOutput = po.includeStructuredOutput ?? responseMode === "full";
|
|
1665
2125
|
const includeTerminalEvents = po.includeTerminalEvents ?? responseMode === "full";
|
|
1666
2126
|
const includeProgressEvents = po.includeProgressEvents ?? responseMode === "full";
|
|
2127
|
+
const maxBytes = po.maxBytes;
|
|
1667
2128
|
const maxEvents = input.maxEvents ?? (responseMode === "minimal" ? 200 : void 0);
|
|
1668
2129
|
const sessionId = input.sessionId;
|
|
1669
2130
|
const session = sessionManager.get(sessionId);
|
|
@@ -1676,7 +2137,7 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
1676
2137
|
let truncated = false;
|
|
1677
2138
|
const truncatedFields = [];
|
|
1678
2139
|
const windowEvents = maxEvents !== void 0 && rawEvents.length > maxEvents ? rawEvents.slice(0, maxEvents) : rawEvents;
|
|
1679
|
-
|
|
2140
|
+
let nextCursor = maxEvents !== void 0 && rawEvents.length > maxEvents ? windowEvents.length > 0 ? windowEvents[windowEvents.length - 1].id + 1 : rawNextCursor : rawNextCursor;
|
|
1680
2141
|
if (maxEvents !== void 0 && rawEvents.length > maxEvents) {
|
|
1681
2142
|
truncated = true;
|
|
1682
2143
|
truncatedFields.push("events");
|
|
@@ -1699,8 +2160,28 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
1699
2160
|
})();
|
|
1700
2161
|
const pending = status === "waiting_permission" ? sessionManager.listPendingPermissions(sessionId) : [];
|
|
1701
2162
|
const stored = status === "idle" || status === "error" ? sessionManager.getResult(sessionId) : void 0;
|
|
1702
|
-
const initTools =
|
|
2163
|
+
const initTools = sessionManager.getInitTools(sessionId);
|
|
1703
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
|
+
}
|
|
1704
2185
|
return {
|
|
1705
2186
|
sessionId,
|
|
1706
2187
|
status,
|
|
@@ -1708,14 +2189,11 @@ function buildResult(sessionManager, toolCache, input) {
|
|
|
1708
2189
|
cursorResetTo,
|
|
1709
2190
|
truncated: truncated ? true : void 0,
|
|
1710
2191
|
truncatedFields: truncatedFields.length > 0 ? truncatedFields : void 0,
|
|
1711
|
-
events:
|
|
1712
|
-
includeUsage,
|
|
1713
|
-
includeModelUsage,
|
|
1714
|
-
includeStructuredOutput,
|
|
1715
|
-
slim: responseMode === "minimal"
|
|
1716
|
-
}),
|
|
2192
|
+
events: cappedEvents.events,
|
|
1717
2193
|
nextCursor,
|
|
1718
2194
|
availableTools,
|
|
2195
|
+
toolValidation: toolValidation.summary,
|
|
2196
|
+
compatWarnings: compatWarnings.length > 0 ? compatWarnings : void 0,
|
|
1719
2197
|
actions: includeActions && status === "waiting_permission" ? pending.map((req) => {
|
|
1720
2198
|
const expiresMs = req.expiresAt ? Date.parse(req.expiresAt) : Number.NaN;
|
|
1721
2199
|
const remainingMs = Number.isFinite(expiresMs) ? Math.max(0, expiresMs - Date.now()) : void 0;
|
|
@@ -1775,7 +2253,14 @@ function redactAgentResult(result, opts) {
|
|
|
1775
2253
|
structuredOutput: opts.includeStructuredOutput ? structuredOutput : void 0
|
|
1776
2254
|
};
|
|
1777
2255
|
}
|
|
1778
|
-
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
|
+
}
|
|
1779
2264
|
if (typeof input.sessionId !== "string" || input.sessionId.trim() === "") {
|
|
1780
2265
|
return {
|
|
1781
2266
|
sessionId: "",
|
|
@@ -1831,7 +2316,14 @@ function executeClaudeCodeCheck(input, sessionManager, toolCache) {
|
|
|
1831
2316
|
}
|
|
1832
2317
|
|
|
1833
2318
|
// src/tools/claude-code-session.ts
|
|
1834
|
-
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
|
+
}
|
|
1835
2327
|
const toSessionJson = (s) => input.includeSensitive ? sessionManager.toSensitiveJSON(s) : sessionManager.toPublicJSON(s);
|
|
1836
2328
|
switch (input.action) {
|
|
1837
2329
|
case "list": {
|
|
@@ -1903,7 +2395,8 @@ var RESOURCE_SCHEME = "claude-code-mcp";
|
|
|
1903
2395
|
var RESOURCE_URIS = {
|
|
1904
2396
|
serverInfo: `${RESOURCE_SCHEME}:///server-info`,
|
|
1905
2397
|
internalTools: `${RESOURCE_SCHEME}:///internal-tools`,
|
|
1906
|
-
gotchas: `${RESOURCE_SCHEME}:///gotchas
|
|
2398
|
+
gotchas: `${RESOURCE_SCHEME}:///gotchas`,
|
|
2399
|
+
compatReport: `${RESOURCE_SCHEME}:///compat-report`
|
|
1907
2400
|
};
|
|
1908
2401
|
function asTextResource(uri, text, mimeType) {
|
|
1909
2402
|
return {
|
|
@@ -1923,7 +2416,7 @@ function registerResources(server, deps) {
|
|
|
1923
2416
|
serverInfoUri.toString(),
|
|
1924
2417
|
{
|
|
1925
2418
|
title: "Server Info",
|
|
1926
|
-
description: "
|
|
2419
|
+
description: "Server metadata (version/platform/runtime).",
|
|
1927
2420
|
mimeType: "application/json"
|
|
1928
2421
|
},
|
|
1929
2422
|
() => asTextResource(
|
|
@@ -1949,7 +2442,7 @@ function registerResources(server, deps) {
|
|
|
1949
2442
|
toolsUri.toString(),
|
|
1950
2443
|
{
|
|
1951
2444
|
title: "Internal Tools",
|
|
1952
|
-
description: "Claude Code internal tool catalog (
|
|
2445
|
+
description: "Claude Code internal tool catalog (runtime-aware).",
|
|
1953
2446
|
mimeType: "application/json"
|
|
1954
2447
|
},
|
|
1955
2448
|
() => asTextResource(
|
|
@@ -1972,10 +2465,10 @@ function registerResources(server, deps) {
|
|
|
1972
2465
|
[
|
|
1973
2466
|
"# claude-code-mcp: gotchas",
|
|
1974
2467
|
"",
|
|
1975
|
-
"- Permission approvals have a timeout (default 60s) and auto-deny (`actions[].expiresAt`/`remainingMs`).",
|
|
2468
|
+
"- Permission approvals have a timeout (default 60s, server-clamped to 5min) and auto-deny (`actions[].expiresAt`/`remainingMs`).",
|
|
1976
2469
|
"- `Read` has a per-call size cap in practice (often ~256KB); for large files use `offset`/`limit` or chunk with `Grep`.",
|
|
1977
2470
|
"- `Edit` with `replace_all=true` is substring replacement; if no match is found the tool returns an error.",
|
|
1978
|
-
"-
|
|
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`.",
|
|
1979
2472
|
"- `TeamDelete` may require members to reach `shutdown_approved`; cleanup can be asynchronous during shutdown.",
|
|
1980
2473
|
'- Skills may become available later in the same session (early calls may show "Unknown").',
|
|
1981
2474
|
"- Some internal features (e.g. ToolSearch) may not appear in `availableTools` because it is derived from SDK `system/init.tools`.",
|
|
@@ -1984,34 +2477,105 @@ function registerResources(server, deps) {
|
|
|
1984
2477
|
"text/markdown"
|
|
1985
2478
|
)
|
|
1986
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
|
+
);
|
|
1987
2529
|
}
|
|
1988
2530
|
|
|
1989
2531
|
// src/server.ts
|
|
1990
|
-
var SERVER_VERSION = true ? "2.0
|
|
1991
|
-
function
|
|
2532
|
+
var SERVER_VERSION = true ? "2.2.0" : "0.0.0-dev";
|
|
2533
|
+
function createServerContext(serverCwd) {
|
|
1992
2534
|
const sessionManager = new SessionManager();
|
|
1993
|
-
const
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
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
|
+
}
|
|
1997
2561
|
});
|
|
1998
2562
|
const agentDefinitionSchema = z.object({
|
|
1999
2563
|
description: z.string(),
|
|
2000
2564
|
prompt: z.string(),
|
|
2001
|
-
tools: z.array(z.string()).optional(),
|
|
2002
|
-
disallowedTools: z.array(z.string()).optional(),
|
|
2003
|
-
model: z.enum(AGENT_MODELS).optional(),
|
|
2004
|
-
maxTurns: z.number().int().positive().optional(),
|
|
2005
|
-
mcpServers: z.array(z.union([z.string(), z.record(z.string(), z.unknown())])).optional(),
|
|
2006
|
-
skills: z.array(z.string()).optional(),
|
|
2007
|
-
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")
|
|
2008
2572
|
});
|
|
2009
2573
|
const systemPromptSchema = z.union([
|
|
2010
2574
|
z.string(),
|
|
2011
2575
|
z.object({
|
|
2012
2576
|
type: z.literal("preset"),
|
|
2013
2577
|
preset: z.literal("claude_code"),
|
|
2014
|
-
append: z.string().optional().describe("
|
|
2578
|
+
append: z.string().optional().describe("Default: none")
|
|
2015
2579
|
})
|
|
2016
2580
|
]);
|
|
2017
2581
|
const toolsConfigSchema = z.union([
|
|
@@ -2029,47 +2593,135 @@ function createServer(serverCwd) {
|
|
|
2029
2593
|
}),
|
|
2030
2594
|
z.object({ type: z.literal("disabled") })
|
|
2031
2595
|
]);
|
|
2596
|
+
const effortOptionSchema = z.enum(EFFORT_LEVELS).optional();
|
|
2597
|
+
const thinkingOptionSchema = thinkingSchema.optional();
|
|
2032
2598
|
const outputFormatSchema = z.object({
|
|
2033
2599
|
type: z.literal("json_schema"),
|
|
2034
2600
|
schema: z.record(z.string(), z.unknown())
|
|
2035
2601
|
});
|
|
2036
|
-
const
|
|
2037
|
-
tools: toolsConfigSchema.optional().describe("
|
|
2602
|
+
const sharedOptionFieldsSchemaShape = {
|
|
2603
|
+
tools: toolsConfigSchema.optional().describe("Tool set. Default: SDK"),
|
|
2038
2604
|
persistSession: z.boolean().optional().describe("Default: true"),
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
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"),
|
|
2042
2607
|
maxBudgetUsd: z.number().positive().optional().describe("Default: none"),
|
|
2043
|
-
effort: z.enum(EFFORT_LEVELS).optional().describe("Default: SDK"),
|
|
2044
2608
|
betas: z.array(z.string()).optional().describe("Default: none"),
|
|
2045
2609
|
additionalDirectories: z.array(z.string()).optional().describe("Default: none"),
|
|
2046
|
-
outputFormat: outputFormatSchema.optional().describe("Default: none
|
|
2047
|
-
thinking: thinkingSchema.optional().describe("Default: SDK"),
|
|
2610
|
+
outputFormat: outputFormatSchema.optional().describe("Default: none"),
|
|
2048
2611
|
pathToClaudeCodeExecutable: z.string().optional().describe("Default: SDK-bundled"),
|
|
2049
2612
|
mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("Default: none"),
|
|
2050
2613
|
sandbox: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
|
|
2051
2614
|
fallbackModel: z.string().optional().describe("Default: none"),
|
|
2052
2615
|
enableFileCheckpointing: z.boolean().optional().describe("Default: false"),
|
|
2053
|
-
includePartialMessages: z.boolean().optional().describe("
|
|
2616
|
+
includePartialMessages: z.boolean().optional().describe("Default: false"),
|
|
2054
2617
|
strictMcpConfig: z.boolean().optional().describe("Default: false"),
|
|
2055
|
-
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"),
|
|
2056
2619
|
debug: z.boolean().optional().describe("Default: false"),
|
|
2057
|
-
debugFile: z.string().optional().describe("
|
|
2058
|
-
env: z.record(z.string(), z.string().optional()).optional().describe("
|
|
2059
|
-
}
|
|
2060
|
-
|
|
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(
|
|
2061
2701
|
"claude_code",
|
|
2062
|
-
buildInternalToolsDescription(toolCache.getTools()),
|
|
2063
2702
|
{
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
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
|
+
}
|
|
2073
2725
|
},
|
|
2074
2726
|
async (args, extra) => {
|
|
2075
2727
|
try {
|
|
@@ -2088,76 +2740,50 @@ function createServer(serverCwd) {
|
|
|
2088
2740
|
text: JSON.stringify(result, null, 2)
|
|
2089
2741
|
}
|
|
2090
2742
|
],
|
|
2743
|
+
structuredContent: result,
|
|
2091
2744
|
isError
|
|
2092
2745
|
};
|
|
2093
2746
|
} catch (err) {
|
|
2094
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
|
+
};
|
|
2095
2753
|
return {
|
|
2096
2754
|
content: [
|
|
2097
2755
|
{
|
|
2098
2756
|
type: "text",
|
|
2099
|
-
text:
|
|
2757
|
+
text: JSON.stringify(errorResult, null, 2)
|
|
2100
2758
|
}
|
|
2101
2759
|
],
|
|
2760
|
+
structuredContent: errorResult,
|
|
2102
2761
|
isError: true
|
|
2103
2762
|
};
|
|
2104
2763
|
}
|
|
2105
2764
|
}
|
|
2106
2765
|
);
|
|
2107
|
-
server.
|
|
2766
|
+
server.registerTool(
|
|
2108
2767
|
"claude_code_reply",
|
|
2109
|
-
`Send a follow-up message to an existing Claude Code session.
|
|
2110
|
-
|
|
2111
|
-
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.
|
|
2112
|
-
|
|
2113
|
-
Supports session forking (forkSession=true) to explore alternative approaches without modifying the original session.
|
|
2114
|
-
|
|
2115
|
-
Defaults:
|
|
2116
|
-
- forkSession: false
|
|
2117
|
-
- sessionInitTimeoutMs: 10000 (only used when forkSession=true)
|
|
2118
|
-
- permissionRequestTimeoutMs: 60000
|
|
2119
|
-
- Disk resume: disabled unless CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1
|
|
2120
|
-
|
|
2121
|
-
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.`,
|
|
2122
2768
|
{
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
effort: z.enum(EFFORT_LEVELS).optional().describe("Default: SDK"),
|
|
2142
|
-
betas: z.array(z.string()).optional().describe("Default: none"),
|
|
2143
|
-
additionalDirectories: z.array(z.string()).optional().describe("Default: none"),
|
|
2144
|
-
outputFormat: outputFormatSchema.optional().describe("Default: none"),
|
|
2145
|
-
thinking: thinkingSchema.optional().describe("Default: SDK"),
|
|
2146
|
-
resumeSessionAt: z.string().optional().describe("Resume to specific message UUID. Default: none"),
|
|
2147
|
-
pathToClaudeCodeExecutable: z.string().optional().describe("Default: SDK-bundled"),
|
|
2148
|
-
mcpServers: z.record(z.string(), z.record(z.string(), z.unknown())).optional().describe("Default: none"),
|
|
2149
|
-
sandbox: z.record(z.string(), z.unknown()).optional().describe("Default: none"),
|
|
2150
|
-
fallbackModel: z.string().optional().describe("Default: none"),
|
|
2151
|
-
enableFileCheckpointing: z.boolean().optional().describe("Default: false"),
|
|
2152
|
-
includePartialMessages: z.boolean().optional().describe("Stream events to claude_code_check. Default: false"),
|
|
2153
|
-
strictMcpConfig: z.boolean().optional().describe("Default: false"),
|
|
2154
|
-
settingSources: z.array(z.enum(["user", "project", "local"])).optional().describe("Default: ['user','project','local']. [] = isolation mode"),
|
|
2155
|
-
debug: z.boolean().optional().describe("Default: false"),
|
|
2156
|
-
debugFile: z.string().optional().describe("Enables debug. Default: none"),
|
|
2157
|
-
env: z.record(z.string(), z.string().optional()).optional().describe("Default: none")
|
|
2158
|
-
}).optional().describe(
|
|
2159
|
-
"Disk resume config (needs CLAUDE_CODE_MCP_ALLOW_DISK_RESUME=1). Requires resumeToken + cwd."
|
|
2160
|
-
)
|
|
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
|
+
}
|
|
2161
2787
|
},
|
|
2162
2788
|
async (args, extra) => {
|
|
2163
2789
|
try {
|
|
@@ -2170,137 +2796,339 @@ Disk resume: If the server restarted and the session is no longer in memory, set
|
|
|
2170
2796
|
text: JSON.stringify(result, null, 2)
|
|
2171
2797
|
}
|
|
2172
2798
|
],
|
|
2799
|
+
structuredContent: result,
|
|
2173
2800
|
isError
|
|
2174
2801
|
};
|
|
2175
2802
|
} catch (err) {
|
|
2176
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
|
+
};
|
|
2177
2809
|
return {
|
|
2178
2810
|
content: [
|
|
2179
2811
|
{
|
|
2180
2812
|
type: "text",
|
|
2181
|
-
text:
|
|
2813
|
+
text: JSON.stringify(errorResult, null, 2)
|
|
2182
2814
|
}
|
|
2183
2815
|
],
|
|
2816
|
+
structuredContent: errorResult,
|
|
2184
2817
|
isError: true
|
|
2185
2818
|
};
|
|
2186
2819
|
}
|
|
2187
2820
|
}
|
|
2188
2821
|
);
|
|
2189
|
-
server.
|
|
2822
|
+
server.registerTool(
|
|
2190
2823
|
"claude_code_session",
|
|
2191
|
-
`List, inspect, or cancel Claude Code sessions.
|
|
2192
|
-
|
|
2193
|
-
- action="list": Get all sessions with their status, cost, turn count, and settings.
|
|
2194
|
-
- action="get": Get full details for one session (pass sessionId). Add includeSensitive=true to also see cwd, systemPrompt, agents, and additionalDirectories.
|
|
2195
|
-
- action="cancel": Stop a running session immediately (pass sessionId).`,
|
|
2196
2824
|
{
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
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
|
+
}
|
|
2200
2838
|
},
|
|
2201
|
-
async (args) => {
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
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
|
+
}
|
|
2212
2870
|
}
|
|
2213
2871
|
);
|
|
2214
|
-
server.
|
|
2872
|
+
server.registerTool(
|
|
2215
2873
|
"claude_code_check",
|
|
2216
|
-
`Query a running session for new events, retrieve the final result, or respond to permission requests.
|
|
2217
|
-
|
|
2218
|
-
Two actions:
|
|
2219
|
-
|
|
2220
|
-
Defaults (poll):
|
|
2221
|
-
- responseMode: "minimal"
|
|
2222
|
-
- minimal mode strips verbose fields from assistant messages (usage, model, id, cache_control) and filters out noisy progress events (tool_progress, auth_status)
|
|
2223
|
-
- maxEvents: 200 in minimal mode (unlimited in full mode unless maxEvents is set)
|
|
2224
|
-
|
|
2225
|
-
action="poll" \u2014 Retrieve events since the last poll.
|
|
2226
|
-
Returns events (agent output, progress updates, permission requests, errors, final result).
|
|
2227
|
-
Pass the cursor from the previous poll's nextCursor for incremental updates. Omit cursor to get all buffered events.
|
|
2228
|
-
If the agent is waiting for permission, the response includes an "actions" array with pending requests.
|
|
2229
|
-
|
|
2230
|
-
action="respond_permission" \u2014 Approve or deny a pending permission request.
|
|
2231
|
-
Pass the requestId from the actions array, plus decision="allow" or decision="deny".
|
|
2232
|
-
Approving resumes agent execution. Denying (with optional interrupt=true) can halt the entire session.
|
|
2233
|
-
The response also includes the latest poll state (events, status, etc.), so a separate poll call is not needed.`,
|
|
2234
2874
|
{
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
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
|
+
}
|
|
2259
2910
|
},
|
|
2260
|
-
async (args) => {
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
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
|
+
}
|
|
2272
2945
|
}
|
|
2273
2946
|
);
|
|
2274
2947
|
registerResources(server, { toolCache });
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
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));
|
|
2281
2976
|
}
|
|
2282
2977
|
|
|
2283
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
|
+
}
|
|
2284
2992
|
async function main() {
|
|
2285
2993
|
const serverCwd = process.cwd();
|
|
2286
|
-
const
|
|
2994
|
+
const ctx = createServerContext(serverCwd);
|
|
2995
|
+
const server = ctx.server;
|
|
2996
|
+
const sessionManager = ctx.sessionManager;
|
|
2287
2997
|
const transport = new StdioServerTransport();
|
|
2288
2998
|
let closing = false;
|
|
2289
|
-
|
|
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") => {
|
|
2290
3012
|
if (closing) return;
|
|
2291
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);
|
|
2292
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();
|
|
2293
3032
|
await server.close();
|
|
2294
3033
|
} catch {
|
|
2295
3034
|
}
|
|
2296
|
-
process.exitCode =
|
|
2297
|
-
|
|
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();
|
|
2298
3072
|
};
|
|
2299
|
-
|
|
2300
|
-
|
|
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");
|
|
3092
|
+
};
|
|
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);
|
|
2301
3118
|
await server.connect(transport);
|
|
3119
|
+
server.sendToolListChanged();
|
|
3120
|
+
server.sendResourceListChanged();
|
|
2302
3121
|
checkWindowsBashAvailability();
|
|
2303
|
-
|
|
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})`);
|
|
2304
3132
|
}
|
|
2305
3133
|
main().catch((err) => {
|
|
2306
3134
|
console.error("Fatal error:", err);
|