mcp-agents 0.5.8 → 0.6.5
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/README.md +29 -5
- package/package.json +1 -1
- package/server.js +231 -20
package/README.md
CHANGED
|
@@ -78,14 +78,29 @@ Any additional `tools/call` arguments are ignored (for example `model` or `model
|
|
|
78
78
|
### `codex` (pass-through)
|
|
79
79
|
|
|
80
80
|
The codex provider passes through to Codex's native MCP server (`codex mcp-server`)
|
|
81
|
-
|
|
81
|
+
inside an isolated `CODEX_HOME`. The bridge copies `auth.json` into a temporary Codex
|
|
82
|
+
home, writes a minimal `config.toml`, and does not inherit your normal external MCP
|
|
83
|
+
server list. That keeps Codex from recursively starting other agent tools like Claude
|
|
84
|
+
or Gemini during bridge calls.
|
|
82
85
|
|
|
83
86
|
| CLI Flag | Default | Codex config key |
|
|
84
87
|
|----------|---------|-----------------|
|
|
85
88
|
| `--model` | `gpt-5.4` | `model` |
|
|
86
|
-
| `--model_reasoning_effort` | `
|
|
89
|
+
| `--model_reasoning_effort` | `xhigh` | `model_reasoning_effort` |
|
|
87
90
|
|
|
88
|
-
Hardcoded defaults: `sandbox_mode=read-only`, `approval_policy=never
|
|
91
|
+
Hardcoded defaults: `sandbox_mode=read-only`, `approval_policy=never`,
|
|
92
|
+
`features.multi_agent=false`.
|
|
93
|
+
|
|
94
|
+
Startup flags set server-wide defaults for the native Codex MCP server. Per-call overrides still work through the native `codex` tool schema, for example:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"prompt": "Review this diff",
|
|
99
|
+
"config": {
|
|
100
|
+
"model_reasoning_effort": "medium"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
89
104
|
|
|
90
105
|
## Integration with Claude Code
|
|
91
106
|
|
|
@@ -119,7 +134,7 @@ Optional Gemini sandbox mode in `.mcp.json`:
|
|
|
119
134
|
}
|
|
120
135
|
```
|
|
121
136
|
|
|
122
|
-
Override codex defaults at server startup
|
|
137
|
+
Override codex defaults at server startup:
|
|
123
138
|
|
|
124
139
|
```json
|
|
125
140
|
{
|
|
@@ -132,6 +147,11 @@ Override codex defaults at server startup (not via `tools/call` arguments):
|
|
|
132
147
|
}
|
|
133
148
|
```
|
|
134
149
|
|
|
150
|
+
The startup default can still be overridden for a single Codex tool call by passing `config.model_reasoning_effort` to the native `codex` tool.
|
|
151
|
+
|
|
152
|
+
Because the bridge runs in an isolated Codex home, inherited MCP servers from your normal
|
|
153
|
+
`~/.codex/config.toml` are intentionally unavailable inside bridged Codex sessions.
|
|
154
|
+
|
|
135
155
|
<details>
|
|
136
156
|
<summary>Alternative: using npx (slower, not recommended)</summary>
|
|
137
157
|
|
|
@@ -196,7 +216,11 @@ After `npm link`, any edits to `server.js` take effect immediately — no reinst
|
|
|
196
216
|
4. Client calls `tools/call` with the tool name and a `prompt`
|
|
197
217
|
5. The server runs the CLI as a child process and returns tool text (Claude JSON `result`, or stdout/stderr for other providers)
|
|
198
218
|
|
|
199
|
-
The server
|
|
219
|
+
The server keeps a small keepalive timer so Node.js does not exit prematurely
|
|
220
|
+
when stdin reaches EOF before an async subprocess registers an active handle.
|
|
221
|
+
For Claude and Gemini provider mode, that keepalive is cleared during shutdown:
|
|
222
|
+
the server now exits when the MCP stdio connection closes and kills any tracked
|
|
223
|
+
detached provider child process groups that would otherwise linger.
|
|
200
224
|
|
|
201
225
|
## License
|
|
202
226
|
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
/* eslint-disable no-console */
|
|
3
3
|
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
copyFileSync,
|
|
7
|
+
existsSync,
|
|
8
|
+
mkdtempSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
6
14
|
import { dirname, join } from "node:path";
|
|
7
15
|
import { fileURLToPath } from "node:url";
|
|
8
16
|
|
|
@@ -19,8 +27,13 @@ const VERSION = JSON.parse(
|
|
|
19
27
|
).version;
|
|
20
28
|
|
|
21
29
|
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
30
|
+
const DEFAULT_CODEX_MODEL = "gpt-5.4";
|
|
31
|
+
const DEFAULT_CODEX_MODEL_REASONING_EFFORT = "xhigh";
|
|
22
32
|
const MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
23
33
|
const CLAUDE_EMPTY_OUTPUT_MAX_ATTEMPTS = 2;
|
|
34
|
+
const SIGNAL_CODES = { SIGHUP: 1, SIGINT: 2, SIGTERM: 15 };
|
|
35
|
+
const SHUTDOWN_TIMEOUT_MS = 3_000;
|
|
36
|
+
let fatalShutdown;
|
|
24
37
|
|
|
25
38
|
// ---------------------------------------------------------------------------
|
|
26
39
|
// CLI Backend Definitions
|
|
@@ -111,8 +124,8 @@ Usage: mcp-agents [options]
|
|
|
111
124
|
|
|
112
125
|
Options:
|
|
113
126
|
--provider <name> CLI backend to use (${providers}) [default: codex]
|
|
114
|
-
--model <model> Codex model [default:
|
|
115
|
-
--model_reasoning_effort <e> Codex reasoning effort [default:
|
|
127
|
+
--model <model> Codex model [default: ${DEFAULT_CODEX_MODEL}]
|
|
128
|
+
--model_reasoning_effort <e> Codex reasoning effort [default: ${DEFAULT_CODEX_MODEL_REASONING_EFFORT}]
|
|
116
129
|
--timeout <seconds> Default timeout per call [default: 300]
|
|
117
130
|
--help, -h Show this help message
|
|
118
131
|
--version, -v Show version number`);
|
|
@@ -193,12 +206,19 @@ function parseArgs() {
|
|
|
193
206
|
* on timeout — prevents orphan child processes.
|
|
194
207
|
* @param {string} command
|
|
195
208
|
* @param {string[]} args
|
|
196
|
-
* @param {{
|
|
209
|
+
* @param {{
|
|
210
|
+
* timeoutMs?: number,
|
|
211
|
+
* stdinData?: string,
|
|
212
|
+
* onSpawn?: (childInfo: { pid?: number, killGroup: () => void }) => void,
|
|
213
|
+
* onSettled?: (pid?: number) => void,
|
|
214
|
+
* }} [opts]
|
|
197
215
|
* @returns {Promise<{ output: string, stdoutBytes: number, stderrBytes: number, durationMs: number }>}
|
|
198
216
|
*/
|
|
199
217
|
function runCli(command, args, opts = {}) {
|
|
200
218
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
201
219
|
const stdinData = opts.stdinData;
|
|
220
|
+
const onSpawn = opts.onSpawn;
|
|
221
|
+
const onSettled = opts.onSettled;
|
|
202
222
|
const startedAt = Date.now();
|
|
203
223
|
|
|
204
224
|
return new Promise((resolve, reject) => {
|
|
@@ -225,11 +245,13 @@ function runCli(command, args, opts = {}) {
|
|
|
225
245
|
const killGroup = () => {
|
|
226
246
|
try { process.kill(-child.pid, "SIGKILL"); } catch {}
|
|
227
247
|
};
|
|
248
|
+
onSpawn?.({ pid: child.pid, killGroup });
|
|
228
249
|
|
|
229
250
|
const done = (err) => {
|
|
230
251
|
clearTimeout(timer);
|
|
231
252
|
if (settled) return;
|
|
232
253
|
settled = true;
|
|
254
|
+
onSettled?.(child.pid);
|
|
233
255
|
err ? reject(err) : resolve({
|
|
234
256
|
output: (stdout || stderr || "").trimEnd(),
|
|
235
257
|
stdoutBytes: stdoutLen,
|
|
@@ -262,6 +284,7 @@ function runCli(command, args, opts = {}) {
|
|
|
262
284
|
const timer = setTimeout(() => {
|
|
263
285
|
killGroup();
|
|
264
286
|
}, timeoutMs);
|
|
287
|
+
timer.unref();
|
|
265
288
|
|
|
266
289
|
child.on("error", (err) => {
|
|
267
290
|
done(new Error(`Failed to start ${command}: ${err.message}`));
|
|
@@ -284,22 +307,108 @@ function runCli(command, args, opts = {}) {
|
|
|
284
307
|
});
|
|
285
308
|
}
|
|
286
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Resolve the source Codex home used by the parent process.
|
|
312
|
+
* @returns {string}
|
|
313
|
+
*/
|
|
314
|
+
function resolveCodexHome() {
|
|
315
|
+
return process.env.CODEX_HOME || join(process.env.HOME || tmpdir(), ".codex");
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Quote a string for TOML output.
|
|
320
|
+
* @param {string} value
|
|
321
|
+
* @returns {string}
|
|
322
|
+
*/
|
|
323
|
+
function toTomlString(value) {
|
|
324
|
+
return JSON.stringify(value);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Build the minimal config for the isolated Codex bridge runtime.
|
|
329
|
+
* @param {{ model: string, modelReasoningEffort: string }} opts
|
|
330
|
+
* @returns {string}
|
|
331
|
+
*/
|
|
332
|
+
function buildCodexBridgeConfig({ model, modelReasoningEffort }) {
|
|
333
|
+
return [
|
|
334
|
+
`model = ${toTomlString(model)}`,
|
|
335
|
+
`model_reasoning_effort = ${toTomlString(modelReasoningEffort)}`,
|
|
336
|
+
'approval_policy = "never"',
|
|
337
|
+
'sandbox_mode = "read-only"',
|
|
338
|
+
"",
|
|
339
|
+
"[features]",
|
|
340
|
+
"multi_agent = false",
|
|
341
|
+
"",
|
|
342
|
+
].join("\n");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Create an isolated Codex home that preserves auth but strips inherited MCP servers.
|
|
347
|
+
* @param {{ model: string, modelReasoningEffort: string }} opts
|
|
348
|
+
* @returns {string}
|
|
349
|
+
*/
|
|
350
|
+
function createIsolatedCodexHome({ model, modelReasoningEffort }) {
|
|
351
|
+
const codexHome = mkdtempSync(join(tmpdir(), "mcp-agents-codex-"));
|
|
352
|
+
const sourceAuthPath = join(resolveCodexHome(), "auth.json");
|
|
353
|
+
const targetAuthPath = join(codexHome, "auth.json");
|
|
354
|
+
const configPath = join(codexHome, "config.toml");
|
|
355
|
+
|
|
356
|
+
if (existsSync(sourceAuthPath)) {
|
|
357
|
+
copyFileSync(sourceAuthPath, targetAuthPath);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
writeFileSync(
|
|
361
|
+
configPath,
|
|
362
|
+
buildCodexBridgeConfig({ model, modelReasoningEffort }),
|
|
363
|
+
"utf8",
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
return codexHome;
|
|
367
|
+
}
|
|
368
|
+
|
|
287
369
|
/**
|
|
288
370
|
* Spawn codex mcp-server as a pass-through, piping stdio directly.
|
|
289
371
|
* @param {{ model?: string, modelReasoningEffort?: string }} opts
|
|
290
372
|
*/
|
|
291
373
|
function runCodexPassthrough({ model, modelReasoningEffort }) {
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
"-c", "approval_policy=never",
|
|
297
|
-
"-c", `model_reasoning_effort=${modelReasoningEffort || "high"}`,
|
|
298
|
-
];
|
|
374
|
+
const resolvedModel = model || DEFAULT_CODEX_MODEL;
|
|
375
|
+
const resolvedModelReasoningEffort =
|
|
376
|
+
modelReasoningEffort || DEFAULT_CODEX_MODEL_REASONING_EFFORT;
|
|
377
|
+
let isolatedCodexHome;
|
|
299
378
|
|
|
300
|
-
|
|
379
|
+
try {
|
|
380
|
+
isolatedCodexHome = createIsolatedCodexHome({
|
|
381
|
+
model: resolvedModel,
|
|
382
|
+
modelReasoningEffort: resolvedModelReasoningEffort,
|
|
383
|
+
});
|
|
384
|
+
} catch (err) {
|
|
385
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
386
|
+
logErr(`[mcp-agents] failed to prepare isolated codex home: ${msg}`);
|
|
387
|
+
process.exitCode = 1;
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const args = ["mcp-server"];
|
|
392
|
+
let cleanedUp = false;
|
|
393
|
+
const cleanupIsolatedCodexHome = () => {
|
|
394
|
+
if (cleanedUp || !isolatedCodexHome) return;
|
|
395
|
+
cleanedUp = true;
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
rmSync(isolatedCodexHome, { recursive: true, force: true });
|
|
399
|
+
} catch (err) {
|
|
400
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
401
|
+
logErr(`[mcp-agents] failed to clean isolated codex home: ${msg}`);
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
logErr(
|
|
406
|
+
`[mcp-agents] passthrough: codex ${args.join(" ")} ` +
|
|
407
|
+
`(model=${resolvedModel}, reasoning_effort=${resolvedModelReasoningEffort}, isolated_home=true)`,
|
|
408
|
+
);
|
|
301
409
|
|
|
302
410
|
const child = spawn("codex", args, {
|
|
411
|
+
env: { ...process.env, CODEX_HOME: isolatedCodexHome },
|
|
303
412
|
stdio: ["inherit", "inherit", "pipe"],
|
|
304
413
|
});
|
|
305
414
|
|
|
@@ -307,23 +416,25 @@ function runCodexPassthrough({ model, modelReasoningEffort }) {
|
|
|
307
416
|
logErr(`[codex] ${chunk.toString().trimEnd()}`);
|
|
308
417
|
});
|
|
309
418
|
|
|
310
|
-
const SIGNAL_CODES = { SIGHUP: 1, SIGINT: 2, SIGTERM: 15 };
|
|
311
419
|
for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
|
312
420
|
process.once(sig, () => {
|
|
313
421
|
child.kill(sig);
|
|
314
422
|
setTimeout(() => {
|
|
315
423
|
child.kill("SIGKILL");
|
|
424
|
+
cleanupIsolatedCodexHome();
|
|
316
425
|
process.exit(128 + SIGNAL_CODES[sig]);
|
|
317
426
|
}, 5000).unref();
|
|
318
427
|
});
|
|
319
428
|
}
|
|
320
429
|
|
|
321
430
|
child.on("error", (err) => {
|
|
431
|
+
cleanupIsolatedCodexHome();
|
|
322
432
|
logErr(`[mcp-agents] failed to start codex: ${err.message}`);
|
|
323
433
|
process.exitCode = 1;
|
|
324
434
|
});
|
|
325
435
|
|
|
326
436
|
child.on("exit", (code, signal) => {
|
|
437
|
+
cleanupIsolatedCodexHome();
|
|
327
438
|
if (signal) {
|
|
328
439
|
logErr(`[mcp-agents] codex killed by ${signal}`);
|
|
329
440
|
process.exitCode = 128 + (SIGNAL_CODES[signal] ?? 0);
|
|
@@ -358,6 +469,53 @@ async function main() {
|
|
|
358
469
|
{ name: "mcp-agents", version: VERSION },
|
|
359
470
|
{ capabilities: { tools: {} } },
|
|
360
471
|
);
|
|
472
|
+
let keepAlive;
|
|
473
|
+
let shutdownStarted = false;
|
|
474
|
+
let shutdownExitCode = 0;
|
|
475
|
+
let shutdownPromise;
|
|
476
|
+
let shutdownTimer;
|
|
477
|
+
let activeRequests = 0;
|
|
478
|
+
const activeChildren = new Map();
|
|
479
|
+
|
|
480
|
+
const maybeFinalizeShutdown = () => {
|
|
481
|
+
if (!shutdownStarted || activeRequests > 0 || shutdownPromise) return;
|
|
482
|
+
|
|
483
|
+
shutdownPromise = Promise.resolve()
|
|
484
|
+
.then(async () => {
|
|
485
|
+
if (keepAlive) clearInterval(keepAlive);
|
|
486
|
+
await server.close();
|
|
487
|
+
})
|
|
488
|
+
.catch((err) => {
|
|
489
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
490
|
+
logErr(`[mcp-agents] shutdown close failed: ${msg}`);
|
|
491
|
+
})
|
|
492
|
+
.finally(() => {
|
|
493
|
+
if (shutdownTimer) clearTimeout(shutdownTimer);
|
|
494
|
+
process.exit(shutdownExitCode);
|
|
495
|
+
});
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
const beginShutdown = (reason, exitCode = 0) => {
|
|
499
|
+
if (shutdownStarted) return;
|
|
500
|
+
|
|
501
|
+
shutdownStarted = true;
|
|
502
|
+
shutdownExitCode = exitCode;
|
|
503
|
+
logErr(
|
|
504
|
+
`[mcp-agents] shutting down (provider=${providerName}, reason=${reason})`,
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
shutdownTimer = setTimeout(() => {
|
|
508
|
+
process.exit(shutdownExitCode);
|
|
509
|
+
}, SHUTDOWN_TIMEOUT_MS);
|
|
510
|
+
shutdownTimer.unref();
|
|
511
|
+
|
|
512
|
+
for (const killGroup of activeChildren.values()) {
|
|
513
|
+
killGroup();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
maybeFinalizeShutdown();
|
|
517
|
+
};
|
|
518
|
+
fatalShutdown = beginShutdown;
|
|
361
519
|
|
|
362
520
|
const effectiveTimeout = defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
363
521
|
|
|
@@ -404,6 +562,13 @@ async function main() {
|
|
|
404
562
|
return { content: [{ type: "text", text: "pong" }] };
|
|
405
563
|
}
|
|
406
564
|
|
|
565
|
+
if (shutdownStarted) {
|
|
566
|
+
return {
|
|
567
|
+
content: [{ type: "text", text: "Server is shutting down" }],
|
|
568
|
+
isError: true,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
407
572
|
if (params.name !== backend.toolName) {
|
|
408
573
|
return {
|
|
409
574
|
content: [
|
|
@@ -416,6 +581,7 @@ async function main() {
|
|
|
416
581
|
};
|
|
417
582
|
}
|
|
418
583
|
|
|
584
|
+
activeRequests += 1;
|
|
419
585
|
const rawArgs =
|
|
420
586
|
params.arguments && typeof params.arguments === "object"
|
|
421
587
|
? params.arguments
|
|
@@ -441,6 +607,8 @@ async function main() {
|
|
|
441
607
|
: effectiveTimeout;
|
|
442
608
|
|
|
443
609
|
if (!prompt.trim()) {
|
|
610
|
+
activeRequests -= 1;
|
|
611
|
+
maybeFinalizeShutdown();
|
|
444
612
|
return {
|
|
445
613
|
content: [
|
|
446
614
|
{
|
|
@@ -461,13 +629,30 @@ async function main() {
|
|
|
461
629
|
? backend.buildArgs(extraOpts)
|
|
462
630
|
: backend.buildArgs(prompt, extraOpts);
|
|
463
631
|
const buildCliOpts = (attemptTimeoutMs) => (
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
632
|
+
{
|
|
633
|
+
timeoutMs: attemptTimeoutMs,
|
|
634
|
+
...(backend.stdinPrompt ? { stdinData: prompt } : {}),
|
|
635
|
+
onSpawn: ({ pid, killGroup }) => {
|
|
636
|
+
if (!pid) return;
|
|
637
|
+
activeChildren.set(pid, killGroup);
|
|
638
|
+
},
|
|
639
|
+
onSettled: (pid) => {
|
|
640
|
+
if (!pid) return;
|
|
641
|
+
activeChildren.delete(pid);
|
|
642
|
+
maybeFinalizeShutdown();
|
|
643
|
+
},
|
|
644
|
+
}
|
|
467
645
|
);
|
|
468
646
|
|
|
469
647
|
logErr(`[mcp-agents] tools/call: running ${backend.command} …`);
|
|
470
648
|
try {
|
|
649
|
+
if (shutdownStarted) {
|
|
650
|
+
return {
|
|
651
|
+
content: [{ type: "text", text: "Server is shutting down" }],
|
|
652
|
+
isError: true,
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
471
656
|
const startedAt = Date.now();
|
|
472
657
|
const maxAttempts = providerName === "claude"
|
|
473
658
|
? CLAUDE_EMPTY_OUTPUT_MAX_ATTEMPTS
|
|
@@ -552,6 +737,9 @@ async function main() {
|
|
|
552
737
|
content: [{ type: "text", text: msg }],
|
|
553
738
|
isError: true,
|
|
554
739
|
};
|
|
740
|
+
} finally {
|
|
741
|
+
activeRequests -= 1;
|
|
742
|
+
maybeFinalizeShutdown();
|
|
555
743
|
}
|
|
556
744
|
});
|
|
557
745
|
|
|
@@ -562,13 +750,28 @@ async function main() {
|
|
|
562
750
|
// request handlers (tools/call -> execFile) register active handles.
|
|
563
751
|
// The SDK transport doesn't listen for stdin 'end', so the event
|
|
564
752
|
// loop loses its only handle when the pipe closes.
|
|
565
|
-
|
|
753
|
+
keepAlive = setInterval(() => {}, 60_000);
|
|
566
754
|
const origOnClose = transport.onclose;
|
|
567
755
|
transport.onclose = () => {
|
|
568
756
|
clearInterval(keepAlive);
|
|
569
757
|
origOnClose?.();
|
|
570
758
|
};
|
|
571
759
|
|
|
760
|
+
process.stdin.once("end", () => {
|
|
761
|
+
beginShutdown("stdin-end");
|
|
762
|
+
});
|
|
763
|
+
process.stdin.once("close", () => {
|
|
764
|
+
beginShutdown("stdin-close");
|
|
765
|
+
});
|
|
766
|
+
process.stdout.on("error", (err) => {
|
|
767
|
+
if (err?.code === "EPIPE") beginShutdown("stdout-epipe");
|
|
768
|
+
});
|
|
769
|
+
for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
|
770
|
+
process.once(sig, () => {
|
|
771
|
+
beginShutdown(sig, 128 + SIGNAL_CODES[sig]);
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
|
|
572
775
|
logErr(`[mcp-agents] ready (provider: ${providerName})`);
|
|
573
776
|
}
|
|
574
777
|
|
|
@@ -576,15 +779,23 @@ process.on("unhandledRejection", (reason) => {
|
|
|
576
779
|
logErr(
|
|
577
780
|
`UnhandledRejection: ${reason instanceof Error ? reason.stack : reason}`,
|
|
578
781
|
);
|
|
579
|
-
|
|
782
|
+
if (fatalShutdown) {
|
|
783
|
+
fatalShutdown("unhandledRejection", 1);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
process.exit(1);
|
|
580
787
|
});
|
|
581
788
|
|
|
582
789
|
process.on("uncaughtException", (err) => {
|
|
583
790
|
logErr(`UncaughtException: ${err.stack || err.message}`);
|
|
584
|
-
|
|
791
|
+
if (fatalShutdown) {
|
|
792
|
+
fatalShutdown("uncaughtException", 1);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
process.exit(1);
|
|
585
796
|
});
|
|
586
797
|
|
|
587
798
|
main().catch((err) => {
|
|
588
799
|
logErr(err.stack || err.message);
|
|
589
|
-
process.
|
|
800
|
+
process.exit(1);
|
|
590
801
|
});
|