@leo000001/codex-mcp 2.0.2 → 2.1.1

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 CHANGED
@@ -19,7 +19,7 @@ MCP server that wraps [OpenAI Codex](https://github.com/openai/codex) `app-serve
19
19
  ## Prerequisites
20
20
 
21
21
  - [Node.js](https://nodejs.org) >= 18
22
- - [OpenAI Codex CLI](https://github.com/openai/codex) installed and configured (`codex` in PATH)
22
+ - [OpenAI Codex CLI](https://github.com/openai/codex) installed and configured (`codex` or `codex-internal` in PATH)
23
23
 
24
24
  ## Quick Start
25
25
 
@@ -76,6 +76,46 @@ Or add to `~/.claude/settings.json`:
76
76
  }
77
77
  ```
78
78
 
79
+ ## Codex Executable Configuration
80
+
81
+ By default, codex-mcp auto-detects the Codex CLI by searching PATH for `codex`, then `codex-internal`. You can override this with environment variables:
82
+
83
+ | Variable | Description | Example |
84
+ | --- | --- | --- |
85
+ | `CODEX_MCP_DEFAULT_CODEX_COMMAND` | Bare command name (resolved from PATH) | `codex-internal` |
86
+ | `CODEX_MCP_DEFAULT_CODEX_PATH` | Absolute or relative filesystem path to the executable | `/usr/local/bin/codex-internal` |
87
+
88
+ - The two variables are **mutually exclusive** — setting both causes a startup error.
89
+ - When neither is set, codex-mcp tries `codex` then `codex-internal` on PATH automatically.
90
+
91
+ Examples:
92
+
93
+ ```bash
94
+ # Use codex-internal instead of codex
95
+ CODEX_MCP_DEFAULT_CODEX_COMMAND=codex-internal npx -y @leo000001/codex-mcp
96
+ ```
97
+
98
+ ```bash
99
+ # Use an explicit path
100
+ CODEX_MCP_DEFAULT_CODEX_PATH=/opt/codex/bin/codex npx -y @leo000001/codex-mcp
101
+ ```
102
+
103
+ MCP client config with env override:
104
+
105
+ ```json
106
+ {
107
+ "mcpServers": {
108
+ "codex": {
109
+ "command": "npx",
110
+ "args": ["-y", "@leo000001/codex-mcp"],
111
+ "env": {
112
+ "CODEX_MCP_DEFAULT_CODEX_COMMAND": "codex-internal"
113
+ }
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
79
119
  ## STDIO Guard Modes
80
120
 
81
121
  `codex-mcp` includes a startup preflight guard for stdout contamination risk.
package/dist/index.js CHANGED
@@ -111,21 +111,38 @@ function resolveCodexInvocation(codexArgs, deps = {}) {
111
111
  const readFile = deps.readFile ?? ((p) => readFileSync(p, "utf8"));
112
112
  const pathApi = platform === "win32" ? path.win32 : path.posix;
113
113
  const delimiter = platform === "win32" ? ";" : ":";
114
+ const codexCommand = deps.codexCommand ?? "codex";
115
+ const codexIsPath = deps.codexIsPath ?? false;
116
+ if (codexIsPath) {
117
+ if (platform === "win32" && (codexCommand.toLowerCase().endsWith(".cmd") || codexCommand.toLowerCase().endsWith(".bat"))) {
118
+ const comspec2 = env.ComSpec || env.COMSPEC || "cmd.exe";
119
+ return {
120
+ cmd: comspec2,
121
+ args: ["/d", "/s", "/c", codexCommand, ...codexArgs],
122
+ spawnedViaCmd: true
123
+ };
124
+ }
125
+ return { cmd: codexCommand, args: codexArgs, spawnedViaCmd: false };
126
+ }
114
127
  if (platform !== "win32") {
115
- return { cmd: "codex", args: codexArgs, spawnedViaCmd: false };
128
+ return { cmd: codexCommand, args: codexArgs, spawnedViaCmd: false };
116
129
  }
117
- const shim = findOnPath("codex", env, exists, pathApi, delimiter, [".exe", ".cmd", ".bat"]);
130
+ const shim = findOnPath(codexCommand, env, exists, pathApi, delimiter, [".exe", ".cmd", ".bat"]);
118
131
  if (shim && shim.toLowerCase().endsWith(".exe")) {
119
132
  return { cmd: shim, args: codexArgs, spawnedViaCmd: false };
120
133
  }
121
134
  if (shim && (shim.toLowerCase().endsWith(".cmd") || shim.toLowerCase().endsWith(".bat"))) {
122
- const script = tryResolveNodeScriptFromShim(shim, exists, readFile, pathApi);
135
+ const script = tryResolveNodeScriptFromShim(shim, codexCommand, exists, readFile, pathApi);
123
136
  if (script) {
124
137
  return { cmd: process.execPath, args: [script, ...codexArgs], spawnedViaCmd: false };
125
138
  }
126
139
  }
127
140
  const comspec = env.ComSpec || env.COMSPEC || "cmd.exe";
128
- return { cmd: comspec, args: ["/d", "/s", "/c", "codex", ...codexArgs], spawnedViaCmd: true };
141
+ return {
142
+ cmd: comspec,
143
+ args: ["/d", "/s", "/c", codexCommand, ...codexArgs],
144
+ spawnedViaCmd: true
145
+ };
129
146
  }
130
147
  function findOnPath(base, env, exists, pathApi, delimiter, exts) {
131
148
  const pathEnv = env.PATH || env.Path || env.path || "";
@@ -146,7 +163,7 @@ function stripSurroundingQuotes(value) {
146
163
  }
147
164
  return value;
148
165
  }
149
- function tryResolveNodeScriptFromShim(shimPath, exists, readFile, pathApi) {
166
+ function tryResolveNodeScriptFromShim(shimPath, codexCommand, exists, readFile, pathApi) {
150
167
  let contents;
151
168
  try {
152
169
  contents = readFile(shimPath);
@@ -161,7 +178,13 @@ function tryResolveNodeScriptFromShim(shimPath, exists, readFile, pathApi) {
161
178
  matches.push(m[1]);
162
179
  }
163
180
  if (matches.length === 0) return void 0;
164
- const preferred = matches.find((m) => /codex/i.test(pathApi.basename(m))) ?? matches.find((m) => /@openai\\codex|\\codex\\|\/codex\//i.test(m)) ?? matches[matches.length - 1];
181
+ const escapedCommand = codexCommand.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
182
+ const baseNameRe = new RegExp(escapedCommand, "i");
183
+ const pathRe = new RegExp(
184
+ `@openai\\\\${escapedCommand}|\\\\${escapedCommand}\\\\|\\/${escapedCommand}\\/`,
185
+ "i"
186
+ );
187
+ const preferred = matches.find((m) => baseNameRe.test(pathApi.basename(m))) ?? matches.find((m) => pathRe.test(m)) ?? matches[matches.length - 1];
165
188
  const shimDir = pathApi.dirname(shimPath);
166
189
  const dp0 = shimDir.endsWith(pathApi.sep) ? shimDir : shimDir + pathApi.sep;
167
190
  let resolved = preferred.replace(/%~dp0/gi, dp0).replace(/%dp0%/gi, dp0);
@@ -171,6 +194,139 @@ function tryResolveNodeScriptFromShim(shimPath, exists, readFile, pathApi) {
171
194
  return abs;
172
195
  }
173
196
 
197
+ // src/utils/codex-executable.ts
198
+ import { accessSync, constants, existsSync as existsSync2, statSync } from "fs";
199
+ import path2 from "path";
200
+ var CODEX_MCP_DEFAULT_CODEX_COMMAND = "CODEX_MCP_DEFAULT_CODEX_COMMAND";
201
+ var CODEX_MCP_DEFAULT_CODEX_PATH = "CODEX_MCP_DEFAULT_CODEX_PATH";
202
+ var AUTO_CODEX_COMMANDS = ["codex", "codex-internal"];
203
+ var _resolved;
204
+ var WINDOWS_SUPPORTED_EXTENSIONS = [".com", ".exe", ".bat", ".cmd"];
205
+ function stripSurroundingQuotes2(value) {
206
+ if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
207
+ return value.slice(1, -1);
208
+ }
209
+ return value;
210
+ }
211
+ function normalizeMaybeQuotedToken(raw) {
212
+ return stripSurroundingQuotes2(raw.trim());
213
+ }
214
+ function normalizeWindowsExtension(value) {
215
+ const trimmed = stripSurroundingQuotes2(value.trim());
216
+ if (!trimmed) return void 0;
217
+ return trimmed.startsWith(".") ? trimmed.toLowerCase() : `.${trimmed.toLowerCase()}`;
218
+ }
219
+ function isExecutableFile(candidate) {
220
+ try {
221
+ const stat = statSync(candidate);
222
+ if (!stat.isFile()) return false;
223
+ if (process.platform === "win32") return true;
224
+ accessSync(candidate, constants.X_OK);
225
+ return true;
226
+ } catch {
227
+ return false;
228
+ }
229
+ }
230
+ function getPathEntries(env) {
231
+ const pathEnv = env.PATH || env.Path || env.path || "";
232
+ return pathEnv.split(process.platform === "win32" ? ";" : ":").map((entry) => stripSurroundingQuotes2(entry.trim())).filter(Boolean);
233
+ }
234
+ function getPathExtensions(env) {
235
+ if (process.platform !== "win32") return [""];
236
+ const configured = env.PATHEXT ?? env.Pathext ?? env.pathext ?? process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD";
237
+ const configuredExts = configured.split(";").map((entry) => normalizeWindowsExtension(entry)).filter((entry) => Boolean(entry));
238
+ const merged = configuredExts.filter(
239
+ (ext) => WINDOWS_SUPPORTED_EXTENSIONS.includes(ext)
240
+ );
241
+ for (const ext of WINDOWS_SUPPORTED_EXTENSIONS) {
242
+ if (!merged.includes(ext)) merged.push(ext);
243
+ }
244
+ return Array.from(new Set(merged));
245
+ }
246
+ function commandExistsOnPath(command, env) {
247
+ const dirs = getPathEntries(env);
248
+ const ext = process.platform === "win32" ? path2.extname(command) : "";
249
+ const names = process.platform === "win32" ? Array.from(
250
+ new Set(
251
+ ext ? [command] : [...getPathExtensions(env).map((suffix) => `${command}${suffix}`), command]
252
+ )
253
+ ) : [command];
254
+ for (const dir of dirs) {
255
+ for (const name of names) {
256
+ const candidate = path2.join(dir, name);
257
+ if (isExecutableFile(candidate)) return true;
258
+ }
259
+ }
260
+ return false;
261
+ }
262
+ function looksLikePath(value) {
263
+ return value.includes("/") || value.includes("\\");
264
+ }
265
+ function resolveDefaultCodexExecutable(env = process.env) {
266
+ const envPathRaw = env[CODEX_MCP_DEFAULT_CODEX_PATH]?.trim();
267
+ const envCommandRaw = env[CODEX_MCP_DEFAULT_CODEX_COMMAND]?.trim();
268
+ const envPath = envPathRaw ? normalizeMaybeQuotedToken(envPathRaw) : void 0;
269
+ const envCommand = envCommandRaw ? normalizeMaybeQuotedToken(envCommandRaw) : void 0;
270
+ if (envPath && envCommand) {
271
+ throw new Error(
272
+ `Cannot set both ${CODEX_MCP_DEFAULT_CODEX_PATH} and ${CODEX_MCP_DEFAULT_CODEX_COMMAND}. Use one or the other.`
273
+ );
274
+ }
275
+ if (envPath) {
276
+ const resolvedPath = path2.resolve(envPath);
277
+ if (!existsSync2(resolvedPath)) {
278
+ throw new Error(`${CODEX_MCP_DEFAULT_CODEX_PATH}="${envPath}" \u2014 file does not exist.`);
279
+ }
280
+ if (!isExecutableFile(resolvedPath)) {
281
+ throw new Error(`${CODEX_MCP_DEFAULT_CODEX_PATH}="${envPath}" \u2014 not an executable file.`);
282
+ }
283
+ return { command: resolvedPath, isPath: true, source: "env_path" };
284
+ }
285
+ if (envCommand) {
286
+ if (looksLikePath(envCommand)) {
287
+ throw new Error(
288
+ `${CODEX_MCP_DEFAULT_CODEX_COMMAND}="${envCommand}" looks like a path. Use ${CODEX_MCP_DEFAULT_CODEX_PATH} for filesystem paths.`
289
+ );
290
+ }
291
+ if (!commandExistsOnPath(envCommand, env)) {
292
+ throw new Error(`${CODEX_MCP_DEFAULT_CODEX_COMMAND}="${envCommand}" was not found in PATH.`);
293
+ }
294
+ return { command: envCommand, isPath: false, source: "env_command" };
295
+ }
296
+ for (const candidate of AUTO_CODEX_COMMANDS) {
297
+ if (commandExistsOnPath(candidate, env)) {
298
+ return { command: candidate, isPath: false, source: "auto_detect" };
299
+ }
300
+ }
301
+ return { command: "codex", isPath: false, source: "default" };
302
+ }
303
+ function getDefaultCodexExecutable() {
304
+ if (!_resolved) {
305
+ _resolved = resolveDefaultCodexExecutable();
306
+ }
307
+ return _resolved;
308
+ }
309
+ function checkDefaultCodexExecutableAvailability() {
310
+ const info = getDefaultCodexExecutable();
311
+ const label = info.isPath ? "path" : "command";
312
+ switch (info.source) {
313
+ case "env_path":
314
+ console.error(`[codex-executable] Using ${CODEX_MCP_DEFAULT_CODEX_PATH}: ${info.command}`);
315
+ break;
316
+ case "env_command":
317
+ console.error(`[codex-executable] Using ${CODEX_MCP_DEFAULT_CODEX_COMMAND}: ${info.command}`);
318
+ break;
319
+ case "auto_detect":
320
+ console.error(`[codex-executable] Auto-detected ${label}: ${info.command}`);
321
+ break;
322
+ case "default":
323
+ console.error(
324
+ `[codex-executable] No codex found on PATH; falling back to "${info.command}". Set ${CODEX_MCP_DEFAULT_CODEX_COMMAND} or ${CODEX_MCP_DEFAULT_CODEX_PATH} to configure.`
325
+ );
326
+ break;
327
+ }
328
+ }
329
+
174
330
  // src/types.ts
175
331
  var APPROVAL_POLICIES = ["untrusted", "on-failure", "on-request", "never"];
176
332
  var SANDBOX_MODES = ["read-only", "workspace-write", "danger-full-access"];
@@ -191,6 +347,7 @@ var COMMAND_DECISIONS = [
191
347
  "accept",
192
348
  "acceptForSession",
193
349
  "acceptWithExecpolicyAmendment",
350
+ "applyNetworkPolicyAmendment",
194
351
  "decline",
195
352
  "cancel"
196
353
  ];
@@ -199,6 +356,7 @@ var ALL_DECISIONS = [
199
356
  "accept",
200
357
  "acceptForSession",
201
358
  "acceptWithExecpolicyAmendment",
359
+ "applyNetworkPolicyAmendment",
202
360
  "decline",
203
361
  "cancel"
204
362
  ];
@@ -233,7 +391,7 @@ var DEFAULT_TERMINAL_CLEANUP_MS = 5 * 60 * 1e3;
233
391
  var CLEANUP_INTERVAL_MS = 6e4;
234
392
 
235
393
  // src/app-server/client.ts
236
- var CLIENT_VERSION = true ? "2.0.2" : "0.0.0-dev";
394
+ var CLIENT_VERSION = true ? "2.1.1" : "0.0.0-dev";
237
395
  var DEFAULT_REQUEST_TIMEOUT = 3e4;
238
396
  var STARTUP_REQUEST_TIMEOUT = 9e4;
239
397
  var MAX_WRITE_QUEUE_BYTES = 5 * 1024 * 1024;
@@ -262,7 +420,11 @@ var AppServerClient = class extends EventEmitter {
262
420
  const args = buildAppServerArgs(opts);
263
421
  const env = { ...process.env };
264
422
  const stdio = ["pipe", "pipe", "pipe"];
265
- const invocation = resolveCodexInvocation(args);
423
+ const exe = getDefaultCodexExecutable();
424
+ const invocation = resolveCodexInvocation(args, {
425
+ codexCommand: exe.command,
426
+ codexIsPath: exe.isPath
427
+ });
266
428
  this.spawnedViaCmd = invocation.spawnedViaCmd;
267
429
  this.spawnedDetached = process.platform !== "win32";
268
430
  const proc = spawn(invocation.cmd, invocation.args, {
@@ -622,16 +784,16 @@ var AppServerClient = class extends EventEmitter {
622
784
  };
623
785
 
624
786
  // src/utils/cwd.ts
625
- import { existsSync as existsSync2, statSync } from "fs";
626
- import path2 from "path";
787
+ import { existsSync as existsSync3, statSync as statSync2 } from "fs";
788
+ import path3 from "path";
627
789
  function resolveAndValidateCwd(inputCwd, baseCwd) {
628
790
  const candidate = inputCwd ?? baseCwd;
629
- const resolved = path2.isAbsolute(candidate) ? candidate : path2.resolve(baseCwd, candidate);
630
- if (!existsSync2(resolved)) {
791
+ const resolved = path3.isAbsolute(candidate) ? candidate : path3.resolve(baseCwd, candidate);
792
+ if (!existsSync3(resolved)) {
631
793
  throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd does not exist: ${resolved}`);
632
794
  }
633
795
  try {
634
- const stat = statSync(resolved);
796
+ const stat = statSync2(resolved);
635
797
  if (!stat.isDirectory()) {
636
798
  throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: cwd is not a directory: ${resolved}`);
637
799
  }
@@ -653,15 +815,15 @@ function redactPaths(message) {
653
815
  }
654
816
 
655
817
  // src/utils/files.ts
656
- import { existsSync as existsSync3, statSync as statSync2 } from "fs";
657
- import path3 from "path";
818
+ import { existsSync as existsSync4, statSync as statSync3 } from "fs";
819
+ import path4 from "path";
658
820
  function resolveAndValidateFilePath(inputPath, baseDir, label = "path") {
659
- const resolved = path3.isAbsolute(inputPath) ? inputPath : path3.resolve(baseDir, inputPath);
660
- if (!existsSync3(resolved)) {
821
+ const resolved = path4.isAbsolute(inputPath) ? inputPath : path4.resolve(baseDir, inputPath);
822
+ if (!existsSync4(resolved)) {
661
823
  throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${label} does not exist: ${resolved}`);
662
824
  }
663
825
  try {
664
- const stat = statSync2(resolved);
826
+ const stat = statSync3(resolved);
665
827
  if (!stat.isFile()) {
666
828
  throw new Error(`Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: ${label} is not a file: ${resolved}`);
667
829
  }
@@ -1119,6 +1281,10 @@ var SessionManager = class {
1119
1281
  approvalId: req.approvalId,
1120
1282
  commandActions: req.commandActions,
1121
1283
  proposedExecpolicyAmendment: req.proposedExecpolicyAmendment,
1284
+ availableDecisions: req.availableDecisions,
1285
+ proposedNetworkPolicyAmendments: req.proposedNetworkPolicyAmendments,
1286
+ additionalPermissions: req.additionalPermissions,
1287
+ networkApprovalContext: req.networkApprovalContext,
1122
1288
  createdAt: req.createdAt
1123
1289
  });
1124
1290
  }
@@ -1220,6 +1386,17 @@ var SessionManager = class {
1220
1386
  );
1221
1387
  }
1222
1388
  if (req.kind === "command") {
1389
+ const available = parseAvailableDecisionSet(req.availableDecisions);
1390
+ if (available && !available.has(decision)) {
1391
+ throw new Error(
1392
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Decision '${decision}' is not available for this approval prompt`
1393
+ );
1394
+ }
1395
+ if (!available && decision === "applyNetworkPolicyAmendment") {
1396
+ throw new Error(
1397
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Decision '${decision}' is not supported by this Codex CLI version (missing availableDecisions)`
1398
+ );
1399
+ }
1223
1400
  if (!COMMAND_DECISIONS.includes(decision)) {
1224
1401
  throw new Error(
1225
1402
  `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Invalid command decision '${decision}'`
@@ -1230,6 +1407,33 @@ var SessionManager = class {
1230
1407
  `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: execpolicy_amendment required for acceptWithExecpolicyAmendment`
1231
1408
  );
1232
1409
  }
1410
+ if (decision !== "acceptWithExecpolicyAmendment" && extra?.execpolicy_amendment !== void 0) {
1411
+ throw new Error(
1412
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: execpolicy_amendment is only valid for acceptWithExecpolicyAmendment`
1413
+ );
1414
+ }
1415
+ if (decision === "applyNetworkPolicyAmendment") {
1416
+ const amendment = extra?.network_policy_amendment;
1417
+ if (!amendment) {
1418
+ throw new Error(
1419
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment required for applyNetworkPolicyAmendment`
1420
+ );
1421
+ }
1422
+ if (amendment.action !== "allow" && amendment.action !== "deny") {
1423
+ throw new Error(
1424
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment.action must be 'allow' or 'deny'`
1425
+ );
1426
+ }
1427
+ if (!amendment.host) {
1428
+ throw new Error(
1429
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment.host required for applyNetworkPolicyAmendment`
1430
+ );
1431
+ }
1432
+ } else if (extra?.network_policy_amendment !== void 0) {
1433
+ throw new Error(
1434
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment is only valid for applyNetworkPolicyAmendment`
1435
+ );
1436
+ }
1233
1437
  } else if (req.kind === "fileChange") {
1234
1438
  if (!FILE_CHANGE_DECISIONS.includes(decision)) {
1235
1439
  throw new Error(
@@ -1243,7 +1447,10 @@ var SessionManager = class {
1243
1447
  }
1244
1448
  let response;
1245
1449
  if (req.kind === "command") {
1246
- response = buildCommandApprovalResponse(decision, extra?.execpolicy_amendment);
1450
+ response = buildCommandApprovalResponse(decision, {
1451
+ execpolicy_amendment: extra?.execpolicy_amendment,
1452
+ network_policy_amendment: extra?.network_policy_amendment
1453
+ });
1247
1454
  } else if (req.kind === "fileChange") {
1248
1455
  response = { decision };
1249
1456
  }
@@ -1501,6 +1708,12 @@ var SessionManager = class {
1501
1708
  const proposedExecpolicyAmendment = normalizeStringArrayOrNull(
1502
1709
  approvalParams.proposedExecpolicyAmendment
1503
1710
  );
1711
+ const availableDecisions = Array.isArray(approvalParams.availableDecisions) ? approvalParams.availableDecisions : null;
1712
+ const proposedNetworkPolicyAmendments = Array.isArray(
1713
+ approvalParams.proposedNetworkPolicyAmendments
1714
+ ) ? approvalParams.proposedNetworkPolicyAmendments : null;
1715
+ const additionalPermissions = "additionalPermissions" in approvalParams ? approvalParams.additionalPermissions : void 0;
1716
+ const networkApprovalContext = "networkApprovalContext" in approvalParams ? approvalParams.networkApprovalContext : void 0;
1504
1717
  const pending = {
1505
1718
  requestId,
1506
1719
  kind: "command",
@@ -1512,6 +1725,10 @@ var SessionManager = class {
1512
1725
  approvalId,
1513
1726
  commandActions,
1514
1727
  proposedExecpolicyAmendment,
1728
+ availableDecisions,
1729
+ proposedNetworkPolicyAmendments,
1730
+ additionalPermissions,
1731
+ networkApprovalContext,
1515
1732
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1516
1733
  resolved: false,
1517
1734
  respond: (result) => client.respondToServer(id, result)
@@ -1559,7 +1776,11 @@ var SessionManager = class {
1559
1776
  cwd: approvalParams.cwd,
1560
1777
  reason,
1561
1778
  commandActions,
1562
- proposedExecpolicyAmendment
1779
+ proposedExecpolicyAmendment,
1780
+ availableDecisions,
1781
+ proposedNetworkPolicyAmendments,
1782
+ additionalPermissions,
1783
+ networkApprovalContext
1563
1784
  },
1564
1785
  true
1565
1786
  );
@@ -1878,7 +2099,11 @@ function compactActionsForBudget(actions) {
1878
2099
  itemId: action.itemId,
1879
2100
  createdAt: action.createdAt,
1880
2101
  commandActions: action.commandActions,
1881
- proposedExecpolicyAmendment: action.proposedExecpolicyAmendment
2102
+ proposedExecpolicyAmendment: action.proposedExecpolicyAmendment,
2103
+ availableDecisions: action.availableDecisions,
2104
+ additionalPermissions: action.additionalPermissions,
2105
+ networkApprovalContext: action.networkApprovalContext,
2106
+ proposedNetworkPolicyAmendments: action.proposedNetworkPolicyAmendments
1882
2107
  }));
1883
2108
  }
1884
2109
  function compactActionParamsForBudget(action) {
@@ -1911,7 +2136,11 @@ function compactActionsToMinimum(actions) {
1911
2136
  itemId: first.itemId,
1912
2137
  createdAt: first.createdAt,
1913
2138
  commandActions: first.commandActions,
1914
- proposedExecpolicyAmendment: first.proposedExecpolicyAmendment
2139
+ proposedExecpolicyAmendment: first.proposedExecpolicyAmendment,
2140
+ availableDecisions: first.availableDecisions,
2141
+ additionalPermissions: first.additionalPermissions,
2142
+ networkApprovalContext: first.networkApprovalContext,
2143
+ proposedNetworkPolicyAmendments: first.proposedNetworkPolicyAmendments
1915
2144
  }
1916
2145
  ];
1917
2146
  }
@@ -2135,8 +2364,9 @@ function toSensitiveInfo(session) {
2135
2364
  config: session.config
2136
2365
  };
2137
2366
  }
2138
- function buildCommandApprovalResponse(decision, execpolicy_amendment) {
2367
+ function buildCommandApprovalResponse(decision, extra) {
2139
2368
  if (decision === "acceptWithExecpolicyAmendment") {
2369
+ const execpolicy_amendment = extra?.execpolicy_amendment;
2140
2370
  if (!execpolicy_amendment || execpolicy_amendment.length === 0) {
2141
2371
  throw new Error(
2142
2372
  `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: execpolicy_amendment required for acceptWithExecpolicyAmendment`
@@ -2150,11 +2380,41 @@ function buildCommandApprovalResponse(decision, execpolicy_amendment) {
2150
2380
  }
2151
2381
  };
2152
2382
  }
2383
+ if (decision === "applyNetworkPolicyAmendment") {
2384
+ const amendment = extra?.network_policy_amendment;
2385
+ if (!amendment) {
2386
+ throw new Error(
2387
+ `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment required for applyNetworkPolicyAmendment`
2388
+ );
2389
+ }
2390
+ return {
2391
+ decision: {
2392
+ applyNetworkPolicyAmendment: {
2393
+ network_policy_amendment: amendment
2394
+ }
2395
+ }
2396
+ };
2397
+ }
2153
2398
  return { decision };
2154
2399
  }
2155
2400
  function isRecord(value) {
2156
2401
  return typeof value === "object" && value !== null;
2157
2402
  }
2403
+ function parseAvailableDecisionSet(available) {
2404
+ if (!Array.isArray(available) || available.length === 0) return null;
2405
+ const set = /* @__PURE__ */ new Set();
2406
+ for (const entry of available) {
2407
+ if (typeof entry === "string") {
2408
+ set.add(entry);
2409
+ continue;
2410
+ }
2411
+ if (isRecord(entry)) {
2412
+ if ("acceptWithExecpolicyAmendment" in entry) set.add("acceptWithExecpolicyAmendment");
2413
+ if ("applyNetworkPolicyAmendment" in entry) set.add("applyNetworkPolicyAmendment");
2414
+ }
2415
+ }
2416
+ return set.size > 0 ? set : null;
2417
+ }
2158
2418
  function extractThreadId(result) {
2159
2419
  if (!isRecord(result)) {
2160
2420
  throw new Error(`Error [${"INTERNAL" /* INTERNAL */}]: Invalid thread response: expected object`);
@@ -2272,9 +2532,9 @@ function executeCodexCheck(args, sessionManager) {
2272
2532
  const pollOptions = args.pollOptions;
2273
2533
  switch (args.action) {
2274
2534
  case "poll": {
2275
- if (args.requestId !== void 0 || args.decision !== void 0 || args.execpolicy_amendment !== void 0 || args.denyMessage !== void 0 || args.answers !== void 0) {
2535
+ if (args.requestId !== void 0 || args.decision !== void 0 || args.execpolicy_amendment !== void 0 || args.network_policy_amendment !== void 0 || args.denyMessage !== void 0 || args.answers !== void 0) {
2276
2536
  return {
2277
- error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: requestId/decision/execpolicy_amendment/denyMessage/answers are only valid for respond_* actions`,
2537
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: requestId/decision/execpolicy_amendment/network_policy_amendment/denyMessage/answers are only valid for respond_* actions`,
2278
2538
  isError: true
2279
2539
  };
2280
2540
  }
@@ -2310,6 +2570,31 @@ function executeCodexCheck(args, sessionManager) {
2310
2570
  isError: true
2311
2571
  };
2312
2572
  }
2573
+ if (args.decision === "applyNetworkPolicyAmendment") {
2574
+ if (!args.network_policy_amendment) {
2575
+ return {
2576
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment required for applyNetworkPolicyAmendment`,
2577
+ isError: true
2578
+ };
2579
+ }
2580
+ if (args.network_policy_amendment.action !== "allow" && args.network_policy_amendment.action !== "deny") {
2581
+ return {
2582
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment.action must be 'allow' or 'deny'`,
2583
+ isError: true
2584
+ };
2585
+ }
2586
+ if (!args.network_policy_amendment.host) {
2587
+ return {
2588
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment.host required for applyNetworkPolicyAmendment`,
2589
+ isError: true
2590
+ };
2591
+ }
2592
+ } else if (args.network_policy_amendment !== void 0) {
2593
+ return {
2594
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: network_policy_amendment is only valid with decision='applyNetworkPolicyAmendment'`,
2595
+ isError: true
2596
+ };
2597
+ }
2313
2598
  if (!ALL_DECISIONS.includes(args.decision)) {
2314
2599
  return {
2315
2600
  error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: Unknown decision '${args.decision}'`,
@@ -2319,6 +2604,7 @@ function executeCodexCheck(args, sessionManager) {
2319
2604
  try {
2320
2605
  sessionManager.resolveApproval(args.sessionId, args.requestId, args.decision, {
2321
2606
  execpolicy_amendment: args.execpolicy_amendment,
2607
+ network_policy_amendment: args.network_policy_amendment,
2322
2608
  denyMessage: args.denyMessage
2323
2609
  });
2324
2610
  } catch (err) {
@@ -2338,9 +2624,9 @@ function executeCodexCheck(args, sessionManager) {
2338
2624
  isError: true
2339
2625
  };
2340
2626
  }
2341
- if (args.decision !== void 0 || args.execpolicy_amendment !== void 0 || args.denyMessage !== void 0) {
2627
+ if (args.decision !== void 0 || args.execpolicy_amendment !== void 0 || args.network_policy_amendment !== void 0 || args.denyMessage !== void 0) {
2342
2628
  return {
2343
- error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision/execpolicy_amendment/denyMessage are only valid for respond_permission`,
2629
+ error: `Error [${"INVALID_ARGUMENT" /* INVALID_ARGUMENT */}]: decision/execpolicy_amendment/network_policy_amendment/denyMessage are only valid for respond_permission`,
2344
2630
  isError: true
2345
2631
  };
2346
2632
  }
@@ -2935,7 +3221,7 @@ function registerResources(server, deps) {
2935
3221
  }
2936
3222
 
2937
3223
  // src/server.ts
2938
- var SERVER_VERSION = true ? "2.0.2" : "0.0.0-dev";
3224
+ var SERVER_VERSION = true ? "2.1.1" : "0.0.0-dev";
2939
3225
  function formatErrorMessage(err) {
2940
3226
  const message = err instanceof Error ? err.message : String(err);
2941
3227
  const m = /^Error \[([A-Z_]+)\]:\s*(.*)$/.exec(message);
@@ -3004,9 +3290,13 @@ function createServer(serverCwd) {
3004
3290
  // respond_permission
3005
3291
  requestId: z.string().optional().describe("Request ID from actions[]"),
3006
3292
  decision: z.enum(ALL_DECISIONS).optional().describe(
3007
- "Approval decision for respond_permission. acceptWithExecpolicyAmendment requires execpolicy_amendment."
3293
+ "Approval decision for respond_permission. acceptWithExecpolicyAmendment requires execpolicy_amendment; applyNetworkPolicyAmendment requires network_policy_amendment."
3008
3294
  ),
3009
3295
  execpolicy_amendment: z.array(z.string()).optional().describe("For acceptWithExecpolicyAmendment only"),
3296
+ network_policy_amendment: z.object({
3297
+ action: z.enum(["allow", "deny"]),
3298
+ host: z.string().min(1)
3299
+ }).optional().describe("For applyNetworkPolicyAmendment only"),
3010
3300
  denyMessage: z.string().optional().describe("Deny reason (not sent to agent)"),
3011
3301
  // respond_user_input
3012
3302
  answers: z.record(
@@ -3016,10 +3306,10 @@ function createServer(serverCwd) {
3016
3306
  })
3017
3307
  ).optional().describe("question-id -> answers map (id from actions[] user_input request).")
3018
3308
  }).superRefine((value, ctx) => {
3019
- const addIssue = (path4, message) => {
3309
+ const addIssue = (path5, message) => {
3020
3310
  ctx.addIssue({
3021
3311
  code: z.ZodIssueCode.custom,
3022
- path: [path4],
3312
+ path: [path5],
3023
3313
  message
3024
3314
  });
3025
3315
  };
@@ -3043,6 +3333,12 @@ function createServer(serverCwd) {
3043
3333
  "execpolicy_amendment is only allowed for action='respond_permission'."
3044
3334
  );
3045
3335
  }
3336
+ if (value.network_policy_amendment !== void 0) {
3337
+ addIssue(
3338
+ "network_policy_amendment",
3339
+ "network_policy_amendment is only allowed for action='respond_permission'."
3340
+ );
3341
+ }
3046
3342
  if (value.denyMessage !== void 0) {
3047
3343
  addIssue("denyMessage", "denyMessage is only allowed for action='respond_permission'.");
3048
3344
  }
@@ -3062,6 +3358,7 @@ function createServer(serverCwd) {
3062
3358
  addIssue("answers", "answers is only allowed for action='respond_user_input'.");
3063
3359
  }
3064
3360
  const needsExecpolicy = value.decision === "acceptWithExecpolicyAmendment";
3361
+ const needsNetworkPolicy = value.decision === "applyNetworkPolicyAmendment";
3065
3362
  if (needsExecpolicy && (!value.execpolicy_amendment || value.execpolicy_amendment.length === 0)) {
3066
3363
  addIssue(
3067
3364
  "execpolicy_amendment",
@@ -3074,6 +3371,18 @@ function createServer(serverCwd) {
3074
3371
  "execpolicy_amendment is only allowed when decision='acceptWithExecpolicyAmendment'."
3075
3372
  );
3076
3373
  }
3374
+ if (needsNetworkPolicy && !value.network_policy_amendment) {
3375
+ addIssue(
3376
+ "network_policy_amendment",
3377
+ "network_policy_amendment is required when decision='applyNetworkPolicyAmendment'."
3378
+ );
3379
+ }
3380
+ if (!needsNetworkPolicy && value.network_policy_amendment !== void 0) {
3381
+ addIssue(
3382
+ "network_policy_amendment",
3383
+ "network_policy_amendment is only allowed when decision='applyNetworkPolicyAmendment'."
3384
+ );
3385
+ }
3077
3386
  break;
3078
3387
  }
3079
3388
  case "respond_user_input": {
@@ -3092,6 +3401,12 @@ function createServer(serverCwd) {
3092
3401
  "execpolicy_amendment is only allowed for action='respond_permission'."
3093
3402
  );
3094
3403
  }
3404
+ if (value.network_policy_amendment !== void 0) {
3405
+ addIssue(
3406
+ "network_policy_amendment",
3407
+ "network_policy_amendment is only allowed for action='respond_permission'."
3408
+ );
3409
+ }
3095
3410
  if (value.denyMessage !== void 0) {
3096
3411
  addIssue("denyMessage", "denyMessage is only allowed for action='respond_permission'.");
3097
3412
  }
@@ -3390,6 +3705,7 @@ async function main() {
3390
3705
  "STDIO preflight failed in strict mode due to blocking stdout contamination risk"
3391
3706
  );
3392
3707
  }
3708
+ checkDefaultCodexExecutableAvailability();
3393
3709
  const serverCwd = process.cwd();
3394
3710
  const server = createServer(serverCwd);
3395
3711
  const transport = new StdioServerTransport();