From 853ffb1558fb52cf009d7af4aada0fcc61a92a75 Mon Sep 17 00:00:00 2001 From: 35C4n0r Date: Tue, 19 May 2026 03:54:50 +0000 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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