From ebc55fd103473bf559587818c51483dd21a1e508 Mon Sep 17 00:00:00 2001 From: Malik Date: Fri, 29 May 2026 20:13:28 +0200 Subject: [PATCH] claude-code, codex, antigravity-cli, github-copilot-cli, opencode, vscode, zed-editor: use mcp lib - use lib.hm.mcp.transformMcpServer for MCP integration - Update tests to reflect new merged server configuration --- modules/programs/antigravity-cli.nix | 45 +++++++++++++-- modules/programs/claude-code.nix | 24 ++++---- modules/programs/codex.nix | 29 +++++----- modules/programs/github-copilot-cli.nix | 55 +++++++++++++------ modules/programs/opencode.nix | 45 ++++++++------- modules/programs/vscode/mkVscodeModule.nix | 34 +++--------- modules/programs/zed-editor.nix | 20 ++++--- .../codex/mcp-integration-with-override.toml | 1 - .../programs/codex/mcp-integration.toml | 2 - .../expected-mcp-integration-config.json | 1 - .../mcp-integration-with-override.json | 1 - .../programs/opencode/mcp-integration.json | 5 +- .../programs/opencode/mcp-integration.nix | 3 +- .../vscode/mcp-integration-default.json | 2 - .../vscode/mcp-integration-with-override.json | 1 - .../mcp-integration-with-override.nix | 5 +- .../programs/zed-editor/mcp-integration.json | 7 ++- .../programs/zed-editor/mcp-integration.nix | 7 ++- 18 files changed, 168 insertions(+), 119 deletions(-) diff --git a/modules/programs/antigravity-cli.nix b/modules/programs/antigravity-cli.nix index 8abbd5933c..73df537bb3 100644 --- a/modules/programs/antigravity-cli.nix +++ b/modules/programs/antigravity-cli.nix @@ -407,9 +407,21 @@ in ) cfg.context; } (lib.mkIf useGeminiConfig { - programs.antigravity-cli.settings.mcpServers = lib.mkIf ( - cfg.enableMcpIntegration && config.programs.mcp.enable - ) (lib.mapAttrs (_n: lib.mkDefault) config.programs.mcp.servers); + programs.antigravity-cli.settings.mcpServers = + lib.mkIf (cfg.enableMcpIntegration && config.programs.mcp.enable) + ( + lib.mapAttrs ( + name: server: + lib.hm.mcp.transformMcpServer { + inherit server; + extraTransforms = [ + lib.hm.mcp.addType + (lib.hm.mcp.wrapEnvFilesCommand { inherit pkgs name; }) + (s: lib.mapAttrs (_: lib.mkDefault) s) + ]; + } + ) config.programs.mcp.servers + ); home.file = lib.optionalAttrs (geminiSettings != { }) { @@ -456,7 +468,32 @@ in (lib.mkIf (!useGeminiConfig) { programs.antigravity-cli.mcpServers = lib.mkIf (cfg.enableMcpIntegration && config.programs.mcp.enable) - (lib.mapAttrs (_n: server: lib.mkDefault (transformMcpServer server)) config.programs.mcp.servers); + ( + lib.mapAttrs ( + name: server: + lib.hm.mcp.transformMcpServer { + inherit server; + extraTransforms = [ + ( + s: + removeAttrs s [ + "httpUrl" + "url" + ] + // lib.optionalAttrs (s ? httpUrl) { + serverUrl = s.httpUrl; + } + // lib.optionalAttrs (s ? url) { + serverUrl = s.url; + } + ) + lib.hm.mcp.addType + (lib.hm.mcp.wrapEnvFilesCommand { inherit pkgs name; }) + (s: lib.mapAttrs (_: lib.mkDefault) s) + ]; + } + ) config.programs.mcp.servers + ); assertions = [ { diff --git a/modules/programs/claude-code.nix b/modules/programs/claude-code.nix index 85848846b7..38d54bb8ea 100644 --- a/modules/programs/claude-code.nix +++ b/modules/programs/claude-code.nix @@ -19,17 +19,17 @@ let upstreamConfigDir = "${config.home.homeDirectory}/.claude"; - mkMcpServer = - server: - (removeAttrs server [ "disabled" ]) - // (optionalAttrs (server ? url) { type = "http"; }) - // (optionalAttrs (server ? command) { type = "stdio"; }) - // { - enabled = !(server.disabled or false); - }; - - transformedMcpServers = optionalAttrs (cfg.enableMcpIntegration && config.programs.mcp.enable) ( - lib.mapAttrs (_name: mkMcpServer) config.programs.mcp.servers + transformedMcpServers = lib.optionalAttrs (cfg.enableMcpIntegration && config.programs.mcp.enable) ( + lib.mapAttrs ( + name: server: + lib.hm.mcp.transformMcpServer { + inherit server; + extraTransforms = [ + lib.hm.mcp.addType + (lib.hm.mcp.wrapEnvFilesCommand { inherit pkgs name; }) # envFiles currently still need wrapping https://github.com/anthropics/claude-code/issues/28942 + ]; + } + ) config.programs.mcp.servers ); mkContentOption = @@ -632,7 +632,7 @@ in programs.claude-code.finalPackage = let - mergedMcpServers = transformedMcpServers // cfg.mcpServers; + mergedMcpServers = transformedMcpServers // lib.mapAttrs (_: lib.hm.mcp.addType) cfg.mcpServers; pluginFiles = lib.optional (mergedMcpServers != { }) { name = ".mcp.json"; diff --git a/modules/programs/codex.nix b/modules/programs/codex.nix index fff046ceea..a81863c69b 100644 --- a/modules/programs/codex.nix +++ b/modules/programs/codex.nix @@ -17,8 +17,8 @@ let settingsFormat = if isTomlConfig then tomlFormat else yamlFormat; in { - meta.maintainers = [ - lib.maintainers.delafthi + meta.maintainers = with lib.maintainers; [ + delafthi ]; imports = [ @@ -221,21 +221,22 @@ in transformedMcpServers = lib.optionalAttrs (cfg.enableMcpIntegration && config.programs.mcp.enable) ( lib.mapAttrs ( - _name: server: + name: server: # NOTE: Convert shared programs.mcp fields to Codex config keys: - # - removeAttrs drops keys that Codex does not use directly - # - "disabled" becomes inverse "enabled" + # - file-backed env entries are wrapped in a shell script that sets environment variables before exec # - "headers" is renamed to "http_headers" # See: https://developers.openai.com/codex/mcp#other-configuration-options - (lib.removeAttrs server [ - "disabled" - "headers" - ]) - // (lib.optionalAttrs (server ? headers && !(server ? http_headers)) { - http_headers = server.headers; - }) - // { - enabled = !(server.disabled or false); + lib.hm.mcp.transformMcpServer { + inherit server; + exclude = [ + "headers" + "type" + ]; + extraTransforms = [ + (s: s // lib.optionalAttrs (s.headers or { } != { }) { http_headers = s.headers; }) + lib.hm.mcp.addType + (lib.hm.mcp.wrapEnvFilesCommand { inherit pkgs name; }) + ]; } ) config.programs.mcp.servers ); diff --git a/modules/programs/github-copilot-cli.nix b/modules/programs/github-copilot-cli.nix index 04228aa5cd..a529c38556 100644 --- a/modules/programs/github-copilot-cli.nix +++ b/modules/programs/github-copilot-cli.nix @@ -19,34 +19,57 @@ let upstreamConfigDir = "${config.home.homeDirectory}/.copilot"; - transformSingleServer = - _name: server: + forCopilotFormat = + server: let - server' = removeAttrs server [ "disabled" ]; - type = server'.type or (if server' ? url then "http" else "local"); + isLocal = server.type == "stdio"; in - server' + server // { - inherit type; + type = if server.type == "stdio" then "local" else server.type or "local"; } - // lib.optionalAttrs (type == "local") { - args = server'.args or [ ]; + // lib.optionalAttrs isLocal { + args = server.args or [ ]; } - // lib.optionalAttrs (!(server' ? tools)) { + // lib.optionalAttrs (!(server ? tools)) { tools = [ "*" ]; }; + enabledServers = lib.filterAttrs ( + _: server: !(server.disabled or false) && (server ? url || server ? command) + ) config.programs.mcp.servers; + transformedMcpServers = - if cfg.enableMcpIntegration && config.programs.mcp.enable && config.programs.mcp.servers != { } then - lib.mapAttrs transformSingleServer ( - lib.filterAttrs ( - _: server: !(server.disabled or false) && (server ? url || server ? command) - ) config.programs.mcp.servers - ) + if cfg.enableMcpIntegration && config.programs.mcp.enable && enabledServers != { } then + lib.mapAttrs ( + name: server: + lib.hm.mcp.transformMcpServer { + inherit server; + extraTransforms = [ + lib.hm.mcp.addType + (lib.hm.mcp.wrapEnvFilesCommand { inherit pkgs name; }) + forCopilotFormat + ]; + } + ) enabledServers else { }; - mergedMcpServers = transformedMcpServers // cfg.mcpServers; + mergedMcpServers = + transformedMcpServers + // lib.mapAttrs ( + name: server: + lib.hm.mcp.transformMcpServer { + inherit server; + extraTransforms = [ + lib.hm.mcp.addType + (lib.hm.mcp.wrapEnvFilesCommand { + inherit pkgs name; + }) + forCopilotFormat + ]; + } + ) cfg.mcpServers; in { meta.maintainers = [ lib.maintainers.ojsef39 ]; diff --git a/modules/programs/opencode.nix b/modules/programs/opencode.nix index 0461f37433..224c82540f 100644 --- a/modules/programs/opencode.nix +++ b/modules/programs/opencode.nix @@ -18,32 +18,39 @@ let jsonFormat = pkgs.formats.json { }; - transformMcpServer = name: server: { - inherit name; - value = { - enabled = !(server.disabled or false); + toOpencodeShape = + s: + let + isRemote = s ? url && s.url != null; + renderedEnv = lib.hm.mcp.renderEnv (p: "{file:${p}}") (s.env or { }); + in + lib.optionalAttrs (s.enabled or null != null) { inherit (s) enabled; } + // { + type = if isRemote then "remote" else "local"; } // ( - if server ? url then - { - type = "remote"; - inherit (server) url; - } - // (lib.optionalAttrs (server ? headers) { inherit (server) headers; }) - else if server ? command then - { - type = "local"; - command = [ server.command ] ++ (server.args or [ ]); - } - // (lib.optionalAttrs (server ? env) { environment = server.env; }) + if isRemote then + { inherit (s) url; } // lib.optionalAttrs (s.headers or { } != { }) { inherit (s) headers; } else - { } + { + command = [ s.command ] ++ (s.args or [ ]); + } + // lib.optionalAttrs (renderedEnv != { }) { environment = renderedEnv; } ); - }; transformedMcpServers = if cfg.enableMcpIntegration && config.programs.mcp.enable && config.programs.mcp.servers != { } then - lib.listToAttrs (lib.mapAttrsToList transformMcpServer config.programs.mcp.servers) + lib.mapAttrs ( + _: server: + lib.hm.mcp.transformMcpServer { + inherit server; + extraTransforms = [ toOpencodeShape ]; + exclude = [ + "args" + "env" + ]; + } + ) config.programs.mcp.servers else { }; diff --git a/modules/programs/vscode/mkVscodeModule.nix b/modules/programs/vscode/mkVscodeModule.nix index c3cbb2b5a9..c42d261d0c 100644 --- a/modules/programs/vscode/mkVscodeModule.nix +++ b/modules/programs/vscode/mkVscodeModule.nix @@ -70,32 +70,16 @@ let isPath = p: builtins.isPath p || lib.isStorePath p; - transformMcpServerForVscode = - name: server: - let - # Remove the disabled field from the server config - cleanServer = lib.filterAttrs (n: _v: n != "disabled") server; - in - { - inherit name; - value = { - enabled = !(server.disabled or false); - } - // ( - if server ? url then - { - type = "http"; - } - // cleanServer - else if server ? command then - { - type = "stdio"; - } - // cleanServer - else - { } - ); + transformMcpServerForVscode = name: server: { + inherit name; + value = lib.hm.mcp.transformMcpServer { + inherit server; + extraTransforms = [ + lib.hm.mcp.addType + (lib.hm.mcp.wrapEnvFilesCommand { inherit pkgs name; }) + ]; }; + }; profileType = types.submodule { options = { diff --git a/modules/programs/zed-editor.nix b/modules/programs/zed-editor.nix index f1ab4d6e54..47f74313b4 100644 --- a/modules/programs/zed-editor.nix +++ b/modules/programs/zed-editor.nix @@ -31,19 +31,21 @@ let transformedMcpServers = lib.optionalAttrs (cfg.enableMcpIntegration && config.programs.mcp.enable) ( lib.mapAttrs ( - _name: server: - # NOTE: Convert shared programs.mcp fields to Zed config keys: - # - removeAttrs drops keys that Zed does not use directly - # - "disabled" becomes inverse "enabled" - # See: https://zed.dev/docs/ai/mcp - (lib.removeAttrs server [ "disabled" ]) - // { - enabled = !(server.disabled or false); + name: server: + # See: https://zed.dev/docs/ai/mcp & https://github.com/zed-industries/zed/discussions/53780 + lib.hm.mcp.transformMcpServer { + inherit server; + extraTransforms = [ + lib.hm.mcp.addType + (lib.hm.mcp.wrapEnvFilesCommand { inherit pkgs name; }) + ]; } ) config.programs.mcp.servers ); - settingMcpServers = lib.attrByPath [ "context_servers" ] { } cfg.userSettings; + settingMcpServers = lib.mapAttrs (_: lib.hm.mcp.addType) ( + lib.attrByPath [ "context_servers" ] { } cfg.userSettings + ); mergedMcpServers = transformedMcpServers // settingMcpServers; mergedSettings = diff --git a/tests/modules/programs/codex/mcp-integration-with-override.toml b/tests/modules/programs/codex/mcp-integration-with-override.toml index bf046589c9..8a7c0cec5d 100644 --- a/tests/modules/programs/codex/mcp-integration-with-override.toml +++ b/tests/modules/programs/codex/mcp-integration-with-override.toml @@ -1,7 +1,6 @@ model = "gpt-5-codex" [mcp_servers.context7] -enabled = true url = "https://mcp.context7.com/mcp" [mcp_servers.context7.http_headers] diff --git a/tests/modules/programs/codex/mcp-integration.toml b/tests/modules/programs/codex/mcp-integration.toml index c46f5acd0a..6b8fc04ca9 100644 --- a/tests/modules/programs/codex/mcp-integration.toml +++ b/tests/modules/programs/codex/mcp-integration.toml @@ -1,5 +1,4 @@ [mcp_servers.context7] -enabled = true url = "https://mcp.context7.com/mcp" [mcp_servers.context7.http_headers] @@ -13,4 +12,3 @@ enabled = false [mcp_servers.everything] args = ["-y", "@modelcontextprotocol/server-everything"] command = "npx" -enabled = true diff --git a/tests/modules/programs/github-copilot-cli/expected-mcp-integration-config.json b/tests/modules/programs/github-copilot-cli/expected-mcp-integration-config.json index 14d4b9a590..af381d6e93 100644 --- a/tests/modules/programs/github-copilot-cli/expected-mcp-integration-config.json +++ b/tests/modules/programs/github-copilot-cli/expected-mcp-integration-config.json @@ -15,7 +15,6 @@ "type": "local" }, "fetch": { - "args": [], "command": "mcp-server-fetch", "tools": [ "*" diff --git a/tests/modules/programs/opencode/mcp-integration-with-override.json b/tests/modules/programs/opencode/mcp-integration-with-override.json index d2073eec06..adda7a8312 100644 --- a/tests/modules/programs/opencode/mcp-integration-with-override.json +++ b/tests/modules/programs/opencode/mcp-integration-with-override.json @@ -2,7 +2,6 @@ "$schema": "https://opencode.ai/config.json", "mcp": { "context7": { - "enabled": true, "headers": { "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" }, diff --git a/tests/modules/programs/opencode/mcp-integration.json b/tests/modules/programs/opencode/mcp-integration.json index ddd917af9a..11a16fe7eb 100644 --- a/tests/modules/programs/opencode/mcp-integration.json +++ b/tests/modules/programs/opencode/mcp-integration.json @@ -2,7 +2,6 @@ "$schema": "https://opencode.ai/config.json", "mcp": { "context7": { - "enabled": true, "headers": { "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" }, @@ -23,7 +22,9 @@ "-y", "@modelcontextprotocol/server-everything" ], - "enabled": true, + "environment": { + "NPM_TOKEN": "{file:/run/secrets/npm-token}" + }, "type": "local" } } diff --git a/tests/modules/programs/opencode/mcp-integration.nix b/tests/modules/programs/opencode/mcp-integration.nix index 7afa6eff6d..c194e1dc74 100644 --- a/tests/modules/programs/opencode/mcp-integration.nix +++ b/tests/modules/programs/opencode/mcp-integration.nix @@ -8,6 +8,7 @@ "-y" "@modelcontextprotocol/server-everything" ]; + env.NPM_TOKEN.file = "/run/secrets/npm-token"; }; context7 = { url = "https://mcp.context7.com/mcp"; @@ -18,7 +19,7 @@ disabled-server = { command = "echo"; args = [ "test" ]; - disabled = true; + enabled = false; }; }; }; diff --git a/tests/modules/programs/vscode/mcp-integration-default.json b/tests/modules/programs/vscode/mcp-integration-default.json index 66e3dfd42f..b5ef996146 100644 --- a/tests/modules/programs/vscode/mcp-integration-default.json +++ b/tests/modules/programs/vscode/mcp-integration-default.json @@ -1,7 +1,6 @@ { "servers": { "context7": { - "enabled": true, "headers": { "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" }, @@ -19,7 +18,6 @@ "@modelcontextprotocol/server-everything" ], "command": "npx", - "enabled": true, "type": "stdio" } } diff --git a/tests/modules/programs/vscode/mcp-integration-with-override.json b/tests/modules/programs/vscode/mcp-integration-with-override.json index 10772d0bfd..c0fef518da 100644 --- a/tests/modules/programs/vscode/mcp-integration-with-override.json +++ b/tests/modules/programs/vscode/mcp-integration-with-override.json @@ -5,7 +5,6 @@ "url": "https://example.com/mcp" }, "context7": { - "enabled": true, "headers": { "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" }, diff --git a/tests/modules/programs/zed-editor/mcp-integration-with-override.nix b/tests/modules/programs/zed-editor/mcp-integration-with-override.nix index 6c02f87f0c..2f2413f26b 100644 --- a/tests/modules/programs/zed-editor/mcp-integration-with-override.nix +++ b/tests/modules/programs/zed-editor/mcp-integration-with-override.nix @@ -43,7 +43,6 @@ { "context_servers": { "custom-server": { - "enabled": false } }, } @@ -53,10 +52,10 @@ { "context_servers": { "custom-server": { - "enabled": false, "headers": { "Authorization": "Bearer token" }, + "type": "http", "url": "https://custom.example.com/mcp" }, "everything": { @@ -65,7 +64,7 @@ "@modelcontextprotocol/server-everything" ], "command": "npx", - "enabled": true + "type": "stdio" } } } diff --git a/tests/modules/programs/zed-editor/mcp-integration.json b/tests/modules/programs/zed-editor/mcp-integration.json index cf1b45700e..a12df95754 100644 --- a/tests/modules/programs/zed-editor/mcp-integration.json +++ b/tests/modules/programs/zed-editor/mcp-integration.json @@ -1,10 +1,10 @@ { "context_servers": { "context7": { - "enabled": true, "headers": { "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" }, + "type": "http", "url": "https://mcp.context7.com/mcp" }, "disabled-server": { @@ -12,7 +12,8 @@ "test" ], "command": "echo", - "enabled": false + "enabled": false, + "type": "stdio" }, "everything": { "args": [ @@ -20,7 +21,7 @@ "@modelcontextprotocol/server-everything" ], "command": "npx", - "enabled": true + "type": "stdio" } } } diff --git a/tests/modules/programs/zed-editor/mcp-integration.nix b/tests/modules/programs/zed-editor/mcp-integration.nix index de99efa239..aa2aafc4cb 100644 --- a/tests/modules/programs/zed-editor/mcp-integration.nix +++ b/tests/modules/programs/zed-editor/mcp-integration.nix @@ -44,10 +44,10 @@ { "context_servers": { "context7": { - "enabled": true, "headers": { "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" }, + "type": "http", "url": "https://mcp.context7.com/mcp" }, "disabled-server": { @@ -55,7 +55,8 @@ "test" ], "command": "echo", - "enabled": false + "enabled": false, + "type": "stdio" }, "everything": { "args": [ @@ -63,7 +64,7 @@ "@modelcontextprotocol/server-everything" ], "command": "npx", - "enabled": true + "type": "stdio" } } }