From 580582e9211f8fbcccbff0568fdd99b16e857a7c Mon Sep 17 00:00:00 2001 From: Augusto Salazar <34018193+Thecesar85@users.noreply.github.com> Date: Fri, 12 Jun 2026 01:44:58 -0600 Subject: [PATCH] feat(codex): add remote MCP config support --- registry/coder-labs/modules/codex/README.md | 18 +++++++ .../coder-labs/modules/codex/main.test.ts | 48 +++++++++++++++++++ registry/coder-labs/modules/codex/main.tf | 7 +++ .../modules/codex/scripts/install.sh.tftpl | 16 +++++++ 4 files changed, 89 insertions(+) diff --git a/registry/coder-labs/modules/codex/README.md b/registry/coder-labs/modules/codex/README.md index f35fcb8eb..c88a87e0c 100644 --- a/registry/coder-labs/modules/codex/README.md +++ b/registry/coder-labs/modules/codex/README.md @@ -107,9 +107,27 @@ module "codex" { args = ["-y", "@modelcontextprotocol/server-github"] type = "stdio" EOT + + mcp_config_remote_path = [ + "https://example.com/codex/mcp.toml", + "https://raw.githubusercontent.com/acme/platform/main/.codex-mcp.toml", + ] } ``` +> [!NOTE] +> Servers configured through `mcp` or `mcp_config_remote_path` are appended to Codex's user `config.toml`, making them available across every project the workspace owner opens. + +> [!NOTE] +> Remote URLs should return valid TOML snippets, for example: +> +> ```toml +> [mcp_servers.github] +> command = "npx" +> args = ["-y", "@modelcontextprotocol/server-github"] +> type = "stdio" +> ``` + ### 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. diff --git a/registry/coder-labs/modules/codex/main.test.ts b/registry/coder-labs/modules/codex/main.test.ts index 44ce9e96a..f16c6f38f 100644 --- a/registry/coder-labs/modules/codex/main.test.ts +++ b/registry/coder-labs/modules/codex/main.test.ts @@ -255,6 +255,54 @@ describe("codex", async () => { expect(resp).toContain("GitHub integration"); }); + test("remote-mcp-configs", async () => { + const successUrl = "file:///tmp/codex-remote-mcp.toml"; + const failingUrl = "file:///tmp/does-not-exist.toml"; + const remoteConfig = [ + "[mcp_servers.RemoteGitHub]", + 'command = "npx"', + 'args = ["-y", "@modelcontextprotocol/server-github"]', + 'type = "stdio"', + "", + "[mcp_servers.RemoteFilesystem]", + 'command = "npx"', + 'args = ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]', + 'type = "stdio"', + ].join("\n"); + + const { id, scripts } = await setup({ + moduleVariables: { + mcp_config_remote_path: JSON.stringify([failingUrl, successUrl]), + }, + }); + await execContainer(id, [ + "bash", + "-c", + `cat <<'EOF' > /tmp/codex-remote-mcp.toml\n${remoteConfig}\nEOF`, + ]); + await runScripts(id, scripts); + + const installLog = await readFileContainer( + id, + "/home/coder/.coder-modules/coder-labs/codex/logs/install.log", + ); + expect(installLog).toContain(failingUrl); + expect(installLog).toContain(successUrl); + expect(installLog).toContain( + `Warning: Failed to fetch MCP configuration from '${failingUrl}'`, + ); + expect(installLog).toContain( + `Appending remote MCP config from ${successUrl}`, + ); + + const configToml = await readFileContainer( + id, + "/home/coder/.codex/config.toml", + ); + expect(configToml).toContain("[mcp_servers.RemoteGitHub]"); + expect(configToml).toContain("[mcp_servers.RemoteFilesystem]"); + }); + test("minimal-default-config", async () => { const { id, scripts } = await setup(); await runScripts(id, scripts); diff --git a/registry/coder-labs/modules/codex/main.tf b/registry/coder-labs/modules/codex/main.tf index 7deb46452..b84be35c2 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 TOML MCP server configurations. When set, each config is fetched at install time and appended to Codex 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/scripts/install.sh.tftpl b/registry/coder-labs/modules/codex/scripts/install.sh.tftpl index 407a091db..6d5032d28 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='${ARG_CODEX_VERSION}' 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}' @@ -23,6 +24,7 @@ printf "codex_version: %s\n" "$${ARG_CODEX_VERSION}" printf "workdir: %s\n" "$${ARG_WORKDIR}" printf "enable_ai_gateway: %s\n" "$${ARG_ENABLE_AI_GATEWAY}" printf "install_codex: %s\n" "$${ARG_INSTALL}" +printf "mcp_config_remote_path: %s\n" "$${ARG_MCP_CONFIG_REMOTE_PATH}" printf "model_reasoning_effort: %s\n" "$${ARG_MODEL_REASONING_EFFORT}" echo "--------------------------------" @@ -127,6 +129,20 @@ 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 + printf "Fetching remote MCP config from %s\n" "$${url}" + remote_mcp=$(curl -fsSL "$${url}") || { + echo "Warning: Failed to fetch MCP configuration from '$${url}', continuing..." + continue + } + if [ -n "$${remote_mcp}" ]; then + printf "Appending remote MCP config from %s\n" "$${url}" + printf "\n%s\n" "$${remote_mcp}" >> "$${config_path}" + fi + 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"