mcp-agents 0.6.0 → 0.6.6
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 +26 -6
- package/package.json +1 -1
- package/server.js +110 -11
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
|
-
| `--model` | `gpt-5.
|
|
86
|
-
| `--model_reasoning_effort` | `
|
|
88
|
+
| `--model` | `gpt-5.5` | `model` |
|
|
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,19 +134,24 @@ 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
|
{
|
|
126
141
|
"mcpServers": {
|
|
127
142
|
"codex": {
|
|
128
143
|
"command": "mcp-agents",
|
|
129
|
-
"args": ["--provider", "codex", "--model", "gpt-5.
|
|
144
|
+
"args": ["--provider", "codex", "--model", "gpt-5.5", "--model_reasoning_effort", "medium"]
|
|
130
145
|
}
|
|
131
146
|
}
|
|
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
|
|
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,6 +27,8 @@ const VERSION = JSON.parse(
|
|
|
19
27
|
).version;
|
|
20
28
|
|
|
21
29
|
const DEFAULT_TIMEOUT_MS = 300_000;
|
|
30
|
+
const DEFAULT_CODEX_MODEL = "gpt-5.5";
|
|
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;
|
|
24
34
|
const SIGNAL_CODES = { SIGHUP: 1, SIGINT: 2, SIGTERM: 15 };
|
|
@@ -114,8 +124,8 @@ Usage: mcp-agents [options]
|
|
|
114
124
|
|
|
115
125
|
Options:
|
|
116
126
|
--provider <name> CLI backend to use (${providers}) [default: codex]
|
|
117
|
-
--model <model> Codex model [default:
|
|
118
|
-
--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}]
|
|
119
129
|
--timeout <seconds> Default timeout per call [default: 300]
|
|
120
130
|
--help, -h Show this help message
|
|
121
131
|
--version, -v Show version number`);
|
|
@@ -297,22 +307,108 @@ function runCli(command, args, opts = {}) {
|
|
|
297
307
|
});
|
|
298
308
|
}
|
|
299
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
|
+
|
|
300
369
|
/**
|
|
301
370
|
* Spawn codex mcp-server as a pass-through, piping stdio directly.
|
|
302
371
|
* @param {{ model?: string, modelReasoningEffort?: string }} opts
|
|
303
372
|
*/
|
|
304
373
|
function runCodexPassthrough({ model, modelReasoningEffort }) {
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
374
|
+
const resolvedModel = model || DEFAULT_CODEX_MODEL;
|
|
375
|
+
const resolvedModelReasoningEffort =
|
|
376
|
+
modelReasoningEffort || DEFAULT_CODEX_MODEL_REASONING_EFFORT;
|
|
377
|
+
let isolatedCodexHome;
|
|
378
|
+
|
|
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
|
+
};
|
|
312
404
|
|
|
313
|
-
logErr(
|
|
405
|
+
logErr(
|
|
406
|
+
`[mcp-agents] passthrough: codex ${args.join(" ")} ` +
|
|
407
|
+
`(model=${resolvedModel}, reasoning_effort=${resolvedModelReasoningEffort}, isolated_home=true)`,
|
|
408
|
+
);
|
|
314
409
|
|
|
315
410
|
const child = spawn("codex", args, {
|
|
411
|
+
env: { ...process.env, CODEX_HOME: isolatedCodexHome },
|
|
316
412
|
stdio: ["inherit", "inherit", "pipe"],
|
|
317
413
|
});
|
|
318
414
|
|
|
@@ -325,17 +421,20 @@ function runCodexPassthrough({ model, modelReasoningEffort }) {
|
|
|
325
421
|
child.kill(sig);
|
|
326
422
|
setTimeout(() => {
|
|
327
423
|
child.kill("SIGKILL");
|
|
424
|
+
cleanupIsolatedCodexHome();
|
|
328
425
|
process.exit(128 + SIGNAL_CODES[sig]);
|
|
329
426
|
}, 5000).unref();
|
|
330
427
|
});
|
|
331
428
|
}
|
|
332
429
|
|
|
333
430
|
child.on("error", (err) => {
|
|
431
|
+
cleanupIsolatedCodexHome();
|
|
334
432
|
logErr(`[mcp-agents] failed to start codex: ${err.message}`);
|
|
335
433
|
process.exitCode = 1;
|
|
336
434
|
});
|
|
337
435
|
|
|
338
436
|
child.on("exit", (code, signal) => {
|
|
437
|
+
cleanupIsolatedCodexHome();
|
|
339
438
|
if (signal) {
|
|
340
439
|
logErr(`[mcp-agents] codex killed by ${signal}`);
|
|
341
440
|
process.exitCode = 128 + (SIGNAL_CODES[signal] ?? 0);
|