From 83bcb17377f0242376a327e742e9404e9a528647 Mon Sep 17 00:00:00 2001 From: Thierry Delafontaine Date: Sun, 18 Jan 2026 04:33:01 +0100 Subject: [PATCH] claude-code: add `enableMcpIntegration` Add `enableMcpIntegration` option to merge MCP servers from `programs.mcp.servers` into Claude Code configuration. Claude Code servers take precedence over general MCP servers when both define the same server name. --- modules/programs/claude-code.nix | 36 ++++++++-- .../programs/claude-code/assertion.nix | 2 +- .../modules/programs/claude-code/default.nix | 1 + .../programs/claude-code/mcp-integration.nix | 72 +++++++++++++++++++ 4 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 tests/modules/programs/claude-code/mcp-integration.nix diff --git a/modules/programs/claude-code.nix b/modules/programs/claude-code.nix index 8d5a4036..dfc0fc55 100644 --- a/modules/programs/claude-code.nix +++ b/modules/programs/claude-code.nix @@ -7,6 +7,17 @@ let cfg = config.programs.claude-code; jsonFormat = pkgs.formats.json { }; + transformedMcpServers = lib.optionalAttrs (cfg.enableMcpIntegration && config.programs.mcp.enable) ( + lib.mapAttrs ( + name: server: + (removeAttrs server [ "disabled" ]) + // (lib.optionalAttrs (server ? url) { type = "http"; }) + // (lib.optionalAttrs (server ? command) { type = "stdio"; }) + // { + enabled = !(server.disabled or false); + } + ) config.programs.mcp.servers + ); in { meta.maintainers = [ lib.maintainers.khaneliman ]; @@ -23,6 +34,20 @@ in description = "Resulting customized claude-code package."; }; + enableMcpIntegration = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether to integrate the MCP servers config from + {option}`programs.mcp.servers` into + {option}`programs.opencode.settings.mcp`. + + Note: Settings defined in {option}`programs.mcp.servers` are merged + with {option}`programs.claude-code.mcpServers`, with Claude Code servers + taking precedence. + ''; + }; + settings = lib.mkOption { inherit (jsonFormat) type; default = { }; @@ -359,8 +384,8 @@ in config = lib.mkIf cfg.enable { assertions = [ { - assertion = cfg.mcpServers == { } || cfg.package != null; - message = "`programs.claude-code.package` cannot be null when `mcpServers` is configured"; + assertion = (cfg.mcpServers == { } && !cfg.enableMcpIntegration) || cfg.package != null; + message = "`programs.claude-code.package` cannot be null when `mcpServers` or `enableMcpIntegration` is configured"; } { assertion = !(cfg.memory.text != null && cfg.memory.source != null); @@ -390,11 +415,14 @@ in programs.claude-code.finalPackage = let + mergedMcpServers = transformedMcpServers // cfg.mcpServers; makeWrapperArgs = lib.flatten ( lib.filter (x: x != [ ]) [ - (lib.optional (cfg.mcpServers != { }) [ + (lib.optional (cfg.mcpServers != { } || transformedMcpServers != { }) [ "--append-flags" - "--mcp-config ${jsonFormat.generate "claude-code-mcp-config.json" { inherit (cfg) mcpServers; }}" + "--mcp-config ${ + jsonFormat.generate "claude-code-mcp-config.json" { mcpServers = mergedMcpServers; } + }" ]) ] ); diff --git a/tests/modules/programs/claude-code/assertion.nix b/tests/modules/programs/claude-code/assertion.nix index 77c002f3..8ce468ef 100644 --- a/tests/modules/programs/claude-code/assertion.nix +++ b/tests/modules/programs/claude-code/assertion.nix @@ -47,7 +47,7 @@ }; test.asserts.assertions.expected = [ - "`programs.claude-code.package` cannot be null when `mcpServers` is configured" + "`programs.claude-code.package` cannot be null when `mcpServers` or `enableMcpIntegration` is configured" "Cannot specify both `programs.claude-code.memory.text` and `programs.claude-code.memory.source`" "Cannot specify both `programs.claude-code.agents` and `programs.claude-code.agentsDir`" "Cannot specify both `programs.claude-code.commands` and `programs.claude-code.commandsDir`" diff --git a/tests/modules/programs/claude-code/default.nix b/tests/modules/programs/claude-code/default.nix index 315a1975..a93d6f93 100644 --- a/tests/modules/programs/claude-code/default.nix +++ b/tests/modules/programs/claude-code/default.nix @@ -2,6 +2,7 @@ claude-code-basic = ./basic.nix; claude-code-full-config = ./full-config.nix; claude-code-mcp = ./mcp.nix; + claude-code-mcp-integration = ./mcp-integration.nix; claude-code-assertion = ./assertion.nix; claude-code-memory-management = ./memory-management.nix; claude-code-memory-from-source = ./memory-from-source.nix; diff --git a/tests/modules/programs/claude-code/mcp-integration.nix b/tests/modules/programs/claude-code/mcp-integration.nix new file mode 100644 index 00000000..9e9c0c66 --- /dev/null +++ b/tests/modules/programs/claude-code/mcp-integration.nix @@ -0,0 +1,72 @@ +{ config, ... }: + +{ + programs = { + claude-code = { + package = config.lib.test.mkStubPackage { + name = "claude-code"; + buildScript = '' + mkdir -p $out/bin + touch $out/bin/claude + chmod 755 $out/bin/claude + ''; + }; + enable = true; + + enableMcpIntagretion = true; + + mcpServers = { + github = { + type = "http"; + url = "https://api.githubcopilot.com/mcp/"; + }; + filesystem = { + type = "stdio"; + command = "npx"; + args = [ + "-y" + "@modelcontextprotocol/server-filesystem" + "/tmp" + ]; + }; + }; + }; + mcp = { + enable = true; + servers = { + filesystem = { + type = "stdio"; + command = "npx"; + args = [ + "-y" + "@modelcontextprotocol/server-filesystem" + "/other-tmp" + ]; + }; + database = { + command = "npx"; + args = [ + "-y" + "@bytebase/dbhub" + "--dsn" + "postgresql://user:pass@localhost:5432/db" + ]; + env = { + DATABASE_URL = "postgresql://user:pass@localhost:5432/db"; + }; + }; + customTransport = { + type = "websocket"; + url = "wss://example.com/mcp"; + customOption = "value"; + timeout = 5000; + }; + }; + }; + }; + + nmt.script = '' + normalizedWrapper=$(normalizeStorePaths home-path/bin/claude) + assertFileContent $normalizedWrapper ${./expected-mcp-wrapper} + ''; +}