mcp-agents 0.8.0 → 0.9.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/server.js +86 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-agents",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "MCP server that wraps AI CLI tools (Claude Code, Gemini CLI, Codex CLI) for use by any MCP client",
5
5
  "type": "module",
6
6
  "bin": {
package/server.js CHANGED
@@ -29,10 +29,13 @@ const VERSION = JSON.parse(
29
29
  const DEFAULT_TIMEOUT_MS = 300_000;
30
30
  const DEFAULT_CODEX_MODEL = "gpt-5.5";
31
31
  const DEFAULT_CODEX_MODEL_REASONING_EFFORT = "xhigh";
32
+ const DEFAULT_CODEX_SANDBOX_MODE = "workspace-write";
33
+ const DEFAULT_CODEX_APPROVAL_POLICY = "never";
32
34
  const DEFAULT_CLAUDE_MODEL = "claude-opus-4-8";
33
35
  const DEFAULT_CLAUDE_EFFORT = "xhigh";
34
36
  // tools/call argument keys stripped from the codex pass-through so callers
35
- // cannot override the pinned model/effort (or the read-only/never config).
37
+ // cannot override the pinned model/effort (or the server's sandbox/approval
38
+ // config) for a single call.
36
39
  const CODEX_STRIPPED_TOOL_ARGS = ["model", "config"];
37
40
  const MAX_BUFFER_BYTES = 10 * 1024 * 1024;
38
41
  const CLAUDE_EMPTY_OUTPUT_MAX_ATTEMPTS = 2;
@@ -141,6 +144,10 @@ Options:
141
144
  --provider <name> CLI backend to use (${providers}) [default: codex]
142
145
  --model <model> Codex model [default: ${DEFAULT_CODEX_MODEL}]
143
146
  --model_reasoning_effort <e> Codex reasoning effort [default: ${DEFAULT_CODEX_MODEL_REASONING_EFFORT}]
147
+ --sandbox_mode <mode> Codex sandbox mode: read-only, workspace-write,
148
+ danger-full-access [default: ${DEFAULT_CODEX_SANDBOX_MODE}]
149
+ --approval_policy <policy> Codex approval policy: untrusted, on-failure,
150
+ on-request, never [default: ${DEFAULT_CODEX_APPROVAL_POLICY}]
144
151
  --timeout <seconds> Default timeout per call [default: 300]
145
152
  --help, -h Show this help message
146
153
  --version, -v Show version number`);
@@ -148,14 +155,17 @@ Options:
148
155
 
149
156
  /**
150
157
  * Parse CLI flags from process.argv.
151
- * Handles --help, --version, --provider, --model, --model_reasoning_effort, and unknown flags.
152
- * @returns {{ provider: string, model?: string, modelReasoningEffort?: string, defaultTimeoutMs?: number }}
158
+ * Handles --help, --version, --provider, --model, --model_reasoning_effort,
159
+ * --sandbox_mode, --approval_policy, and unknown flags.
160
+ * @returns {{ provider: string, model?: string, modelReasoningEffort?: string, sandboxMode?: string, approvalPolicy?: string, defaultTimeoutMs?: number }}
153
161
  */
154
162
  function parseArgs() {
155
163
  const args = process.argv.slice(2);
156
164
  let provider = "codex";
157
165
  let model;
158
166
  let modelReasoningEffort;
167
+ let sandboxMode;
168
+ let approvalPolicy;
159
169
  let defaultTimeoutMs;
160
170
 
161
171
  for (let i = 0; i < args.length; i++) {
@@ -193,6 +203,20 @@ function parseArgs() {
193
203
  }
194
204
  modelReasoningEffort = args[++i];
195
205
  break;
206
+ case "--sandbox_mode":
207
+ if (i + 1 >= args.length) {
208
+ process.stderr.write("error: --sandbox_mode requires a value\n");
209
+ process.exit(1);
210
+ }
211
+ sandboxMode = args[++i];
212
+ break;
213
+ case "--approval_policy":
214
+ if (i + 1 >= args.length) {
215
+ process.stderr.write("error: --approval_policy requires a value\n");
216
+ process.exit(1);
217
+ }
218
+ approvalPolicy = args[++i];
219
+ break;
196
220
  case "--timeout": {
197
221
  if (i + 1 >= args.length) {
198
222
  process.stderr.write("error: --timeout requires a value\n");
@@ -212,7 +236,14 @@ function parseArgs() {
212
236
  }
213
237
  }
214
238
 
215
- return { provider, model, modelReasoningEffort, defaultTimeoutMs };
239
+ return {
240
+ provider,
241
+ model,
242
+ modelReasoningEffort,
243
+ sandboxMode,
244
+ approvalPolicy,
245
+ defaultTimeoutMs,
246
+ };
216
247
  }
217
248
 
218
249
  /**
@@ -356,15 +387,20 @@ function toTomlString(value) {
356
387
 
357
388
  /**
358
389
  * Build the minimal config for the isolated Codex bridge runtime.
359
- * @param {{ model: string, modelReasoningEffort: string }} opts
390
+ * @param {{ model: string, modelReasoningEffort: string, sandboxMode: string, approvalPolicy: string }} opts
360
391
  * @returns {string}
361
392
  */
362
- function buildCodexBridgeConfig({ model, modelReasoningEffort }) {
393
+ function buildCodexBridgeConfig({
394
+ model,
395
+ modelReasoningEffort,
396
+ sandboxMode,
397
+ approvalPolicy,
398
+ }) {
363
399
  return [
364
400
  `model = ${toTomlString(model)}`,
365
401
  `model_reasoning_effort = ${toTomlString(modelReasoningEffort)}`,
366
- 'approval_policy = "never"',
367
- 'sandbox_mode = "read-only"',
402
+ `approval_policy = ${toTomlString(approvalPolicy)}`,
403
+ `sandbox_mode = ${toTomlString(sandboxMode)}`,
368
404
  "",
369
405
  "[features]",
370
406
  "multi_agent = false",
@@ -374,10 +410,15 @@ function buildCodexBridgeConfig({ model, modelReasoningEffort }) {
374
410
 
375
411
  /**
376
412
  * Create an isolated Codex home that preserves auth but strips inherited MCP servers.
377
- * @param {{ model: string, modelReasoningEffort: string }} opts
413
+ * @param {{ model: string, modelReasoningEffort: string, sandboxMode: string, approvalPolicy: string }} opts
378
414
  * @returns {string}
379
415
  */
380
- function createIsolatedCodexHome({ model, modelReasoningEffort }) {
416
+ function createIsolatedCodexHome({
417
+ model,
418
+ modelReasoningEffort,
419
+ sandboxMode,
420
+ approvalPolicy,
421
+ }) {
381
422
  const codexHome = mkdtempSync(join(tmpdir(), "mcp-agents-codex-"));
382
423
  const sourceAuthPath = join(resolveCodexHome(), "auth.json");
383
424
  const targetAuthPath = join(codexHome, "auth.json");
@@ -389,7 +430,12 @@ function createIsolatedCodexHome({ model, modelReasoningEffort }) {
389
430
 
390
431
  writeFileSync(
391
432
  configPath,
392
- buildCodexBridgeConfig({ model, modelReasoningEffort }),
433
+ buildCodexBridgeConfig({
434
+ model,
435
+ modelReasoningEffort,
436
+ sandboxMode,
437
+ approvalPolicy,
438
+ }),
393
439
  "utf8",
394
440
  );
395
441
 
@@ -399,7 +445,7 @@ function createIsolatedCodexHome({ model, modelReasoningEffort }) {
399
445
  /**
400
446
  * Filter a single newline-delimited JSON-RPC message on its way to the codex
401
447
  * pass-through. Strips per-call model/config overrides from `tools/call` so the
402
- * client cannot escape the pinned model/effort (or the read-only/never config).
448
+ * client cannot escape the pinned model/effort (or the sandbox/approval config).
403
449
  * Non-`tools/call` and unparseable lines are returned byte-for-byte unchanged so
404
450
  * the MCP framing is preserved.
405
451
  * @param {string} line
@@ -436,18 +482,27 @@ function filterCodexToolCall(line) {
436
482
  * Spawn codex mcp-server as a pass-through. stdout/stderr flow straight back to
437
483
  * the client, but the client's stdin is intercepted line-by-line so per-call
438
484
  * model/config overrides are stripped before reaching codex.
439
- * @param {{ model?: string, modelReasoningEffort?: string }} opts
485
+ * @param {{ model?: string, modelReasoningEffort?: string, sandboxMode?: string, approvalPolicy?: string }} opts
440
486
  */
441
- function runCodexPassthrough({ model, modelReasoningEffort }) {
487
+ function runCodexPassthrough({
488
+ model,
489
+ modelReasoningEffort,
490
+ sandboxMode,
491
+ approvalPolicy,
492
+ }) {
442
493
  const resolvedModel = model || DEFAULT_CODEX_MODEL;
443
494
  const resolvedModelReasoningEffort =
444
495
  modelReasoningEffort || DEFAULT_CODEX_MODEL_REASONING_EFFORT;
496
+ const resolvedSandboxMode = sandboxMode || DEFAULT_CODEX_SANDBOX_MODE;
497
+ const resolvedApprovalPolicy = approvalPolicy || DEFAULT_CODEX_APPROVAL_POLICY;
445
498
  let isolatedCodexHome;
446
499
 
447
500
  try {
448
501
  isolatedCodexHome = createIsolatedCodexHome({
449
502
  model: resolvedModel,
450
503
  modelReasoningEffort: resolvedModelReasoningEffort,
504
+ sandboxMode: resolvedSandboxMode,
505
+ approvalPolicy: resolvedApprovalPolicy,
451
506
  });
452
507
  } catch (err) {
453
508
  const msg = err instanceof Error ? err.message : String(err);
@@ -472,7 +527,9 @@ function runCodexPassthrough({ model, modelReasoningEffort }) {
472
527
 
473
528
  logErr(
474
529
  `[mcp-agents] passthrough: codex ${args.join(" ")} ` +
475
- `(model=${resolvedModel}, reasoning_effort=${resolvedModelReasoningEffort}, isolated_home=true)`,
530
+ `(model=${resolvedModel}, reasoning_effort=${resolvedModelReasoningEffort}, ` +
531
+ `sandbox_mode=${resolvedSandboxMode}, approval_policy=${resolvedApprovalPolicy}, ` +
532
+ `isolated_home=true)`,
476
533
  );
477
534
 
478
535
  const child = spawn("codex", args, {
@@ -539,7 +596,14 @@ function runCodexPassthrough({ model, modelReasoningEffort }) {
539
596
  // ---------------------------------------------------------------------------
540
597
 
541
598
  async function main() {
542
- const { provider: providerName, model, modelReasoningEffort, defaultTimeoutMs } = parseArgs();
599
+ const {
600
+ provider: providerName,
601
+ model,
602
+ modelReasoningEffort,
603
+ sandboxMode,
604
+ approvalPolicy,
605
+ defaultTimeoutMs,
606
+ } = parseArgs();
543
607
  const backend = CLI_BACKENDS[providerName];
544
608
 
545
609
  if (!backend) {
@@ -550,7 +614,12 @@ async function main() {
550
614
  }
551
615
 
552
616
  if (backend.passthrough) {
553
- runCodexPassthrough({ model, modelReasoningEffort });
617
+ runCodexPassthrough({
618
+ model,
619
+ modelReasoningEffort,
620
+ sandboxMode,
621
+ approvalPolicy,
622
+ });
554
623
  return;
555
624
  }
556
625