mcp-agents 0.7.0 → 0.8.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/README.md +9 -8
- package/package.json +1 -1
- package/server.js +77 -4
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ Each `--provider` flag maps to a single exposed tool:
|
|
|
47
47
|
|
|
48
48
|
| Provider | Tool name | CLI command |
|
|
49
49
|
|----------|-----------|-------------|
|
|
50
|
-
| `claude` | `claude_code` | `claude -p --output-format json` |
|
|
50
|
+
| `claude` | `claude_code` | `claude --model claude-opus-4-8 --effort xhigh -p --output-format json` |
|
|
51
51
|
| `gemini` | `gemini` | `agy --sandbox -p <prompt>` |
|
|
52
52
|
| `codex` | *(pass-through)* | `codex mcp-server` |
|
|
53
53
|
|
|
@@ -58,9 +58,9 @@ Each `--provider` flag maps to a single exposed tool:
|
|
|
58
58
|
| `prompt` | `string` | yes | The prompt to send to Claude Code |
|
|
59
59
|
| `timeout_ms` | `integer` | no | Timeout in ms (default: 300 000 / 5 minutes) |
|
|
60
60
|
|
|
61
|
-
Any additional `tools/call` arguments are ignored (for example `model` or `
|
|
61
|
+
Any additional `tools/call` arguments are ignored (for example `model`, `effort`, or `config`).
|
|
62
62
|
|
|
63
|
-
Claude
|
|
63
|
+
Claude is pinned to `claude-opus-4-8` at effort `xhigh`; callers cannot change the model or effort per call. Calls run with `--output-format json`; the server parses the JSON payload and returns the assistant `result` text (or an MCP error if `is_error=true`).
|
|
64
64
|
|
|
65
65
|
### `gemini` parameters
|
|
66
66
|
|
|
@@ -89,17 +89,18 @@ or Gemini during bridge calls.
|
|
|
89
89
|
Hardcoded defaults: `sandbox_mode=read-only`, `approval_policy=never`,
|
|
90
90
|
`features.multi_agent=false`.
|
|
91
91
|
|
|
92
|
-
Startup flags set
|
|
92
|
+
Startup flags (`--model`, `--model_reasoning_effort`) set the model and effort for the native Codex MCP server. Per-call `model` and `config` arguments are stripped from `tools/call` before they reach Codex, so a client cannot override the pinned model/effort (or the read-only/never sandbox config) for a single call. For example, this request:
|
|
93
93
|
|
|
94
94
|
```json
|
|
95
95
|
{
|
|
96
96
|
"prompt": "Review this diff",
|
|
97
|
-
"
|
|
98
|
-
|
|
99
|
-
}
|
|
97
|
+
"model": "gpt-5.5-codex",
|
|
98
|
+
"config": { "model_reasoning_effort": "medium" }
|
|
100
99
|
}
|
|
101
100
|
```
|
|
102
101
|
|
|
102
|
+
is forwarded to Codex as `{ "prompt": "Review this diff" }`. Change the model or effort at server startup instead.
|
|
103
|
+
|
|
103
104
|
## Integration with Claude Code
|
|
104
105
|
|
|
105
106
|
Add entries to your project's `.mcp.json` (requires `npm i -g mcp-agents`):
|
|
@@ -132,7 +133,7 @@ Override codex defaults at server startup:
|
|
|
132
133
|
}
|
|
133
134
|
```
|
|
134
135
|
|
|
135
|
-
The
|
|
136
|
+
The model and effort are fixed at server startup. Per-call `model` and `config` arguments sent to the native `codex` tool are stripped before reaching Codex, so they cannot override the startup defaults.
|
|
136
137
|
|
|
137
138
|
Because the bridge runs in an isolated Codex home, inherited MCP servers from your normal
|
|
138
139
|
`~/.codex/config.toml` are intentionally unavailable inside bridged Codex sessions.
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -29,6 +29,11 @@ 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_CLAUDE_MODEL = "claude-opus-4-8";
|
|
33
|
+
const DEFAULT_CLAUDE_EFFORT = "xhigh";
|
|
34
|
+
// 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).
|
|
36
|
+
const CODEX_STRIPPED_TOOL_ARGS = ["model", "config"];
|
|
32
37
|
const MAX_BUFFER_BYTES = 10 * 1024 * 1024;
|
|
33
38
|
const CLAUDE_EMPTY_OUTPUT_MAX_ATTEMPTS = 2;
|
|
34
39
|
const SIGNAL_CODES = { SIGHUP: 1, SIGINT: 2, SIGTERM: 15 };
|
|
@@ -44,9 +49,18 @@ const CLI_BACKENDS = {
|
|
|
44
49
|
command: "claude",
|
|
45
50
|
toolName: "claude_code",
|
|
46
51
|
description:
|
|
47
|
-
|
|
52
|
+
`Run Claude Code CLI with a prompt (via stdin), pinned to ${DEFAULT_CLAUDE_MODEL} at effort ${DEFAULT_CLAUDE_EFFORT}. Supports prompt + optional timeout_ms only; other arguments (model/effort/config) are ignored.`,
|
|
48
53
|
stdinPrompt: true,
|
|
49
|
-
buildArgs: () => [
|
|
54
|
+
buildArgs: () => [
|
|
55
|
+
"--model",
|
|
56
|
+
DEFAULT_CLAUDE_MODEL,
|
|
57
|
+
"--effort",
|
|
58
|
+
DEFAULT_CLAUDE_EFFORT,
|
|
59
|
+
"--no-session-persistence",
|
|
60
|
+
"-p",
|
|
61
|
+
"--output-format",
|
|
62
|
+
"json",
|
|
63
|
+
],
|
|
50
64
|
extraProperties: {},
|
|
51
65
|
},
|
|
52
66
|
gemini: {
|
|
@@ -383,7 +397,45 @@ function createIsolatedCodexHome({ model, modelReasoningEffort }) {
|
|
|
383
397
|
}
|
|
384
398
|
|
|
385
399
|
/**
|
|
386
|
-
*
|
|
400
|
+
* Filter a single newline-delimited JSON-RPC message on its way to the codex
|
|
401
|
+
* 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).
|
|
403
|
+
* Non-`tools/call` and unparseable lines are returned byte-for-byte unchanged so
|
|
404
|
+
* the MCP framing is preserved.
|
|
405
|
+
* @param {string} line
|
|
406
|
+
* @returns {string}
|
|
407
|
+
*/
|
|
408
|
+
function filterCodexToolCall(line) {
|
|
409
|
+
const trimmed = line.trim();
|
|
410
|
+
if (!trimmed) return line;
|
|
411
|
+
|
|
412
|
+
let msg;
|
|
413
|
+
try {
|
|
414
|
+
msg = JSON.parse(trimmed);
|
|
415
|
+
} catch {
|
|
416
|
+
return line; // not JSON (e.g. partial/keepalive) — pass through untouched
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const args =
|
|
420
|
+
msg && typeof msg === "object" && msg.method === "tools/call"
|
|
421
|
+
? msg.params?.arguments
|
|
422
|
+
: null;
|
|
423
|
+
if (!args || typeof args !== "object") return line;
|
|
424
|
+
|
|
425
|
+
const stripped = CODEX_STRIPPED_TOOL_ARGS.filter((key) => key in args);
|
|
426
|
+
if (stripped.length === 0) return line;
|
|
427
|
+
|
|
428
|
+
for (const key of stripped) delete args[key];
|
|
429
|
+
logErr(
|
|
430
|
+
`[mcp-agents] codex passthrough: ignoring per-call overrides: ${stripped.join(", ")}`,
|
|
431
|
+
);
|
|
432
|
+
return JSON.stringify(msg);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Spawn codex mcp-server as a pass-through. stdout/stderr flow straight back to
|
|
437
|
+
* the client, but the client's stdin is intercepted line-by-line so per-call
|
|
438
|
+
* model/config overrides are stripped before reaching codex.
|
|
387
439
|
* @param {{ model?: string, modelReasoningEffort?: string }} opts
|
|
388
440
|
*/
|
|
389
441
|
function runCodexPassthrough({ model, modelReasoningEffort }) {
|
|
@@ -425,13 +477,34 @@ function runCodexPassthrough({ model, modelReasoningEffort }) {
|
|
|
425
477
|
|
|
426
478
|
const child = spawn("codex", args, {
|
|
427
479
|
env: { ...process.env, CODEX_HOME: isolatedCodexHome },
|
|
428
|
-
|
|
480
|
+
// stdin is piped (not inherited) so we can strip per-call overrides;
|
|
481
|
+
// stdout stays inherited so codex responses reach the client untouched.
|
|
482
|
+
stdio: ["pipe", "inherit", "pipe"],
|
|
429
483
|
});
|
|
430
484
|
|
|
431
485
|
child.stderr.on("data", (chunk) => {
|
|
432
486
|
logErr(`[codex] ${chunk.toString().trimEnd()}`);
|
|
433
487
|
});
|
|
434
488
|
|
|
489
|
+
// Pump client stdin -> codex stdin, splitting on newlines (MCP stdio framing)
|
|
490
|
+
// so each JSON-RPC message can be filtered before forwarding.
|
|
491
|
+
child.stdin.on("error", () => {}); // ignore EPIPE if codex exits early
|
|
492
|
+
let stdinBuf = "";
|
|
493
|
+
process.stdin.on("data", (chunk) => {
|
|
494
|
+
stdinBuf += chunk.toString("utf8");
|
|
495
|
+
let nl;
|
|
496
|
+
while ((nl = stdinBuf.indexOf("\n")) !== -1) {
|
|
497
|
+
const line = stdinBuf.slice(0, nl);
|
|
498
|
+
stdinBuf = stdinBuf.slice(nl + 1);
|
|
499
|
+
child.stdin.write(`${filterCodexToolCall(line)}\n`);
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
process.stdin.on("error", () => {});
|
|
503
|
+
process.stdin.on("end", () => {
|
|
504
|
+
if (stdinBuf.length > 0) child.stdin.write(filterCodexToolCall(stdinBuf));
|
|
505
|
+
child.stdin.end();
|
|
506
|
+
});
|
|
507
|
+
|
|
435
508
|
for (const sig of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
|
436
509
|
process.once(sig, () => {
|
|
437
510
|
child.kill(sig);
|