ai-sdk-provider-codex-cli 0.5.2 → 0.6.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 CHANGED
@@ -223,6 +223,7 @@ When OpenAI adds streaming support, this provider will be updated to handle thos
223
223
 
224
224
  - `allowNpx`: If true, falls back to `npx -y @openai/codex` when Codex is not on PATH
225
225
  - `cwd`: Working directory for Codex
226
+ - `addDirs`: Extra directories Codex may read/write (repeats `--add-dir`)
226
227
  - Autonomy/sandbox:
227
228
  - `fullAuto` (equivalent to `--full-auto`)
228
229
  - `dangerouslyBypassApprovalsAndSandbox` (bypass approvals and sandbox; dangerous)
@@ -246,6 +247,7 @@ import { codexCli } from 'ai-sdk-provider-codex-cli';
246
247
  const model = codexCli('gpt-5.1-codex', {
247
248
  allowNpx: true,
248
249
  skipGitRepoCheck: true,
250
+ addDirs: ['../shared'],
249
251
 
250
252
  // Reasoning & verbosity
251
253
  reasoningEffort: 'medium', // minimal | low | medium | high | xhigh (xhigh only on gpt-5.1-codex-max)
@@ -259,6 +261,23 @@ const model = codexCli('gpt-5.1-codex', {
259
261
  oss: false, // adds --oss when true
260
262
  webSearch: true, // maps to -c tools.web_search=true
261
263
 
264
+ // MCP servers (stdio + HTTP/RMCP)
265
+ rmcpClient: true, // enables HTTP-based MCP clients (features.rmcp_client=true)
266
+ mcpServers: {
267
+ local: {
268
+ transport: 'stdio',
269
+ command: 'node',
270
+ args: ['tools/mcp.js'],
271
+ env: { API_KEY: process.env.MCP_API_KEY ?? '' },
272
+ },
273
+ docs: {
274
+ transport: 'http',
275
+ url: 'https://mcp.my-org.com',
276
+ bearerTokenEnvVar: 'MCP_BEARER',
277
+ httpHeaders: { 'x-tenant': 'acme' },
278
+ },
279
+ },
280
+
262
281
  // Generic overrides (maps to -c key=value)
263
282
  configOverrides: {
264
283
  experimental_resume: '/tmp/session.jsonl',
@@ -269,6 +288,7 @@ const model = codexCli('gpt-5.1-codex', {
269
288
 
270
289
  Nested override objects are flattened to dotted keys (e.g., the example above emits
271
290
  `-c sandbox_workspace_write.network_access=true`). Arrays are serialized to JSON strings.
291
+ MCP server env/header objects flatten the same way (e.g., `mcp_servers.docs.http_headers.x-tenant=acme`).
272
292
 
273
293
  ### Per-call overrides via `providerOptions` (v0.4.0+)
274
294
 
@@ -293,6 +313,14 @@ const response = await generateText({
293
313
  reasoningEffort: 'high',
294
314
  reasoningSummary: 'detailed',
295
315
  textVerbosity: 'high', // AI SDK naming; maps to model_verbosity
316
+ rmcpClient: true,
317
+ mcpServers: {
318
+ scratch: {
319
+ transport: 'stdio',
320
+ command: 'pnpm',
321
+ args: ['mcp', 'serve'],
322
+ },
323
+ },
296
324
  configOverrides: {
297
325
  experimental_resume: '/tmp/resume.jsonl',
298
326
  },
package/dist/index.cjs CHANGED
@@ -68,9 +68,37 @@ var loggerFunctionSchema = zod.z.object({
68
68
  message: "error must be a function"
69
69
  })
70
70
  });
71
+ var mcpServerBaseSchema = zod.z.object({
72
+ enabled: zod.z.boolean().optional(),
73
+ startupTimeoutSec: zod.z.number().int().positive().optional(),
74
+ toolTimeoutSec: zod.z.number().int().positive().optional(),
75
+ enabledTools: zod.z.array(zod.z.string()).optional(),
76
+ disabledTools: zod.z.array(zod.z.string()).optional()
77
+ });
78
+ var mcpServerStdioSchema = mcpServerBaseSchema.extend({
79
+ transport: zod.z.literal("stdio"),
80
+ command: zod.z.string().min(1),
81
+ args: zod.z.array(zod.z.string()).optional(),
82
+ env: zod.z.record(zod.z.string(), zod.z.string()).optional(),
83
+ cwd: zod.z.string().optional()
84
+ });
85
+ var mcpServerHttpSchema = mcpServerBaseSchema.extend({
86
+ transport: zod.z.literal("http"),
87
+ url: zod.z.string().min(1),
88
+ bearerToken: zod.z.string().optional(),
89
+ bearerTokenEnvVar: zod.z.string().optional(),
90
+ httpHeaders: zod.z.record(zod.z.string(), zod.z.string()).optional(),
91
+ envHttpHeaders: zod.z.record(zod.z.string(), zod.z.string()).optional()
92
+ });
93
+ var mcpServerSchema = zod.z.discriminatedUnion("transport", [
94
+ mcpServerStdioSchema,
95
+ mcpServerHttpSchema
96
+ ]);
97
+ var mcpServersSchema = zod.z.record(zod.z.string(), mcpServerSchema);
71
98
  var settingsSchema = zod.z.object({
72
99
  codexPath: zod.z.string().optional(),
73
100
  cwd: zod.z.string().optional(),
101
+ addDirs: zod.z.array(zod.z.string().min(1)).optional(),
74
102
  approvalMode: zod.z.enum(["untrusted", "on-failure", "on-request", "never"]).optional(),
75
103
  sandboxMode: zod.z.enum(["read-only", "workspace-write", "danger-full-access"]).optional(),
76
104
  fullAuto: zod.z.boolean().optional(),
@@ -78,6 +106,7 @@ var settingsSchema = zod.z.object({
78
106
  skipGitRepoCheck: zod.z.boolean().optional(),
79
107
  color: zod.z.enum(["always", "never", "auto"]).optional(),
80
108
  allowNpx: zod.z.boolean().optional(),
109
+ outputLastMessageFile: zod.z.string().optional(),
81
110
  env: zod.z.record(zod.z.string(), zod.z.string()).optional(),
82
111
  verbose: zod.z.boolean().optional(),
83
112
  logger: zod.z.union([zod.z.literal(false), loggerFunctionSchema]).optional(),
@@ -92,6 +121,8 @@ var settingsSchema = zod.z.object({
92
121
  profile: zod.z.string().optional(),
93
122
  oss: zod.z.boolean().optional(),
94
123
  webSearch: zod.z.boolean().optional(),
124
+ mcpServers: mcpServersSchema.optional(),
125
+ rmcpClient: zod.z.boolean().optional(),
95
126
  // NEW: Generic overrides
96
127
  configOverrides: zod.z.record(
97
128
  zod.z.string(),
@@ -235,6 +266,7 @@ var codexCliProviderOptionsSchema = zod.z.object({
235
266
  reasoningSummary: zod.z.enum(["auto", "detailed"]).optional(),
236
267
  reasoningSummaryFormat: zod.z.enum(["none", "experimental"]).optional(),
237
268
  textVerbosity: zod.z.enum(["low", "medium", "high"]).optional(),
269
+ addDirs: zod.z.array(zod.z.string().min(1)).optional(),
238
270
  configOverrides: zod.z.record(
239
271
  zod.z.string(),
240
272
  zod.z.union([
@@ -244,7 +276,9 @@ var codexCliProviderOptionsSchema = zod.z.object({
244
276
  zod.z.object({}).passthrough(),
245
277
  zod.z.array(zod.z.any())
246
278
  ])
247
- ).optional()
279
+ ).optional(),
280
+ mcpServers: mcpServersSchema.optional(),
281
+ rmcpClient: zod.z.boolean().optional()
248
282
  }).strict();
249
283
  function resolveCodexPath(explicitPath, allowNpx) {
250
284
  if (explicitPath) return { cmd: "node", args: [explicitPath] };
@@ -286,15 +320,80 @@ var CodexCliLanguageModel = class {
286
320
  ...this.settings.configOverrides ?? {},
287
321
  ...providerOptions.configOverrides ?? {}
288
322
  } : void 0;
323
+ const mergedAddDirs = providerOptions.addDirs || this.settings.addDirs ? [...this.settings.addDirs ?? [], ...providerOptions.addDirs ?? []] : void 0;
324
+ const mergedMcpServers = this.mergeMcpServers(
325
+ this.settings.mcpServers,
326
+ providerOptions.mcpServers
327
+ );
289
328
  return {
290
329
  ...this.settings,
291
330
  reasoningEffort: providerOptions.reasoningEffort ?? this.settings.reasoningEffort,
292
331
  reasoningSummary: providerOptions.reasoningSummary ?? this.settings.reasoningSummary,
293
332
  reasoningSummaryFormat: providerOptions.reasoningSummaryFormat ?? this.settings.reasoningSummaryFormat,
294
333
  modelVerbosity: providerOptions.textVerbosity ?? this.settings.modelVerbosity,
295
- configOverrides: mergedConfigOverrides
334
+ configOverrides: mergedConfigOverrides,
335
+ addDirs: mergedAddDirs,
336
+ mcpServers: mergedMcpServers,
337
+ rmcpClient: providerOptions.rmcpClient ?? this.settings.rmcpClient
296
338
  };
297
339
  }
340
+ mergeMcpServers(base, override) {
341
+ if (!base) return override;
342
+ if (!override) return base;
343
+ const merged = { ...base };
344
+ for (const [name, incoming] of Object.entries(override)) {
345
+ const existing = base[name];
346
+ merged[name] = this.mergeSingleMcpServer(existing, incoming);
347
+ }
348
+ return merged;
349
+ }
350
+ mergeSingleMcpServer(existing, incoming) {
351
+ if (!existing || existing.transport !== incoming.transport) {
352
+ return { ...incoming };
353
+ }
354
+ if (incoming.transport === "stdio") {
355
+ const baseStdio = existing;
356
+ const result2 = {
357
+ transport: "stdio",
358
+ command: incoming.command,
359
+ args: incoming.args ?? baseStdio.args,
360
+ env: this.mergeStringRecord(baseStdio.env, incoming.env),
361
+ cwd: incoming.cwd ?? baseStdio.cwd,
362
+ enabled: incoming.enabled ?? existing.enabled,
363
+ startupTimeoutSec: incoming.startupTimeoutSec ?? existing.startupTimeoutSec,
364
+ toolTimeoutSec: incoming.toolTimeoutSec ?? existing.toolTimeoutSec,
365
+ enabledTools: incoming.enabledTools ?? existing.enabledTools,
366
+ disabledTools: incoming.disabledTools ?? existing.disabledTools
367
+ };
368
+ return result2;
369
+ }
370
+ const baseHttp = existing;
371
+ const hasIncomingAuth = incoming.bearerToken !== void 0 || incoming.bearerTokenEnvVar !== void 0;
372
+ const bearerToken = hasIncomingAuth ? incoming.bearerToken : baseHttp.bearerToken;
373
+ const bearerTokenEnvVar = hasIncomingAuth ? incoming.bearerTokenEnvVar : baseHttp.bearerTokenEnvVar;
374
+ const result = {
375
+ transport: "http",
376
+ url: incoming.url,
377
+ bearerToken,
378
+ bearerTokenEnvVar,
379
+ httpHeaders: this.mergeStringRecord(baseHttp.httpHeaders, incoming.httpHeaders),
380
+ envHttpHeaders: this.mergeStringRecord(baseHttp.envHttpHeaders, incoming.envHttpHeaders),
381
+ enabled: incoming.enabled ?? existing.enabled,
382
+ startupTimeoutSec: incoming.startupTimeoutSec ?? existing.startupTimeoutSec,
383
+ toolTimeoutSec: incoming.toolTimeoutSec ?? existing.toolTimeoutSec,
384
+ enabledTools: incoming.enabledTools ?? existing.enabledTools,
385
+ disabledTools: incoming.disabledTools ?? existing.disabledTools
386
+ };
387
+ return result;
388
+ }
389
+ mergeStringRecord(base, override) {
390
+ if (override !== void 0) {
391
+ if (Object.keys(override).length === 0) return {};
392
+ return { ...base ?? {}, ...override };
393
+ }
394
+ if (base) return { ...base };
395
+ return void 0;
396
+ }
298
397
  // Codex JSONL items use `type` for the item discriminator, but some
299
398
  // earlier fixtures (and defensive parsing) might still surface `item_type`.
300
399
  // This helper returns whichever is present.
@@ -345,12 +444,20 @@ var CodexCliLanguageModel = class {
345
444
  if (settings.webSearch) {
346
445
  args.push("-c", "tools.web_search=true");
347
446
  }
447
+ this.applyMcpSettings(args, settings);
348
448
  if (settings.color) {
349
449
  args.push("--color", settings.color);
350
450
  }
351
451
  if (this.modelId) {
352
452
  args.push("-m", this.modelId);
353
453
  }
454
+ if (settings.addDirs?.length) {
455
+ for (const dir of settings.addDirs) {
456
+ if (typeof dir === "string" && dir.trim().length > 0) {
457
+ args.push("--add-dir", dir);
458
+ }
459
+ }
460
+ }
354
461
  if (settings.configOverrides) {
355
462
  for (const [key, value] of Object.entries(settings.configOverrides)) {
356
463
  this.addConfigOverride(args, key, value);
@@ -379,16 +486,73 @@ var CodexCliLanguageModel = class {
379
486
  RUST_LOG: process.env.RUST_LOG || "error"
380
487
  };
381
488
  let lastMessagePath = settings.outputLastMessageFile;
489
+ let lastMessageIsTemp = false;
382
490
  if (!lastMessagePath) {
383
491
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), "codex-cli-"));
384
492
  lastMessagePath = path.join(dir, "last-message.txt");
493
+ lastMessageIsTemp = true;
385
494
  }
386
495
  args.push("--output-last-message", lastMessagePath);
387
- return { cmd: base.cmd, args, env, cwd: settings.cwd, lastMessagePath, schemaPath };
496
+ return {
497
+ cmd: base.cmd,
498
+ args,
499
+ env,
500
+ cwd: settings.cwd,
501
+ lastMessagePath,
502
+ lastMessageIsTemp,
503
+ schemaPath
504
+ };
505
+ }
506
+ applyMcpSettings(args, settings) {
507
+ if (settings.rmcpClient) {
508
+ this.addConfigOverride(args, "features.rmcp_client", true);
509
+ }
510
+ if (!settings.mcpServers) return;
511
+ for (const [rawName, server] of Object.entries(settings.mcpServers)) {
512
+ const name = rawName.trim();
513
+ if (!name) continue;
514
+ const prefix = `mcp_servers.${name}`;
515
+ if (server.enabled !== void 0) {
516
+ this.addConfigOverride(args, `${prefix}.enabled`, server.enabled);
517
+ }
518
+ if (server.startupTimeoutSec !== void 0) {
519
+ this.addConfigOverride(args, `${prefix}.startup_timeout_sec`, server.startupTimeoutSec);
520
+ }
521
+ if (server.toolTimeoutSec !== void 0) {
522
+ this.addConfigOverride(args, `${prefix}.tool_timeout_sec`, server.toolTimeoutSec);
523
+ }
524
+ if (server.enabledTools !== void 0) {
525
+ this.addConfigOverride(args, `${prefix}.enabled_tools`, server.enabledTools);
526
+ }
527
+ if (server.disabledTools !== void 0) {
528
+ this.addConfigOverride(args, `${prefix}.disabled_tools`, server.disabledTools);
529
+ }
530
+ if (server.transport === "stdio") {
531
+ this.addConfigOverride(args, `${prefix}.command`, server.command);
532
+ if (server.args !== void 0) this.addConfigOverride(args, `${prefix}.args`, server.args);
533
+ if (server.env !== void 0) this.addConfigOverride(args, `${prefix}.env`, server.env);
534
+ if (server.cwd) this.addConfigOverride(args, `${prefix}.cwd`, server.cwd);
535
+ } else {
536
+ this.addConfigOverride(args, `${prefix}.url`, server.url);
537
+ if (server.bearerToken !== void 0)
538
+ this.addConfigOverride(args, `${prefix}.bearer_token`, server.bearerToken);
539
+ if (server.bearerTokenEnvVar)
540
+ this.addConfigOverride(args, `${prefix}.bearer_token_env_var`, server.bearerTokenEnvVar);
541
+ if (server.httpHeaders !== void 0)
542
+ this.addConfigOverride(args, `${prefix}.http_headers`, server.httpHeaders);
543
+ if (server.envHttpHeaders !== void 0)
544
+ this.addConfigOverride(args, `${prefix}.env_http_headers`, server.envHttpHeaders);
545
+ }
546
+ }
388
547
  }
389
548
  addConfigOverride(args, key, value) {
390
549
  if (this.isPlainObject(value)) {
391
- for (const [childKey, childValue] of Object.entries(value)) {
550
+ const entries = Object.entries(value);
551
+ if (entries.length === 0) {
552
+ args.push("-c", `${key}={}`);
553
+ return;
554
+ }
555
+ for (const [childKey, childValue] of entries) {
392
556
  this.addConfigOverride(
393
557
  args,
394
558
  `${key}.${childKey}`,
@@ -682,7 +846,7 @@ var CodexCliLanguageModel = class {
682
846
  });
683
847
  const effectiveSettings = this.mergeSettings(providerOptions);
684
848
  const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
685
- const { cmd, args, env, cwd, lastMessagePath, schemaPath } = this.buildArgs(
849
+ const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath } = this.buildArgs(
686
850
  promptText,
687
851
  responseFormat,
688
852
  effectiveSettings
@@ -802,9 +966,11 @@ var CodexCliLanguageModel = class {
802
966
  }
803
967
  } catch {
804
968
  }
805
- try {
806
- fs.rmSync(lastMessagePath, { force: true });
807
- } catch {
969
+ if (lastMessageIsTemp) {
970
+ try {
971
+ fs.rmSync(lastMessagePath, { force: true });
972
+ } catch {
973
+ }
808
974
  }
809
975
  }
810
976
  const content = [{ type: "text", text }];
@@ -838,7 +1004,7 @@ var CodexCliLanguageModel = class {
838
1004
  });
839
1005
  const effectiveSettings = this.mergeSettings(providerOptions);
840
1006
  const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
841
- const { cmd, args, env, cwd, lastMessagePath, schemaPath } = this.buildArgs(
1007
+ const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath } = this.buildArgs(
842
1008
  promptText,
843
1009
  responseFormat,
844
1010
  effectiveSettings
@@ -969,9 +1135,11 @@ var CodexCliLanguageModel = class {
969
1135
  if (fileText) finalText = fileText.trim();
970
1136
  } catch {
971
1137
  }
972
- try {
973
- fs.rmSync(lastMessagePath, { force: true });
974
- } catch {
1138
+ if (lastMessageIsTemp) {
1139
+ try {
1140
+ fs.rmSync(lastMessagePath, { force: true });
1141
+ } catch {
1142
+ }
975
1143
  }
976
1144
  }
977
1145
  if (finalText) {
package/dist/index.d.cts CHANGED
@@ -46,9 +46,60 @@ type ReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
46
46
  type ReasoningSummary = 'auto' | 'detailed';
47
47
  type ReasoningSummaryFormat = 'none' | 'experimental';
48
48
  type ModelVerbosity = 'low' | 'medium' | 'high';
49
+ interface McpServerBase {
50
+ /**
51
+ * Enable/disable this MCP server without removing its definition.
52
+ * Maps to: `mcp_servers.<name>.enabled`
53
+ */
54
+ enabled?: boolean;
55
+ /**
56
+ * Time allowed for the MCP server to start (in seconds).
57
+ * Maps to: `mcp_servers.<name>.startup_timeout_sec`
58
+ */
59
+ startupTimeoutSec?: number;
60
+ /**
61
+ * Max time a single MCP tool call may run (in seconds).
62
+ * Maps to: `mcp_servers.<name>.tool_timeout_sec`
63
+ */
64
+ toolTimeoutSec?: number;
65
+ /**
66
+ * Explicit allow/deny lists for tools exposed by the server.
67
+ * Maps to: `mcp_servers.<name>.enabled_tools` / `disabled_tools`
68
+ */
69
+ enabledTools?: string[];
70
+ disabledTools?: string[];
71
+ }
72
+ interface McpServerStdio extends McpServerBase {
73
+ /** Execute an MCP server over stdio */
74
+ transport: 'stdio';
75
+ /** Command to start the MCP server (e.g., `node`, `python`, or a binary path). */
76
+ command: string;
77
+ /** Arguments passed to the command. */
78
+ args?: string[];
79
+ /** Environment variables passed to the MCP process. */
80
+ env?: Record<string, string>;
81
+ /** Optional working directory for the MCP server process. */
82
+ cwd?: string;
83
+ }
84
+ interface McpServerHttp extends McpServerBase {
85
+ /** Use an HTTP-based MCP server (RMCP). */
86
+ transport: 'http';
87
+ /** Base URL for the MCP server. */
88
+ url: string;
89
+ /** Bearer token supplied inline (use env var variant to avoid embedding secrets). */
90
+ bearerToken?: string;
91
+ /** Name of env var that holds the bearer token. */
92
+ bearerTokenEnvVar?: string;
93
+ /** Static HTTP headers to send with each MCP request. */
94
+ httpHeaders?: Record<string, string>;
95
+ /** Names of env vars whose values should be sent as HTTP headers. */
96
+ envHttpHeaders?: Record<string, string>;
97
+ }
98
+ type McpServerConfig = McpServerStdio | McpServerHttp;
49
99
  interface CodexCliSettings {
50
100
  codexPath?: string;
51
101
  cwd?: string;
102
+ addDirs?: string[];
52
103
  approvalMode?: ApprovalMode;
53
104
  sandboxMode?: SandboxMode;
54
105
  fullAuto?: boolean;
@@ -97,6 +148,16 @@ interface CodexCliSettings {
97
148
  * Maps to: `-c model_verbosity=<value>`
98
149
  */
99
150
  modelVerbosity?: ModelVerbosity;
151
+ /**
152
+ * Configure MCP servers (stdio or HTTP/RMCP). Keys are server names.
153
+ * Each entry maps to the Codex CLI `mcp_servers.<name>` table.
154
+ */
155
+ mcpServers?: Record<string, McpServerConfig>;
156
+ /**
157
+ * Enable the RMCP client so HTTP-based MCP servers can be contacted.
158
+ * Maps to: `-c features.rmcp_client=true`
159
+ */
160
+ rmcpClient?: boolean;
100
161
  /**
101
162
  * Include experimental plan tool that the model can use to update its current plan.
102
163
  *
@@ -169,11 +230,25 @@ interface CodexCliProviderOptions {
169
230
  * Maps to Codex `model_verbosity`.
170
231
  */
171
232
  textVerbosity?: ModelVerbosity;
233
+ /**
234
+ * Per-call override for extra directories Codex can access.
235
+ * Maps to repeated `--add-dir` flags.
236
+ */
237
+ addDirs?: string[];
172
238
  /**
173
239
  * Per-call Codex CLI config overrides. These are merged with
174
240
  * constructor-level overrides with per-call values taking precedence.
175
241
  */
176
242
  configOverrides?: Record<string, string | number | boolean | object>;
243
+ /**
244
+ * Per-call MCP server definitions. Merged with constructor definitions
245
+ * (per-call servers and fields take precedence).
246
+ */
247
+ mcpServers?: Record<string, McpServerConfig>;
248
+ /**
249
+ * Per-call override for RMCP client enablement.
250
+ */
251
+ rmcpClient?: boolean;
177
252
  }
178
253
 
179
254
  interface CodexCliProvider extends ProviderV2 {
@@ -203,8 +278,12 @@ declare class CodexCliLanguageModel implements LanguageModelV2 {
203
278
  private sessionId?;
204
279
  constructor(options: CodexLanguageModelOptions);
205
280
  private mergeSettings;
281
+ private mergeMcpServers;
282
+ private mergeSingleMcpServer;
283
+ private mergeStringRecord;
206
284
  private getItemType;
207
285
  private buildArgs;
286
+ private applyMcpSettings;
208
287
  private addConfigOverride;
209
288
  /**
210
289
  * Serialize a config override value into a CLI-safe string.
package/dist/index.d.ts CHANGED
@@ -46,9 +46,60 @@ type ReasoningEffort = 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
46
46
  type ReasoningSummary = 'auto' | 'detailed';
47
47
  type ReasoningSummaryFormat = 'none' | 'experimental';
48
48
  type ModelVerbosity = 'low' | 'medium' | 'high';
49
+ interface McpServerBase {
50
+ /**
51
+ * Enable/disable this MCP server without removing its definition.
52
+ * Maps to: `mcp_servers.<name>.enabled`
53
+ */
54
+ enabled?: boolean;
55
+ /**
56
+ * Time allowed for the MCP server to start (in seconds).
57
+ * Maps to: `mcp_servers.<name>.startup_timeout_sec`
58
+ */
59
+ startupTimeoutSec?: number;
60
+ /**
61
+ * Max time a single MCP tool call may run (in seconds).
62
+ * Maps to: `mcp_servers.<name>.tool_timeout_sec`
63
+ */
64
+ toolTimeoutSec?: number;
65
+ /**
66
+ * Explicit allow/deny lists for tools exposed by the server.
67
+ * Maps to: `mcp_servers.<name>.enabled_tools` / `disabled_tools`
68
+ */
69
+ enabledTools?: string[];
70
+ disabledTools?: string[];
71
+ }
72
+ interface McpServerStdio extends McpServerBase {
73
+ /** Execute an MCP server over stdio */
74
+ transport: 'stdio';
75
+ /** Command to start the MCP server (e.g., `node`, `python`, or a binary path). */
76
+ command: string;
77
+ /** Arguments passed to the command. */
78
+ args?: string[];
79
+ /** Environment variables passed to the MCP process. */
80
+ env?: Record<string, string>;
81
+ /** Optional working directory for the MCP server process. */
82
+ cwd?: string;
83
+ }
84
+ interface McpServerHttp extends McpServerBase {
85
+ /** Use an HTTP-based MCP server (RMCP). */
86
+ transport: 'http';
87
+ /** Base URL for the MCP server. */
88
+ url: string;
89
+ /** Bearer token supplied inline (use env var variant to avoid embedding secrets). */
90
+ bearerToken?: string;
91
+ /** Name of env var that holds the bearer token. */
92
+ bearerTokenEnvVar?: string;
93
+ /** Static HTTP headers to send with each MCP request. */
94
+ httpHeaders?: Record<string, string>;
95
+ /** Names of env vars whose values should be sent as HTTP headers. */
96
+ envHttpHeaders?: Record<string, string>;
97
+ }
98
+ type McpServerConfig = McpServerStdio | McpServerHttp;
49
99
  interface CodexCliSettings {
50
100
  codexPath?: string;
51
101
  cwd?: string;
102
+ addDirs?: string[];
52
103
  approvalMode?: ApprovalMode;
53
104
  sandboxMode?: SandboxMode;
54
105
  fullAuto?: boolean;
@@ -97,6 +148,16 @@ interface CodexCliSettings {
97
148
  * Maps to: `-c model_verbosity=<value>`
98
149
  */
99
150
  modelVerbosity?: ModelVerbosity;
151
+ /**
152
+ * Configure MCP servers (stdio or HTTP/RMCP). Keys are server names.
153
+ * Each entry maps to the Codex CLI `mcp_servers.<name>` table.
154
+ */
155
+ mcpServers?: Record<string, McpServerConfig>;
156
+ /**
157
+ * Enable the RMCP client so HTTP-based MCP servers can be contacted.
158
+ * Maps to: `-c features.rmcp_client=true`
159
+ */
160
+ rmcpClient?: boolean;
100
161
  /**
101
162
  * Include experimental plan tool that the model can use to update its current plan.
102
163
  *
@@ -169,11 +230,25 @@ interface CodexCliProviderOptions {
169
230
  * Maps to Codex `model_verbosity`.
170
231
  */
171
232
  textVerbosity?: ModelVerbosity;
233
+ /**
234
+ * Per-call override for extra directories Codex can access.
235
+ * Maps to repeated `--add-dir` flags.
236
+ */
237
+ addDirs?: string[];
172
238
  /**
173
239
  * Per-call Codex CLI config overrides. These are merged with
174
240
  * constructor-level overrides with per-call values taking precedence.
175
241
  */
176
242
  configOverrides?: Record<string, string | number | boolean | object>;
243
+ /**
244
+ * Per-call MCP server definitions. Merged with constructor definitions
245
+ * (per-call servers and fields take precedence).
246
+ */
247
+ mcpServers?: Record<string, McpServerConfig>;
248
+ /**
249
+ * Per-call override for RMCP client enablement.
250
+ */
251
+ rmcpClient?: boolean;
177
252
  }
178
253
 
179
254
  interface CodexCliProvider extends ProviderV2 {
@@ -203,8 +278,12 @@ declare class CodexCliLanguageModel implements LanguageModelV2 {
203
278
  private sessionId?;
204
279
  constructor(options: CodexLanguageModelOptions);
205
280
  private mergeSettings;
281
+ private mergeMcpServers;
282
+ private mergeSingleMcpServer;
283
+ private mergeStringRecord;
206
284
  private getItemType;
207
285
  private buildArgs;
286
+ private applyMcpSettings;
208
287
  private addConfigOverride;
209
288
  /**
210
289
  * Serialize a config override value into a CLI-safe string.
package/dist/index.js CHANGED
@@ -65,9 +65,37 @@ var loggerFunctionSchema = z.object({
65
65
  message: "error must be a function"
66
66
  })
67
67
  });
68
+ var mcpServerBaseSchema = z.object({
69
+ enabled: z.boolean().optional(),
70
+ startupTimeoutSec: z.number().int().positive().optional(),
71
+ toolTimeoutSec: z.number().int().positive().optional(),
72
+ enabledTools: z.array(z.string()).optional(),
73
+ disabledTools: z.array(z.string()).optional()
74
+ });
75
+ var mcpServerStdioSchema = mcpServerBaseSchema.extend({
76
+ transport: z.literal("stdio"),
77
+ command: z.string().min(1),
78
+ args: z.array(z.string()).optional(),
79
+ env: z.record(z.string(), z.string()).optional(),
80
+ cwd: z.string().optional()
81
+ });
82
+ var mcpServerHttpSchema = mcpServerBaseSchema.extend({
83
+ transport: z.literal("http"),
84
+ url: z.string().min(1),
85
+ bearerToken: z.string().optional(),
86
+ bearerTokenEnvVar: z.string().optional(),
87
+ httpHeaders: z.record(z.string(), z.string()).optional(),
88
+ envHttpHeaders: z.record(z.string(), z.string()).optional()
89
+ });
90
+ var mcpServerSchema = z.discriminatedUnion("transport", [
91
+ mcpServerStdioSchema,
92
+ mcpServerHttpSchema
93
+ ]);
94
+ var mcpServersSchema = z.record(z.string(), mcpServerSchema);
68
95
  var settingsSchema = z.object({
69
96
  codexPath: z.string().optional(),
70
97
  cwd: z.string().optional(),
98
+ addDirs: z.array(z.string().min(1)).optional(),
71
99
  approvalMode: z.enum(["untrusted", "on-failure", "on-request", "never"]).optional(),
72
100
  sandboxMode: z.enum(["read-only", "workspace-write", "danger-full-access"]).optional(),
73
101
  fullAuto: z.boolean().optional(),
@@ -75,6 +103,7 @@ var settingsSchema = z.object({
75
103
  skipGitRepoCheck: z.boolean().optional(),
76
104
  color: z.enum(["always", "never", "auto"]).optional(),
77
105
  allowNpx: z.boolean().optional(),
106
+ outputLastMessageFile: z.string().optional(),
78
107
  env: z.record(z.string(), z.string()).optional(),
79
108
  verbose: z.boolean().optional(),
80
109
  logger: z.union([z.literal(false), loggerFunctionSchema]).optional(),
@@ -89,6 +118,8 @@ var settingsSchema = z.object({
89
118
  profile: z.string().optional(),
90
119
  oss: z.boolean().optional(),
91
120
  webSearch: z.boolean().optional(),
121
+ mcpServers: mcpServersSchema.optional(),
122
+ rmcpClient: z.boolean().optional(),
92
123
  // NEW: Generic overrides
93
124
  configOverrides: z.record(
94
125
  z.string(),
@@ -232,6 +263,7 @@ var codexCliProviderOptionsSchema = z.object({
232
263
  reasoningSummary: z.enum(["auto", "detailed"]).optional(),
233
264
  reasoningSummaryFormat: z.enum(["none", "experimental"]).optional(),
234
265
  textVerbosity: z.enum(["low", "medium", "high"]).optional(),
266
+ addDirs: z.array(z.string().min(1)).optional(),
235
267
  configOverrides: z.record(
236
268
  z.string(),
237
269
  z.union([
@@ -241,7 +273,9 @@ var codexCliProviderOptionsSchema = z.object({
241
273
  z.object({}).passthrough(),
242
274
  z.array(z.any())
243
275
  ])
244
- ).optional()
276
+ ).optional(),
277
+ mcpServers: mcpServersSchema.optional(),
278
+ rmcpClient: z.boolean().optional()
245
279
  }).strict();
246
280
  function resolveCodexPath(explicitPath, allowNpx) {
247
281
  if (explicitPath) return { cmd: "node", args: [explicitPath] };
@@ -283,15 +317,80 @@ var CodexCliLanguageModel = class {
283
317
  ...this.settings.configOverrides ?? {},
284
318
  ...providerOptions.configOverrides ?? {}
285
319
  } : void 0;
320
+ const mergedAddDirs = providerOptions.addDirs || this.settings.addDirs ? [...this.settings.addDirs ?? [], ...providerOptions.addDirs ?? []] : void 0;
321
+ const mergedMcpServers = this.mergeMcpServers(
322
+ this.settings.mcpServers,
323
+ providerOptions.mcpServers
324
+ );
286
325
  return {
287
326
  ...this.settings,
288
327
  reasoningEffort: providerOptions.reasoningEffort ?? this.settings.reasoningEffort,
289
328
  reasoningSummary: providerOptions.reasoningSummary ?? this.settings.reasoningSummary,
290
329
  reasoningSummaryFormat: providerOptions.reasoningSummaryFormat ?? this.settings.reasoningSummaryFormat,
291
330
  modelVerbosity: providerOptions.textVerbosity ?? this.settings.modelVerbosity,
292
- configOverrides: mergedConfigOverrides
331
+ configOverrides: mergedConfigOverrides,
332
+ addDirs: mergedAddDirs,
333
+ mcpServers: mergedMcpServers,
334
+ rmcpClient: providerOptions.rmcpClient ?? this.settings.rmcpClient
293
335
  };
294
336
  }
337
+ mergeMcpServers(base, override) {
338
+ if (!base) return override;
339
+ if (!override) return base;
340
+ const merged = { ...base };
341
+ for (const [name, incoming] of Object.entries(override)) {
342
+ const existing = base[name];
343
+ merged[name] = this.mergeSingleMcpServer(existing, incoming);
344
+ }
345
+ return merged;
346
+ }
347
+ mergeSingleMcpServer(existing, incoming) {
348
+ if (!existing || existing.transport !== incoming.transport) {
349
+ return { ...incoming };
350
+ }
351
+ if (incoming.transport === "stdio") {
352
+ const baseStdio = existing;
353
+ const result2 = {
354
+ transport: "stdio",
355
+ command: incoming.command,
356
+ args: incoming.args ?? baseStdio.args,
357
+ env: this.mergeStringRecord(baseStdio.env, incoming.env),
358
+ cwd: incoming.cwd ?? baseStdio.cwd,
359
+ enabled: incoming.enabled ?? existing.enabled,
360
+ startupTimeoutSec: incoming.startupTimeoutSec ?? existing.startupTimeoutSec,
361
+ toolTimeoutSec: incoming.toolTimeoutSec ?? existing.toolTimeoutSec,
362
+ enabledTools: incoming.enabledTools ?? existing.enabledTools,
363
+ disabledTools: incoming.disabledTools ?? existing.disabledTools
364
+ };
365
+ return result2;
366
+ }
367
+ const baseHttp = existing;
368
+ const hasIncomingAuth = incoming.bearerToken !== void 0 || incoming.bearerTokenEnvVar !== void 0;
369
+ const bearerToken = hasIncomingAuth ? incoming.bearerToken : baseHttp.bearerToken;
370
+ const bearerTokenEnvVar = hasIncomingAuth ? incoming.bearerTokenEnvVar : baseHttp.bearerTokenEnvVar;
371
+ const result = {
372
+ transport: "http",
373
+ url: incoming.url,
374
+ bearerToken,
375
+ bearerTokenEnvVar,
376
+ httpHeaders: this.mergeStringRecord(baseHttp.httpHeaders, incoming.httpHeaders),
377
+ envHttpHeaders: this.mergeStringRecord(baseHttp.envHttpHeaders, incoming.envHttpHeaders),
378
+ enabled: incoming.enabled ?? existing.enabled,
379
+ startupTimeoutSec: incoming.startupTimeoutSec ?? existing.startupTimeoutSec,
380
+ toolTimeoutSec: incoming.toolTimeoutSec ?? existing.toolTimeoutSec,
381
+ enabledTools: incoming.enabledTools ?? existing.enabledTools,
382
+ disabledTools: incoming.disabledTools ?? existing.disabledTools
383
+ };
384
+ return result;
385
+ }
386
+ mergeStringRecord(base, override) {
387
+ if (override !== void 0) {
388
+ if (Object.keys(override).length === 0) return {};
389
+ return { ...base ?? {}, ...override };
390
+ }
391
+ if (base) return { ...base };
392
+ return void 0;
393
+ }
295
394
  // Codex JSONL items use `type` for the item discriminator, but some
296
395
  // earlier fixtures (and defensive parsing) might still surface `item_type`.
297
396
  // This helper returns whichever is present.
@@ -342,12 +441,20 @@ var CodexCliLanguageModel = class {
342
441
  if (settings.webSearch) {
343
442
  args.push("-c", "tools.web_search=true");
344
443
  }
444
+ this.applyMcpSettings(args, settings);
345
445
  if (settings.color) {
346
446
  args.push("--color", settings.color);
347
447
  }
348
448
  if (this.modelId) {
349
449
  args.push("-m", this.modelId);
350
450
  }
451
+ if (settings.addDirs?.length) {
452
+ for (const dir of settings.addDirs) {
453
+ if (typeof dir === "string" && dir.trim().length > 0) {
454
+ args.push("--add-dir", dir);
455
+ }
456
+ }
457
+ }
351
458
  if (settings.configOverrides) {
352
459
  for (const [key, value] of Object.entries(settings.configOverrides)) {
353
460
  this.addConfigOverride(args, key, value);
@@ -376,16 +483,73 @@ var CodexCliLanguageModel = class {
376
483
  RUST_LOG: process.env.RUST_LOG || "error"
377
484
  };
378
485
  let lastMessagePath = settings.outputLastMessageFile;
486
+ let lastMessageIsTemp = false;
379
487
  if (!lastMessagePath) {
380
488
  const dir = mkdtempSync(join(tmpdir(), "codex-cli-"));
381
489
  lastMessagePath = join(dir, "last-message.txt");
490
+ lastMessageIsTemp = true;
382
491
  }
383
492
  args.push("--output-last-message", lastMessagePath);
384
- return { cmd: base.cmd, args, env, cwd: settings.cwd, lastMessagePath, schemaPath };
493
+ return {
494
+ cmd: base.cmd,
495
+ args,
496
+ env,
497
+ cwd: settings.cwd,
498
+ lastMessagePath,
499
+ lastMessageIsTemp,
500
+ schemaPath
501
+ };
502
+ }
503
+ applyMcpSettings(args, settings) {
504
+ if (settings.rmcpClient) {
505
+ this.addConfigOverride(args, "features.rmcp_client", true);
506
+ }
507
+ if (!settings.mcpServers) return;
508
+ for (const [rawName, server] of Object.entries(settings.mcpServers)) {
509
+ const name = rawName.trim();
510
+ if (!name) continue;
511
+ const prefix = `mcp_servers.${name}`;
512
+ if (server.enabled !== void 0) {
513
+ this.addConfigOverride(args, `${prefix}.enabled`, server.enabled);
514
+ }
515
+ if (server.startupTimeoutSec !== void 0) {
516
+ this.addConfigOverride(args, `${prefix}.startup_timeout_sec`, server.startupTimeoutSec);
517
+ }
518
+ if (server.toolTimeoutSec !== void 0) {
519
+ this.addConfigOverride(args, `${prefix}.tool_timeout_sec`, server.toolTimeoutSec);
520
+ }
521
+ if (server.enabledTools !== void 0) {
522
+ this.addConfigOverride(args, `${prefix}.enabled_tools`, server.enabledTools);
523
+ }
524
+ if (server.disabledTools !== void 0) {
525
+ this.addConfigOverride(args, `${prefix}.disabled_tools`, server.disabledTools);
526
+ }
527
+ if (server.transport === "stdio") {
528
+ this.addConfigOverride(args, `${prefix}.command`, server.command);
529
+ if (server.args !== void 0) this.addConfigOverride(args, `${prefix}.args`, server.args);
530
+ if (server.env !== void 0) this.addConfigOverride(args, `${prefix}.env`, server.env);
531
+ if (server.cwd) this.addConfigOverride(args, `${prefix}.cwd`, server.cwd);
532
+ } else {
533
+ this.addConfigOverride(args, `${prefix}.url`, server.url);
534
+ if (server.bearerToken !== void 0)
535
+ this.addConfigOverride(args, `${prefix}.bearer_token`, server.bearerToken);
536
+ if (server.bearerTokenEnvVar)
537
+ this.addConfigOverride(args, `${prefix}.bearer_token_env_var`, server.bearerTokenEnvVar);
538
+ if (server.httpHeaders !== void 0)
539
+ this.addConfigOverride(args, `${prefix}.http_headers`, server.httpHeaders);
540
+ if (server.envHttpHeaders !== void 0)
541
+ this.addConfigOverride(args, `${prefix}.env_http_headers`, server.envHttpHeaders);
542
+ }
543
+ }
385
544
  }
386
545
  addConfigOverride(args, key, value) {
387
546
  if (this.isPlainObject(value)) {
388
- for (const [childKey, childValue] of Object.entries(value)) {
547
+ const entries = Object.entries(value);
548
+ if (entries.length === 0) {
549
+ args.push("-c", `${key}={}`);
550
+ return;
551
+ }
552
+ for (const [childKey, childValue] of entries) {
389
553
  this.addConfigOverride(
390
554
  args,
391
555
  `${key}.${childKey}`,
@@ -679,7 +843,7 @@ var CodexCliLanguageModel = class {
679
843
  });
680
844
  const effectiveSettings = this.mergeSettings(providerOptions);
681
845
  const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
682
- const { cmd, args, env, cwd, lastMessagePath, schemaPath } = this.buildArgs(
846
+ const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath } = this.buildArgs(
683
847
  promptText,
684
848
  responseFormat,
685
849
  effectiveSettings
@@ -799,9 +963,11 @@ var CodexCliLanguageModel = class {
799
963
  }
800
964
  } catch {
801
965
  }
802
- try {
803
- rmSync(lastMessagePath, { force: true });
804
- } catch {
966
+ if (lastMessageIsTemp) {
967
+ try {
968
+ rmSync(lastMessagePath, { force: true });
969
+ } catch {
970
+ }
805
971
  }
806
972
  }
807
973
  const content = [{ type: "text", text }];
@@ -835,7 +1001,7 @@ var CodexCliLanguageModel = class {
835
1001
  });
836
1002
  const effectiveSettings = this.mergeSettings(providerOptions);
837
1003
  const responseFormat = options.responseFormat?.type === "json" ? { type: "json", schema: options.responseFormat.schema } : void 0;
838
- const { cmd, args, env, cwd, lastMessagePath, schemaPath } = this.buildArgs(
1004
+ const { cmd, args, env, cwd, lastMessagePath, lastMessageIsTemp, schemaPath } = this.buildArgs(
839
1005
  promptText,
840
1006
  responseFormat,
841
1007
  effectiveSettings
@@ -966,9 +1132,11 @@ var CodexCliLanguageModel = class {
966
1132
  if (fileText) finalText = fileText.trim();
967
1133
  } catch {
968
1134
  }
969
- try {
970
- rmSync(lastMessagePath, { force: true });
971
- } catch {
1135
+ if (lastMessageIsTemp) {
1136
+ try {
1137
+ rmSync(lastMessagePath, { force: true });
1138
+ } catch {
1139
+ }
972
1140
  }
973
1141
  }
974
1142
  if (finalText) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-sdk-provider-codex-cli",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "AI SDK v5 provider for OpenAI Codex CLI with native JSON Schema support",
5
5
  "keywords": [
6
6
  "ai-sdk",