@oisincoveney/pipeline 1.16.0 → 1.16.2

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/dist/config.d.ts CHANGED
@@ -223,8 +223,8 @@ declare const configSchema: z.ZodObject<{
223
223
  policy: z.ZodOptional<z.ZodObject<{
224
224
  commands: z.ZodOptional<z.ZodEnum<{
225
225
  allow: "allow";
226
- "trusted-only": "trusted-only";
227
226
  deny: "deny";
227
+ "trusted-only": "trusted-only";
228
228
  }>>;
229
229
  modules: z.ZodOptional<z.ZodEnum<{
230
230
  allow: "allow";
@@ -240,6 +240,7 @@ declare const configSchema: z.ZodObject<{
240
240
  }>;
241
241
  provider: z.ZodLiteral<"toolhive">;
242
242
  token_env: z.ZodDefault<z.ZodString>;
243
+ url: z.ZodOptional<z.ZodString>;
243
244
  url_env: z.ZodDefault<z.ZodString>;
244
245
  }, z.core.$strict>>;
245
246
  mcp_servers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
@@ -278,10 +279,10 @@ declare const configSchema: z.ZodObject<{
278
279
  }, z.core.$strict>>;
279
280
  output: z.ZodOptional<z.ZodObject<{
280
281
  format: z.ZodEnum<{
282
+ json_schema: "json_schema";
281
283
  text: "text";
282
284
  json: "json";
283
285
  jsonl: "jsonl";
284
- json_schema: "json_schema";
285
286
  }>;
286
287
  repair: z.ZodOptional<z.ZodObject<{
287
288
  enabled: z.ZodOptional<z.ZodBoolean>;
@@ -325,10 +326,10 @@ declare const configSchema: z.ZodObject<{
325
326
  disabled: "disabled";
326
327
  }>>>;
327
328
  output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
329
+ json_schema: "json_schema";
328
330
  text: "text";
329
331
  json: "json";
330
332
  jsonl: "jsonl";
331
- json_schema: "json_schema";
332
333
  }>>>;
333
334
  rules: z.ZodOptional<z.ZodBoolean>;
334
335
  skills: z.ZodOptional<z.ZodBoolean>;
package/dist/config.js CHANGED
@@ -132,7 +132,8 @@ const mcpGatewaySchema = z.object({
132
132
  default_profile: z.string().min(1).optional(),
133
133
  mode: z.enum(["hosted", "local"]),
134
134
  provider: z.literal("toolhive"),
135
- token_env: z.string().min(1).default("PIPELINE_MCP_GATEWAY_TOKEN"),
135
+ token_env: z.string().min(1).default("MEMORY_MCP_BASIC_AUTH"),
136
+ url: z.string().url().refine((value) => ["http:", "https:"].includes(new URL(value).protocol), { message: "MCP gateway url must use http or https" }).optional(),
136
137
  url_env: z.string().min(1).default("PIPELINE_MCP_GATEWAY_URL")
137
138
  }).strict();
138
139
  const instructionsSchema = z.object({
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { PipelineConfigError, loadPipelineConfig, tryLoadPipelineConfig } from "./config.js";
3
- import { configureGatewayHosts, localGatewayStatus, renderGatewayConfig, runGatewayDoctor, startLocalGateway } from "./mcp/gateway.js";
4
3
  import { compileWorkflowPlan } from "./workflow-planner.js";
5
4
  import { formatInstallCommandsResult, installCommands, parseCommandHost } from "./install-commands.js";
5
+ import { configureGatewayHosts, localGatewayStatus, renderGatewayConfig, runGatewayDoctor, startLocalGateway } from "./mcp/gateway.js";
6
6
  import { createOrchestratorLaunchPlan, createRunnerLaunchPlan } from "./runner.js";
7
7
  import { formatConfigError, runPipelineFromConfig } from "./pipeline-runtime.js";
8
8
  import { runKubernetesRunnerJob } from "./kubernetes-runner.js";
@@ -1,6 +1,5 @@
1
1
  import { resolveFileReference } from "./path-refs.js";
2
2
  import { loadPipelineConfig } from "./config.js";
3
- import { codexNativeMcpConfig } from "./mcp/native-config.js";
4
3
  import { compileWorkflowPlan } from "./workflow-planner.js";
5
4
  import { existsSync, readFileSync } from "node:fs";
6
5
  import { basename, dirname, join, relative } from "node:path";
@@ -407,7 +406,6 @@ function codexTomlAgentDefinition(config, cwd, id, profile) {
407
406
  };
408
407
  const skillConfig = codexSkillConfig(config, cwd, profile);
409
408
  const agentConfig = {
410
- name: id,
411
409
  description: profile.description ?? id,
412
410
  ...profileWithResolvedModel.model ? { model: profileWithResolvedModel.model } : {},
413
411
  ...profile.filesystem?.mode ? { sandbox_mode: profile.filesystem.mode } : {},
@@ -424,7 +422,6 @@ function codexTomlAgentDefinition(config, cwd, id, profile) {
424
422
  "",
425
423
  codexAgentInstructions(cwd, profile)
426
424
  ].join("\n"),
427
- ...codexNativeMcpConfig(config, profile),
428
425
  skills: { config: skillConfig }
429
426
  };
430
427
  const mcpConfig = "mcp_servers" in agentConfig ? agentConfig : {
@@ -5,7 +5,8 @@ import { execa } from "execa";
5
5
  //#region src/mcp/gateway.ts
6
6
  const PIPELINE_GATEWAY_SERVER_ID = "pipeline-gateway";
7
7
  const DEFAULT_LOCAL_GATEWAY_URL = "http://127.0.0.1:4483/mcp";
8
- const LEGACY_CODEX_MCP_RE = /\[mcp_servers\.(?!pipeline-gateway\])/;
8
+ const AUTHORIZATION_ENV_RE = /\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g;
9
+ const LEGACY_CODEX_MCP_RE = /\[mcp_servers\./;
9
10
  const LEGACY_OPENCODE_MCP_RE = /"mcp"\s*:\s*{(?!\s*"pipeline-gateway")/s;
10
11
  const LEGACY_PIPELINE_MCP_RE = /path:\s*\.mcp\.json|uvx\s+mcpm|mcpm\s+run/;
11
12
  var PipelineMcpGatewayError = class extends Error {
@@ -25,7 +26,7 @@ function gatewayServer(config, env = process.env) {
25
26
  const gateway = configuredGateway(config);
26
27
  const url = gatewayUrl(gateway, env);
27
28
  return {
28
- bearer_token_env_var: gateway.token_env,
29
+ headers: { Authorization: gatewayAuthorizationHeader(gateway) },
29
30
  url
30
31
  };
31
32
  }
@@ -36,6 +37,7 @@ function configuredGateway(config) {
36
37
  function gatewayUrl(gateway, env = process.env) {
37
38
  const url = env[gateway.url_env];
38
39
  if (url) return url;
40
+ if (gateway.url) return gateway.url;
39
41
  if (gateway.mode === "local") return DEFAULT_LOCAL_GATEWAY_URL;
40
42
  throw new PipelineMcpGatewayError(`MCP gateway URL is required. Set ${gateway.url_env}.`);
41
43
  }
@@ -44,6 +46,7 @@ function renderGatewayConfig(config) {
44
46
  return [
45
47
  `provider: ${gateway.provider}`,
46
48
  `mode: ${gateway.mode}`,
49
+ gateway.url ? `url: ${gateway.url}` : "",
47
50
  `url_env: ${gateway.url_env}`,
48
51
  `token_env: ${gateway.token_env}`,
49
52
  gateway.default_profile ? `default_profile: ${gateway.default_profile}` : "",
@@ -53,9 +56,11 @@ function renderGatewayConfig(config) {
53
56
  function renderCodexGatewayConfig(config) {
54
57
  const gateway = configuredGateway(config);
55
58
  return [
56
- "[mcp_servers.pipeline-gateway]",
57
- `url = ${JSON.stringify(gatewayUrl(gateway))}`,
58
- `bearer_token_env_var = ${JSON.stringify(gateway.token_env)}`,
59
+ "# Generated by @oisincoveney/pipeline.",
60
+ "# Codex pipeline runs receive MCP gateway config through runtime launch args.",
61
+ "# Persistent Codex MCP config is intentionally left empty to avoid startup-time MCP fan-out.",
62
+ `# gateway_url = ${JSON.stringify(gatewayUrl(gateway))}`,
63
+ `# token_env = ${JSON.stringify(gateway.token_env)}`,
59
64
  ""
60
65
  ].join("\n");
61
66
  }
@@ -65,7 +70,7 @@ function renderOpenCodeGatewayConfig(config) {
65
70
  $schema: "https://opencode.ai/config.json",
66
71
  mcp: { [PIPELINE_GATEWAY_SERVER_ID]: {
67
72
  enabled: true,
68
- headers: { Authorization: `Bearer {env:${gateway.token_env}}` },
73
+ headers: gatewayOpenCodeHeaders(gateway),
69
74
  type: "remote",
70
75
  url: gatewayUrl(gateway)
71
76
  } }
@@ -169,6 +174,17 @@ function checkGatewayToken(gateway) {
169
174
  passed: false
170
175
  };
171
176
  }
177
+ function gatewayAuthorizationHeader(gateway) {
178
+ return `Basic {env:${gateway.token_env}}`;
179
+ }
180
+ function resolveHeaderEnvPlaceholders(value, env = process.env) {
181
+ return value.replace(AUTHORIZATION_ENV_RE, (_match, envVar) => {
182
+ return env[envVar] || `{env:${envVar}}`;
183
+ });
184
+ }
185
+ function gatewayOpenCodeHeaders(gateway) {
186
+ return { Authorization: gatewayAuthorizationHeader(gateway) };
187
+ }
172
188
  async function checkThv(cwd) {
173
189
  try {
174
190
  await execa("thv", ["version"], {
@@ -238,11 +254,12 @@ async function checkGatewayHealth(gateway) {
238
254
  }
239
255
  }
240
256
  async function firstHealthyGatewayResponse(url, gateway) {
257
+ const authorization = process.env[gateway.token_env] ? `Basic ${process.env[gateway.token_env]}` : void 0;
241
258
  for (const healthUrl of gatewayHealthUrls(url)) {
242
259
  const response = await fetch(healthUrl, {
243
260
  headers: {
244
261
  Accept: "application/json, text/event-stream",
245
- ...process.env[gateway.token_env] ? { Authorization: `Bearer ${process.env[gateway.token_env]}` } : {}
262
+ ...authorization ? { Authorization: authorization } : {}
246
263
  },
247
264
  method: "GET"
248
265
  });
@@ -285,4 +302,4 @@ function legacyContentHit(cwd, path, pattern) {
285
302
  return pattern.test(readFileSync(fullPath, "utf8")) ? path : void 0;
286
303
  }
287
304
  //#endregion
288
- export { configureGatewayHosts, gatewayServerForProfile, localGatewayStatus, renderGatewayConfig, runGatewayDoctor, startLocalGateway };
305
+ export { configureGatewayHosts, gatewayServerForProfile, localGatewayStatus, renderGatewayConfig, resolveHeaderEnvPlaceholders, runGatewayDoctor, startLocalGateway };
@@ -1,4 +1,4 @@
1
- import { gatewayServerForProfile } from "./gateway.js";
1
+ import { gatewayServerForProfile, resolveHeaderEnvPlaceholders } from "./gateway.js";
2
2
  import { mkdtempSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import { tmpdir } from "node:os";
@@ -74,7 +74,7 @@ function codexMcpArgs(servers) {
74
74
  if (isRemoteMcpServer(server)) return [
75
75
  "--config",
76
76
  `mcp_servers.${id}.url=${tomlValue(server.url)}`,
77
- ...server.headers ? ["--config", `mcp_servers.${id}.http_headers=${tomlValue(server.headers)}`] : [],
77
+ ...server.headers ? ["--config", `mcp_servers.${id}.http_headers=${tomlValue(codexHttpHeaders(server.headers))}`] : [],
78
78
  ...server.bearer_token_env_var ? ["--config", `mcp_servers.${id}.bearer_token_env_var=${tomlValue(server.bearer_token_env_var)}`] : []
79
79
  ];
80
80
  return [
@@ -85,6 +85,9 @@ function codexMcpArgs(servers) {
85
85
  ];
86
86
  });
87
87
  }
88
+ function codexHttpHeaders(headers) {
89
+ return Object.fromEntries(Object.entries(headers).map(([key, value]) => [key, resolveHeaderEnvPlaceholders(value)]));
90
+ }
88
91
  function tomlValue(value) {
89
92
  if (Array.isArray(value)) return `[${value.map(tomlValue).join(", ")}]`;
90
93
  if (value && typeof value === "object") return `{ ${Object.entries(value).map(([key, item]) => `${tomlKey(key)} = ${tomlValue(item)}`).join(", ")} }`;
@@ -94,4 +97,4 @@ function tomlKey(key) {
94
97
  return TOML_BARE_KEY_PATTERN.test(key) ? key : JSON.stringify(key);
95
98
  }
96
99
  //#endregion
97
- export { buildMcpLaunchPlan, isRemoteMcpServer, tomlValue };
100
+ export { buildMcpLaunchPlan, tomlValue };
@@ -353,7 +353,7 @@ mcp_gateway:
353
353
  provider: toolhive
354
354
  mode: local
355
355
  url_env: PIPELINE_MCP_GATEWAY_URL
356
- token_env: PIPELINE_MCP_GATEWAY_TOKEN
356
+ token_env: MEMORY_MCP_BASIC_AUTH
357
357
  default_profile: default
358
358
 
359
359
  profiles:
@@ -150,8 +150,9 @@ MCP-enabled profiles use one gateway grant:
150
150
  mcp_gateway:
151
151
  provider: toolhive
152
152
  mode: hosted
153
+ url: https://pipeline-mcp.momokaya.ee/mcp/
153
154
  url_env: PIPELINE_MCP_GATEWAY_URL
154
- token_env: PIPELINE_MCP_GATEWAY_TOKEN
155
+ token_env: MEMORY_MCP_BASIC_AUTH
155
156
  default_profile: default
156
157
 
157
158
  profiles:
@@ -52,8 +52,9 @@ Example profile config:
52
52
  mcp_gateway:
53
53
  provider: toolhive
54
54
  mode: hosted
55
+ url: https://pipeline-mcp.momokaya.ee/mcp/
55
56
  url_env: PIPELINE_MCP_GATEWAY_URL
56
- token_env: PIPELINE_MCP_GATEWAY_TOKEN
57
+ token_env: MEMORY_MCP_BASIC_AUTH
57
58
  default_profile: default
58
59
 
59
60
  profiles:
@@ -86,4 +87,6 @@ Use an off-the-shelf aggregator when possible:
86
87
  Use `pipe mcp gateway doctor` to check required environment variables, gateway
87
88
  health, local ToolHive availability for local mode, and legacy direct MCP
88
89
  entries. Use `pipe mcp gateway configure-host` to rewrite project or global
89
- Codex/OpenCode host config to gateway-only with a backup.
90
+ host config with a backup. For Codex, this removes persistent MCP entries and
91
+ leaves gateway metadata as comments because the runtime injects the gateway
92
+ with `--config` only for pipeline-launched agents.
@@ -378,8 +378,9 @@ Use the same client config shape for hosted and local gateways:
378
378
  mcp_gateway:
379
379
  provider: toolhive
380
380
  mode: hosted
381
+ url: https://pipeline-mcp.momokaya.ee/mcp/
381
382
  url_env: PIPELINE_MCP_GATEWAY_URL
382
- token_env: PIPELINE_MCP_GATEWAY_TOKEN
383
+ token_env: MEMORY_MCP_BASIC_AUTH
383
384
  default_profile: default
384
385
  ```
385
386
 
@@ -412,12 +413,17 @@ pipe mcp gateway local-status
412
413
  pipe mcp gateway local-start
413
414
  ```
414
415
 
415
- `configure-host` rewrites Codex/OpenCode config to the gateway-only shape:
416
+ `configure-host` removes persistent Codex MCP config and records the gateway
417
+ metadata as comments. Codex pipeline runs receive the gateway through runtime
418
+ launch arguments so ordinary Codex sessions do not start pipeline MCP clients at
419
+ startup:
416
420
 
417
421
  ```toml
418
- [mcp_servers.pipeline-gateway]
419
- url = "https://gateway.example/mcp"
420
- bearer_token_env_var = "PIPELINE_MCP_GATEWAY_TOKEN"
422
+ # Generated by @oisincoveney/pipeline.
423
+ # Codex pipeline runs receive MCP gateway config through runtime launch args.
424
+ # Persistent Codex MCP config is intentionally left empty to avoid startup-time MCP fan-out.
425
+ # gateway_url = "https://gateway.example/mcp"
426
+ # token_env = "MEMORY_MCP_BASIC_AUTH"
421
427
  ```
422
428
 
423
429
  OpenCode receives:
@@ -521,8 +527,9 @@ singleton gateway server to profiles that need MCP.
521
527
  mcp_gateway:
522
528
  provider: toolhive
523
529
  mode: hosted
530
+ url: https://pipeline-mcp.momokaya.ee/mcp/
524
531
  url_env: PIPELINE_MCP_GATEWAY_URL
525
- token_env: PIPELINE_MCP_GATEWAY_TOKEN
532
+ token_env: MEMORY_MCP_BASIC_AUTH
526
533
 
527
534
  profiles:
528
535
  pipeline-router:
package/package.json CHANGED
@@ -99,7 +99,7 @@
99
99
  "prepack": "bun run build:cli"
100
100
  },
101
101
  "type": "module",
102
- "version": "1.16.0",
102
+ "version": "1.16.2",
103
103
  "description": "Config-driven multi-agent pipeline runner for repository work",
104
104
  "main": "./dist/index.js",
105
105
  "types": "./dist/index.d.ts",
@@ -1,21 +0,0 @@
1
- import { gatewayServerForProfile } from "./gateway.js";
2
- import { isRemoteMcpServer } from "./launch-plan.js";
3
- //#region src/mcp/native-config.ts
4
- function codexNativeMcpConfig(config, profile) {
5
- const mcpServers = Object.fromEntries(Object.entries(gatewayServerForProfile(config, profile)).map(([id, server]) => [id, codexNativeMcpServerConfig(server)]));
6
- return Object.keys(mcpServers).length > 0 ? { mcp_servers: mcpServers } : {};
7
- }
8
- function codexNativeMcpServerConfig(server) {
9
- if (isRemoteMcpServer(server)) return {
10
- ...server.bearer_token_env_var ? { bearer_token_env_var: server.bearer_token_env_var } : {},
11
- ...server.headers ? { http_headers: server.headers } : {},
12
- url: server.url
13
- };
14
- return {
15
- ...server.args ? { args: server.args } : {},
16
- command: server.command,
17
- ...server.env ? { env: server.env } : {}
18
- };
19
- }
20
- //#endregion
21
- export { codexNativeMcpConfig };