From 853ffb1558fb52cf009d7af4aada0fcc61a92a75 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Tue, 19 May 2026 03:54:50 +0000 Subject: [PATCH 01/11] feat(codex): add mcp_config_remote_path for remote MCP server configurations --- registry/coder-labs/modules/codex/README.md | 27 +++- .../coder-labs/modules/codex/main.test.ts | 139 ++++++++++++++++++ registry/coder-labs/modules/codex/main.tf | 7 + .../coder-labs/modules/codex/main.tftest.hcl | 36 +++++ .../modules/codex/scripts/install.sh.tftpl | 18 +++ 5 files changed, 222 insertions(+), 5 deletions(-) diff --git a/registry/coder-labs/modules/codex/README.md b/registry/coder-labs/modules/codex/README.md index 08701fb1d..67554aec8 100644 --- a/registry/coder-labs/modules/codex/README.md +++ b/registry/coder-labs/modules/codex/README.md @@ -13,7 +13,7 @@ Install and configure the [Codex CLI](https://github.com/openai/codex) in your w ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" + version = "5.1.0" agent_id = coder_agent.main.id openai_api_key = var.openai_api_key } @@ -33,7 +33,7 @@ locals { module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" + version = "5.1.0" agent_id = coder_agent.main.id workdir = local.codex_workdir openai_api_key = var.openai_api_key @@ -64,7 +64,7 @@ resource "coder_app" "codex" { ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" + version = "5.1.0" agent_id = coder_agent.main.id workdir = "/home/coder/project" enable_ai_gateway = true @@ -88,7 +88,7 @@ When `enable_ai_gateway = true`, the module configures Codex to use the `aigatew ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" + version = "5.1.0" agent_id = coder_agent.main.id workdir = "/home/coder/project" openai_api_key = var.openai_api_key @@ -107,9 +107,26 @@ module "codex" { args = ["-y", "@modelcontextprotocol/server-github"] type = "stdio" EOT + + mcp_config_remote_path = [ + "https://example.com/team-mcp-servers.toml", + "https://raw.githubusercontent.com/your-org/your-repo/main/.codex/mcp.toml", + ] } ``` +> [!NOTE] +> Servers configured through `mcp` or `mcp_config_remote_path` are appended to `~/.codex/config.toml`, so they apply to every Codex session in the workspace. Each remote URL must return a body in Codex's native TOML format with one or more `[mcp_servers.]` sections; entries that don't contain a `[mcp_servers.*]` are rejected with a warning. +> +> ```toml +> [mcp_servers.my-tool] +> command = "my-tool-server" +> args = ["--port", "8080"] +> type = "stdio" +> ``` +> +> A fetch failure (network error, non-2xx response, or invalid body) logs a warning and continues with the remaining URLs. + ### Serialize a downstream `coder_script` after the install pipeline The module exposes the `scripts` output: an ordered list of `coder exp sync` names for the scripts this module creates (pre_install, install, post_install). Scripts that were not configured are absent. @@ -117,7 +134,7 @@ The module exposes the `scripts` output: an ordered list of `coder exp sync` nam ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.0.0" + version = "5.1.0" agent_id = coder_agent.main.id openai_api_key = var.openai_api_key } diff --git a/registry/coder-labs/modules/codex/main.test.ts b/registry/coder-labs/modules/codex/main.test.ts index f61807723..573643e88 100644 --- a/registry/coder-labs/modules/codex/main.test.ts +++ b/registry/coder-labs/modules/codex/main.test.ts @@ -425,6 +425,145 @@ describe("codex", async () => { expect(installLog).toContain("Installed Codex CLI"); }); + test("mcp-config-remote-path", async () => { + const remoteToml = [ + "[mcp_servers.remote-fetched]", + 'command = "remote-mcp-cmd"', + 'args = ["--from-url"]', + 'type = "stdio"', + ].join("\n"); + const projectDir = "/home/coder/project"; + const moduleDir = path.resolve(import.meta.dir); + const state = await runTerraformApply(moduleDir, { + agent_id: "foo", + workdir: projectDir, + install_codex: "false", + mcp_config_remote_path: JSON.stringify([ + "http://localhost:19999/mcp.toml", + "file:///tmp/remote-mcp.toml", + ]), + }); + const scripts = collectScripts(state); + const coderEnvVars = extractCoderEnvVars(state); + + const id = await runContainer("codercom/enterprise-node:latest"); + registerCleanup(async () => { + if (process.env["DEBUG"] === "true" || process.env["DEBUG"] === "1") { + console.log(`Not removing container ${id} in debug mode`); + return; + } + await removeContainer(id); + }); + + await execContainer(id, ["bash", "-c", `mkdir -p '${projectDir}'`]); + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/coder", + content: "#!/bin/bash\nexit 0\n", + }); + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/codex", + content: await Bun.file( + path.join(moduleDir, "testdata", "codex-mock.sh"), + ).text(), + }); + // Drop the remote TOML payload at a path the install script will fetch + // via file:// — keeps the test self-contained (no external network). + await execContainer(id, [ + "bash", + "-c", + `cat > /tmp/remote-mcp.toml <<'EOF'\n${remoteToml}\nEOF`, + ]); + + await runScripts(id, scripts, coderEnvVars); + + const installLog = await readFileContainer( + id, + "/home/coder/.coder-modules/coder-labs/codex/logs/install.log", + ); + // Both URLs were attempted. + expect(installLog).toContain("http://localhost:19999/mcp.toml"); + expect(installLog).toContain("file:///tmp/remote-mcp.toml"); + // First URL fails gracefully. + expect(installLog).toContain( + "Warning: Failed to fetch MCP configuration from 'http://localhost:19999/mcp.toml'", + ); + // Second URL succeeds. + expect(installLog).not.toContain( + "Warning: Failed to fetch MCP configuration from 'file:///tmp/remote-mcp.toml'", + ); + expect(installLog).toContain( + "Appending MCP servers from file:///tmp/remote-mcp.toml", + ); + + const configToml = await readFileContainer( + id, + "/home/coder/.codex/config.toml", + ); + expect(configToml).toContain("[mcp_servers.remote-fetched]"); + expect(configToml).toContain('command = "remote-mcp-cmd"'); + }); + + test("mcp-config-remote-path-invalid-toml", async () => { + const projectDir = "/home/coder/project"; + const moduleDir = path.resolve(import.meta.dir); + const state = await runTerraformApply(moduleDir, { + agent_id: "foo", + workdir: projectDir, + install_codex: "false", + mcp_config_remote_path: JSON.stringify(["file:///tmp/invalid-mcp.toml"]), + }); + const scripts = collectScripts(state); + const coderEnvVars = extractCoderEnvVars(state); + + const id = await runContainer("codercom/enterprise-node:latest"); + registerCleanup(async () => { + if (process.env["DEBUG"] === "true" || process.env["DEBUG"] === "1") { + console.log(`Not removing container ${id} in debug mode`); + return; + } + await removeContainer(id); + }); + + await execContainer(id, ["bash", "-c", `mkdir -p '${projectDir}'`]); + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/coder", + content: "#!/bin/bash\nexit 0\n", + }); + await writeExecutable({ + containerId: id, + filePath: "/usr/bin/codex", + content: await Bun.file( + path.join(moduleDir, "testdata", "codex-mock.sh"), + ).text(), + }); + // Fetched body has no [mcp_servers.*] section — the install script should + // reject it rather than appending random content to config.toml. + await execContainer(id, [ + "bash", + "-c", + `cat > /tmp/invalid-mcp.toml <<'EOF'\nnot_a_valid_mcp_section = true\nEOF`, + ]); + + await runScripts(id, scripts, coderEnvVars); + + const installLog = await readFileContainer( + id, + "/home/coder/.coder-modules/coder-labs/codex/logs/install.log", + ); + expect(installLog).toContain( + "Warning: Invalid MCP configuration from 'file:///tmp/invalid-mcp.toml'", + ); + + const configToml = await readFileContainer( + id, + "/home/coder/.codex/config.toml", + ); + expect(configToml).not.toContain("not_a_valid_mcp_section"); + }); + test("custom-config-drops-reasoning-effort", async () => { const baseConfig = [ 'sandbox_mode = "danger-full-access"', diff --git a/registry/coder-labs/modules/codex/main.tf b/registry/coder-labs/modules/codex/main.tf index c23129bc6..c93f48abd 100644 --- a/registry/coder-labs/modules/codex/main.tf +++ b/registry/coder-labs/modules/codex/main.tf @@ -88,6 +88,12 @@ variable "mcp" { default = "" } +variable "mcp_config_remote_path" { + type = list(string) + description = "List of URLs that return MCP server configurations in TOML format (matching Codex's native config format). Fetched at install time and appended to config.toml." + default = [] +} + variable "model_reasoning_effort" { type = string description = "The reasoning effort for the model. One of: none, minimal, low, medium, high, xhigh. See https://platform.openai.com/docs/guides/latest-model#lower-reasoning-effort" @@ -141,6 +147,7 @@ locals { ARG_WORKDIR = local.workdir != "" ? base64encode(local.workdir) : "" ARG_BASE_CONFIG_TOML = var.base_config_toml != "" ? base64encode(var.base_config_toml) : "" ARG_MCP = var.mcp != "" ? base64encode(var.mcp) : "" + ARG_MCP_CONFIG_REMOTE_PATH = base64encode(jsonencode(var.mcp_config_remote_path)) ARG_ENABLE_AI_GATEWAY = tostring(var.enable_ai_gateway) ARG_AIBRIDGE_CONFIG = var.enable_ai_gateway ? base64encode(local.aibridge_config) : "" ARG_MODEL_REASONING_EFFORT = var.model_reasoning_effort diff --git a/registry/coder-labs/modules/codex/main.tftest.hcl b/registry/coder-labs/modules/codex/main.tftest.hcl index 3bcd681ac..2595204f8 100644 --- a/registry/coder-labs/modules/codex/main.tftest.hcl +++ b/registry/coder-labs/modules/codex/main.tftest.hcl @@ -183,3 +183,39 @@ run "test_workdir_optional" { error_message = "scripts output should have install script even without workdir" } } + +run "test_mcp_config_remote_path" { + command = plan + + variables { + agent_id = "test-agent" + workdir = "/home/coder" + mcp_config_remote_path = [ + "https://example.com/mcp-one.toml", + "https://example.com/mcp-two.toml", + ] + } + + assert { + condition = length(var.mcp_config_remote_path) == 2 + error_message = "mcp_config_remote_path should accept a list of URLs" + } + + assert { + condition = strcontains(local.install_script, base64encode(jsonencode(var.mcp_config_remote_path))) + error_message = "install script should embed the base64-encoded mcp_config_remote_path JSON" + } +} + +run "test_mcp_config_remote_path_default" { + command = plan + + variables { + agent_id = "test-agent" + } + + assert { + condition = length(var.mcp_config_remote_path) == 0 + error_message = "mcp_config_remote_path should default to an empty list" + } +} diff --git a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl index 584c978b3..e0349d975 100644 --- a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl @@ -13,6 +13,7 @@ ARG_CODEX_VERSION=$(echo -n '${ARG_CODEX_VERSION}' | base64 -d) ARG_WORKDIR=$(echo -n '${ARG_WORKDIR}' | base64 -d) ARG_BASE_CONFIG_TOML=$(echo -n '${ARG_BASE_CONFIG_TOML}' | base64 -d) ARG_MCP=$(echo -n '${ARG_MCP}' | base64 -d) +ARG_MCP_CONFIG_REMOTE_PATH=$(echo -n '${ARG_MCP_CONFIG_REMOTE_PATH}' | base64 -d) ARG_ENABLE_AI_GATEWAY='${ARG_ENABLE_AI_GATEWAY}' ARG_AIBRIDGE_CONFIG=$(echo -n '${ARG_AIBRIDGE_CONFIG}' | base64 -d) ARG_MODEL_REASONING_EFFORT='${ARG_MODEL_REASONING_EFFORT}' @@ -24,6 +25,7 @@ printf "workdir: %s\n" "$${ARG_WORKDIR}" printf "enable_ai_gateway: %s\n" "$${ARG_ENABLE_AI_GATEWAY}" printf "install_codex: %s\n" "$${ARG_INSTALL}" printf "model_reasoning_effort: %s\n" "$${ARG_MODEL_REASONING_EFFORT}" +printf "mcp_config_remote_path: %s\n" "$${ARG_MCP_CONFIG_REMOTE_PATH}" echo "--------------------------------" function add_path_to_shell_profiles() { @@ -155,6 +157,22 @@ function populate_config_toml() { echo "$${ARG_MCP}" >> "$${config_path}" fi + if [ -n "$${ARG_MCP_CONFIG_REMOTE_PATH}" ] && [ "$${ARG_MCP_CONFIG_REMOTE_PATH}" != "[]" ]; then + for url in $(echo "$${ARG_MCP_CONFIG_REMOTE_PATH}" | jq -r '.[]'); do + echo "Fetching MCP configuration from: $${url}" + mcp_toml=$(curl -fsSL "$${url}") || { + echo "Warning: Failed to fetch MCP configuration from '$${url}', continuing..." + continue + } + if ! echo "$${mcp_toml}" | grep -qE '^\[mcp_servers\.'; then + echo "Warning: Invalid MCP configuration from '$${url}' (missing [mcp_servers.*] section), continuing..." + continue + fi + printf "Appending MCP servers from %s\n" "$${url}" + printf '\n%s\n' "$${mcp_toml}" >> "$${config_path}" + done + fi + if [ "$${ARG_ENABLE_AI_GATEWAY}" = "true" ] && [ -n "$${ARG_AIBRIDGE_CONFIG}" ]; then if ! grep -q '\[model_providers\.aigateway\]' "$${config_path}" 2>/dev/null; then printf "Adding AI Gateway configuration\n" From b27dabe83f4a7e0dbcb825facdf2c43d3aff06b2 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Tue, 19 May 2026 04:46:05 +0000 Subject: [PATCH 02/11] fix(install): improve regex to handle whitespace in MCP configuration check --- registry/coder-labs/modules/codex/scripts/install.sh.tftpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl index e0349d975..a99880fdc 100644 --- a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl @@ -164,7 +164,7 @@ function populate_config_toml() { echo "Warning: Failed to fetch MCP configuration from '$${url}', continuing..." continue } - if ! echo "$${mcp_toml}" | grep -qE '^\[mcp_servers\.'; then + if ! echo "$${mcp_toml}" | grep -qE '^[[:space:]]*\[mcp_servers\.'; then echo "Warning: Invalid MCP configuration from '$${url}' (missing [mcp_servers.*] section), continuing..." continue fi From af2cc72e44575f5f566ad871dc37a55f2a9a1971 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Tue, 19 May 2026 04:55:46 +0000 Subject: [PATCH 03/11] fix(docs): clarify MCP configuration handling and fetch failure logging --- registry/coder-labs/modules/codex/README.md | 4 +- .../coder-labs/modules/codex/main.test.ts | 59 ------------------- .../modules/codex/scripts/install.sh.tftpl | 4 -- 3 files changed, 2 insertions(+), 65 deletions(-) diff --git a/registry/coder-labs/modules/codex/README.md b/registry/coder-labs/modules/codex/README.md index 67554aec8..b43c6dd2d 100644 --- a/registry/coder-labs/modules/codex/README.md +++ b/registry/coder-labs/modules/codex/README.md @@ -116,7 +116,7 @@ module "codex" { ``` > [!NOTE] -> Servers configured through `mcp` or `mcp_config_remote_path` are appended to `~/.codex/config.toml`, so they apply to every Codex session in the workspace. Each remote URL must return a body in Codex's native TOML format with one or more `[mcp_servers.]` sections; entries that don't contain a `[mcp_servers.*]` are rejected with a warning. +> Servers configured through `mcp` or `mcp_config_remote_path` are appended to `~/.codex/config.toml`, so they apply to every Codex session in the workspace. Each remote URL should return a body in Codex's native TOML format, e.g.: > > ```toml > [mcp_servers.my-tool] @@ -125,7 +125,7 @@ module "codex" { > type = "stdio" > ``` > -> A fetch failure (network error, non-2xx response, or invalid body) logs a warning and continues with the remaining URLs. +> Fetch failures (network errors or non-2xx responses) log a warning and the install continues with the remaining URLs. Bodies are appended verbatim without further validation, so make sure the URL returns valid Codex TOML. ### Serialize a downstream `coder_script` after the install pipeline diff --git a/registry/coder-labs/modules/codex/main.test.ts b/registry/coder-labs/modules/codex/main.test.ts index 573643e88..7125efbe9 100644 --- a/registry/coder-labs/modules/codex/main.test.ts +++ b/registry/coder-labs/modules/codex/main.test.ts @@ -505,65 +505,6 @@ describe("codex", async () => { expect(configToml).toContain('command = "remote-mcp-cmd"'); }); - test("mcp-config-remote-path-invalid-toml", async () => { - const projectDir = "/home/coder/project"; - const moduleDir = path.resolve(import.meta.dir); - const state = await runTerraformApply(moduleDir, { - agent_id: "foo", - workdir: projectDir, - install_codex: "false", - mcp_config_remote_path: JSON.stringify(["file:///tmp/invalid-mcp.toml"]), - }); - const scripts = collectScripts(state); - const coderEnvVars = extractCoderEnvVars(state); - - const id = await runContainer("codercom/enterprise-node:latest"); - registerCleanup(async () => { - if (process.env["DEBUG"] === "true" || process.env["DEBUG"] === "1") { - console.log(`Not removing container ${id} in debug mode`); - return; - } - await removeContainer(id); - }); - - await execContainer(id, ["bash", "-c", `mkdir -p '${projectDir}'`]); - await writeExecutable({ - containerId: id, - filePath: "/usr/bin/coder", - content: "#!/bin/bash\nexit 0\n", - }); - await writeExecutable({ - containerId: id, - filePath: "/usr/bin/codex", - content: await Bun.file( - path.join(moduleDir, "testdata", "codex-mock.sh"), - ).text(), - }); - // Fetched body has no [mcp_servers.*] section — the install script should - // reject it rather than appending random content to config.toml. - await execContainer(id, [ - "bash", - "-c", - `cat > /tmp/invalid-mcp.toml <<'EOF'\nnot_a_valid_mcp_section = true\nEOF`, - ]); - - await runScripts(id, scripts, coderEnvVars); - - const installLog = await readFileContainer( - id, - "/home/coder/.coder-modules/coder-labs/codex/logs/install.log", - ); - expect(installLog).toContain( - "Warning: Invalid MCP configuration from 'file:///tmp/invalid-mcp.toml'", - ); - - const configToml = await readFileContainer( - id, - "/home/coder/.codex/config.toml", - ); - expect(configToml).not.toContain("not_a_valid_mcp_section"); - }); - test("custom-config-drops-reasoning-effort", async () => { const baseConfig = [ 'sandbox_mode = "danger-full-access"', diff --git a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl index a99880fdc..1920beff5 100644 --- a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl @@ -164,10 +164,6 @@ function populate_config_toml() { echo "Warning: Failed to fetch MCP configuration from '$${url}', continuing..." continue } - if ! echo "$${mcp_toml}" | grep -qE '^[[:space:]]*\[mcp_servers\.'; then - echo "Warning: Invalid MCP configuration from '$${url}' (missing [mcp_servers.*] section), continuing..." - continue - fi printf "Appending MCP servers from %s\n" "$${url}" printf '\n%s\n' "$${mcp_toml}" >> "$${config_path}" done From 594256f961c9079247bcf26b25dd8e5c554f3596 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Tue, 19 May 2026 10:50:19 +0000 Subject: [PATCH 04/11] fix(coder-labs/modules/codex): address review findings from Netero - Guard jq usage with command -v check; emit a clear error and skip the remote MCP fetch when jq is not available (DEREM-1) - Remove vacuous length assertion in tftest that tested its own input, not the module behavior (DEREM-2) - Replace em-dash with period in test comment (DEREM-3) --- registry/coder-labs/modules/codex/main.test.ts | 2 +- registry/coder-labs/modules/codex/main.tftest.hcl | 5 ----- registry/coder-labs/modules/codex/scripts/install.sh.tftpl | 4 ++++ 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/registry/coder-labs/modules/codex/main.test.ts b/registry/coder-labs/modules/codex/main.test.ts index 7125efbe9..72f9b88b7 100644 --- a/registry/coder-labs/modules/codex/main.test.ts +++ b/registry/coder-labs/modules/codex/main.test.ts @@ -469,7 +469,7 @@ describe("codex", async () => { ).text(), }); // Drop the remote TOML payload at a path the install script will fetch - // via file:// — keeps the test self-contained (no external network). + // via file://. Keeps the test self-contained (no external network). await execContainer(id, [ "bash", "-c", diff --git a/registry/coder-labs/modules/codex/main.tftest.hcl b/registry/coder-labs/modules/codex/main.tftest.hcl index 2595204f8..c4c7403f3 100644 --- a/registry/coder-labs/modules/codex/main.tftest.hcl +++ b/registry/coder-labs/modules/codex/main.tftest.hcl @@ -196,11 +196,6 @@ run "test_mcp_config_remote_path" { ] } - assert { - condition = length(var.mcp_config_remote_path) == 2 - error_message = "mcp_config_remote_path should accept a list of URLs" - } - assert { condition = strcontains(local.install_script, base64encode(jsonencode(var.mcp_config_remote_path))) error_message = "install script should embed the base64-encoded mcp_config_remote_path JSON" diff --git a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl index 1920beff5..f7e63a295 100644 --- a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl @@ -158,6 +158,9 @@ function populate_config_toml() { fi if [ -n "$${ARG_MCP_CONFIG_REMOTE_PATH}" ] && [ "$${ARG_MCP_CONFIG_REMOTE_PATH}" != "[]" ]; then + if ! command -v jq > /dev/null 2>&1; then + printf "Error: 'jq' is required to process mcp_config_remote_path but was not found. Skipping remote MCP config fetch.\n" >&2 + else for url in $(echo "$${ARG_MCP_CONFIG_REMOTE_PATH}" | jq -r '.[]'); do echo "Fetching MCP configuration from: $${url}" mcp_toml=$(curl -fsSL "$${url}") || { @@ -167,6 +170,7 @@ function populate_config_toml() { printf "Appending MCP servers from %s\n" "$${url}" printf '\n%s\n' "$${mcp_toml}" >> "$${config_path}" done + fi fi if [ "$${ARG_ENABLE_AI_GATEWAY}" = "true" ] && [ -n "$${ARG_AIBRIDGE_CONFIG}" ]; then From 3d31e31b0cb13b7001c36fbc8327773dfacfb9f1 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 26 Jun 2026 15:55:52 +0000 Subject: [PATCH 05/11] fix(coder-labs/modules/codex): append remote MCP config to managed block instead of config_path --- registry/coder-labs/modules/codex/scripts/install.sh.tftpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl index 930336c89..ac537259c 100644 --- a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl @@ -179,7 +179,7 @@ function populate_config_toml() { continue } printf "Appending MCP servers from %s\n" "$${url}" - printf '\n%s\n' "$${mcp_toml}" >> "$${config_path}" + printf '\n%s\n' "$${mcp_toml}" >> "$${managed}" done fi fi From bf7546527938c6743fd36746f08fad6e2b96978d Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 26 Jun 2026 16:03:10 +0000 Subject: [PATCH 06/11] refactor(coder-labs/modules/codex): use setup() helper in mcp-config-remote-path test --- .../coder-labs/modules/codex/main.test.ts | 42 ++++--------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/registry/coder-labs/modules/codex/main.test.ts b/registry/coder-labs/modules/codex/main.test.ts index bc87bd64f..982964dbf 100644 --- a/registry/coder-labs/modules/codex/main.test.ts +++ b/registry/coder-labs/modules/codex/main.test.ts @@ -439,41 +439,13 @@ describe("codex", async () => { 'args = ["--from-url"]', 'type = "stdio"', ].join("\n"); - const projectDir = "/home/coder/project"; - const moduleDir = path.resolve(import.meta.dir); - const state = await runTerraformApply(moduleDir, { - agent_id: "foo", - workdir: projectDir, - install_codex: "false", - mcp_config_remote_path: JSON.stringify([ - "http://localhost:19999/mcp.toml", - "file:///tmp/remote-mcp.toml", - ]), - }); - const scripts = collectScripts(state); - const coderEnvVars = extractCoderEnvVars(state); - - const id = await runContainer("codercom/enterprise-node:latest"); - registerCleanup(async () => { - if (process.env["DEBUG"] === "true" || process.env["DEBUG"] === "1") { - console.log(`Not removing container ${id} in debug mode`); - return; - } - await removeContainer(id); - }); - - await execContainer(id, ["bash", "-c", `mkdir -p '${projectDir}'`]); - await writeExecutable({ - containerId: id, - filePath: "/usr/bin/coder", - content: "#!/bin/bash\nexit 0\n", - }); - await writeExecutable({ - containerId: id, - filePath: "/usr/bin/codex", - content: await Bun.file( - path.join(moduleDir, "testdata", "codex-mock.sh"), - ).text(), + const { id, coderEnvVars, scripts } = await setup({ + moduleVariables: { + mcp_config_remote_path: JSON.stringify([ + "http://localhost:19999/mcp.toml", + "file:///tmp/remote-mcp.toml", + ]), + }, }); // Drop the remote TOML payload at a path the install script will fetch // via file://. Keeps the test self-contained (no external network). From b75bd838a343e5a045d08dfba3470e4cda811e72 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 26 Jun 2026 16:08:19 +0000 Subject: [PATCH 07/11] fix(coder-labs/modules/codex): exit 1 when jq is missing and mcp_config_remote_path is configured --- registry/coder-labs/modules/codex/scripts/install.sh.tftpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl index ac537259c..1f2466ec7 100644 --- a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl @@ -170,8 +170,9 @@ function populate_config_toml() { if [ -n "$${ARG_MCP_CONFIG_REMOTE_PATH}" ] && [ "$${ARG_MCP_CONFIG_REMOTE_PATH}" != "[]" ]; then if ! command -v jq > /dev/null 2>&1; then - printf "Error: 'jq' is required to process mcp_config_remote_path but was not found. Skipping remote MCP config fetch.\n" >&2 - else + printf "Error: 'jq' is required to process mcp_config_remote_path but was not found.\n" >&2 + return 1 + fi for url in $(echo "$${ARG_MCP_CONFIG_REMOTE_PATH}" | jq -r '.[]'); do echo "Fetching MCP configuration from: $${url}" mcp_toml=$(curl -fsSL "$${url}") || { @@ -181,7 +182,6 @@ function populate_config_toml() { printf "Appending MCP servers from %s\n" "$${url}" printf '\n%s\n' "$${mcp_toml}" >> "$${managed}" done - fi fi if [ "$${ARG_ENABLE_AI_GATEWAY}" = "true" ] && [ -n "$${ARG_AIBRIDGE_CONFIG}" ]; then From 209b092a3ec5fb5e3a57430e7c94a5a6cf842168 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 26 Jun 2026 16:39:07 +0000 Subject: [PATCH 08/11] fix(coder-labs/modules/codex): validate remote MCP config for managed-block markers before appending --- .../coder-labs/modules/codex/main.test.ts | 38 +++++++++++++++++++ .../modules/codex/scripts/install.sh.tftpl | 4 ++ 2 files changed, 42 insertions(+) diff --git a/registry/coder-labs/modules/codex/main.test.ts b/registry/coder-labs/modules/codex/main.test.ts index 982964dbf..e37c18e5d 100644 --- a/registry/coder-labs/modules/codex/main.test.ts +++ b/registry/coder-labs/modules/codex/main.test.ts @@ -484,6 +484,44 @@ describe("codex", async () => { expect(configToml).toContain('command = "remote-mcp-cmd"'); }); + test("mcp-config-remote-path-rejects-managed-markers", async () => { + const poisonedToml = [ + "# >>> coder-managed: codex module >>>", + "[mcp_servers.evil]", + 'command = "evil-cmd"', + 'type = "stdio"', + ].join("\n"); + const { id, coderEnvVars, scripts } = await setup({ + moduleVariables: { + mcp_config_remote_path: JSON.stringify([ + "file:///tmp/poisoned-mcp.toml", + ]), + }, + }); + await execContainer(id, [ + "bash", + "-c", + `cat > /tmp/poisoned-mcp.toml <<'EOF'\n${poisonedToml}\nEOF`, + ]); + + await runScripts(id, scripts, coderEnvVars); + + const installLog = await readFileContainer( + id, + "/home/coder/.coder-modules/coder-labs/codex/logs/install.log", + ); + expect(installLog).toContain( + "contains managed-block markers, skipping", + ); + + const configToml = await readFileContainer( + id, + "/home/coder/.codex/config.toml", + ); + expect(configToml).not.toContain("[mcp_servers.evil]"); + expect(configToml).not.toContain('command = "evil-cmd"'); + }); + test("base-config-plus-mcp-combined", async () => { const baseConfig = [ 'sandbox_mode = "danger-full-access"', diff --git a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl index 1f2466ec7..90ec8c74e 100644 --- a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl @@ -179,6 +179,10 @@ function populate_config_toml() { echo "Warning: Failed to fetch MCP configuration from '$${url}', continuing..." continue } + if printf '%s' "$${mcp_toml}" | grep -qF -e "$${MANAGED_START}" -e "$${MANAGED_END}"; then + echo "Warning: Remote MCP configuration from '$${url}' contains managed-block markers, skipping..." + continue + fi printf "Appending MCP servers from %s\n" "$${url}" printf '\n%s\n' "$${mcp_toml}" >> "$${managed}" done From 5951dc07492bbd343f8b1ef29b444cacb190182d Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 26 Jun 2026 16:39:54 +0000 Subject: [PATCH 09/11] fix(coder-labs/modules/codex): safe URL iteration, curl timeouts, and user-facing error message --- registry/coder-labs/modules/codex/scripts/install.sh.tftpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl index 90ec8c74e..05c3d94af 100644 --- a/registry/coder-labs/modules/codex/scripts/install.sh.tftpl +++ b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl @@ -170,12 +170,12 @@ function populate_config_toml() { if [ -n "$${ARG_MCP_CONFIG_REMOTE_PATH}" ] && [ "$${ARG_MCP_CONFIG_REMOTE_PATH}" != "[]" ]; then if ! command -v jq > /dev/null 2>&1; then - printf "Error: 'jq' is required to process mcp_config_remote_path but was not found.\n" >&2 + printf "Error: 'jq' is required to fetch remote MCP configurations but was not found.\n" >&2 return 1 fi - for url in $(echo "$${ARG_MCP_CONFIG_REMOTE_PATH}" | jq -r '.[]'); do + echo "$${ARG_MCP_CONFIG_REMOTE_PATH}" | jq -r '.[]' | while IFS= read -r url; do echo "Fetching MCP configuration from: $${url}" - mcp_toml=$(curl -fsSL "$${url}") || { + mcp_toml=$(curl -fsSL --connect-timeout 10 --max-time 30 "$${url}") || { echo "Warning: Failed to fetch MCP configuration from '$${url}', continuing..." continue } From 406c9bb0e2ad3c19e4dfa35a4e55fa55381b4fe4 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 26 Jun 2026 16:50:06 +0000 Subject: [PATCH 10/11] style(coder-labs/modules/codex): fix prettier formatting --- registry/coder-labs/modules/codex/main.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/registry/coder-labs/modules/codex/main.test.ts b/registry/coder-labs/modules/codex/main.test.ts index e37c18e5d..5f86e0cd6 100644 --- a/registry/coder-labs/modules/codex/main.test.ts +++ b/registry/coder-labs/modules/codex/main.test.ts @@ -510,9 +510,7 @@ describe("codex", async () => { id, "/home/coder/.coder-modules/coder-labs/codex/logs/install.log", ); - expect(installLog).toContain( - "contains managed-block markers, skipping", - ); + expect(installLog).toContain("contains managed-block markers, skipping"); const configToml = await readFileContainer( id, From 575c670eaeae82e6e0503969f8fd70be0016860a Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Fri, 26 Jun 2026 17:23:12 +0000 Subject: [PATCH 11/11] chore(coder-labs/modules/codex): bump version to 5.3.0 --- registry/coder-labs/modules/codex/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/registry/coder-labs/modules/codex/README.md b/registry/coder-labs/modules/codex/README.md index b5a46ef7f..8de20cb82 100644 --- a/registry/coder-labs/modules/codex/README.md +++ b/registry/coder-labs/modules/codex/README.md @@ -13,7 +13,7 @@ Install and configure the [Codex CLI](https://github.com/openai/codex) in your w ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.2.0" + version = "5.3.0" agent_id = coder_agent.main.id openai_api_key = var.openai_api_key } @@ -33,7 +33,7 @@ locals { module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.2.0" + version = "5.3.0" agent_id = coder_agent.main.id workdir = local.codex_workdir openai_api_key = var.openai_api_key @@ -64,7 +64,7 @@ resource "coder_app" "codex" { ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.2.0" + version = "5.3.0" agent_id = coder_agent.main.id workdir = "/home/coder/project" enable_ai_gateway = true @@ -88,7 +88,7 @@ When `enable_ai_gateway = true`, the module configures Codex to use the `aigatew ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.2.0" + version = "5.3.0" agent_id = coder_agent.main.id workdir = "/home/coder/project" openai_api_key = var.openai_api_key @@ -134,7 +134,7 @@ The module exposes the `scripts` output: an ordered list of `coder exp sync` nam ```tf module "codex" { source = "registry.coder.com/coder-labs/codex/coder" - version = "5.2.0" + version = "5.3.0" agent_id = coder_agent.main.id openai_api_key = var.openai_api_key }