diff --git a/modules/programs/opencode.nix b/modules/programs/opencode.nix index 538cff2a3..8e74ca872 100644 --- a/modules/programs/opencode.nix +++ b/modules/programs/opencode.nix @@ -16,6 +16,35 @@ let cfg = config.programs.opencode; jsonFormat = pkgs.formats.json { }; + + transformMcpServer = name: server: { + name = name; + value = { + enabled = !(server.disabled or false); + } + // ( + if server ? url then + { + type = "remote"; + url = server.url; + } + // (lib.optionalAttrs (server ? headers) { headers = server.headers; }) + else if server ? command then + { + type = "local"; + command = [ server.command ] ++ (server.args or [ ]); + } + // (lib.optionalAttrs (server ? env) { environment = server.env; }) + else + { } + ); + }; + + transformedMcpServers = + if cfg.enableMcpIntegration && config.programs.mcp.enable && config.programs.mcp.servers != { } then + lib.listToAttrs (lib.mapAttrsToList transformMcpServer config.programs.mcp.servers) + else + { }; in { meta.maintainers = with lib.maintainers; [ delafthi ]; @@ -25,6 +54,20 @@ in package = mkPackageOption pkgs "opencode" { nullable = true; }; + enableMcpIntegration = 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.opencode.settings.mcp`, with OpenCode settings + taking precedence. + ''; + }; + settings = mkOption { inherit (jsonFormat) type; default = { }; @@ -147,7 +190,7 @@ in Custom themes for opencode. The attribute name becomes the theme filename, and the value is either: - An attribute set, that is converted to a json - - A path to a file conaining the content + - A path to a file containing the content Themes are stored in {file}`$XDG_CONFIG_HOME/opencode/themes/` directory. Set `programs.opencode.settings.theme` to enable the custom theme. See for the documentation. @@ -159,13 +202,21 @@ in home.packages = mkIf (cfg.package != null) [ cfg.package ]; xdg.configFile = { - "opencode/config.json" = mkIf (cfg.settings != { }) { - source = jsonFormat.generate "config.json" ( - { - "$schema" = "https://opencode.ai/config.json"; - } - // cfg.settings - ); + "opencode/config.json" = mkIf (cfg.settings != { } || transformedMcpServers != { }) { + source = + let + # Merge MCP servers: transformed servers + user settings, with user settings taking precedence + mergedMcpServers = transformedMcpServers // (cfg.settings.mcp or { }); + # Merge all settings + mergedSettings = + cfg.settings // (lib.optionalAttrs (mergedMcpServers != { }) { mcp = mergedMcpServers; }); + in + jsonFormat.generate "config.json" ( + { + "$schema" = "https://opencode.ai/config.json"; + } + // mergedSettings + ); }; "opencode/AGENTS.md" = ( diff --git a/tests/modules/programs/opencode/default.nix b/tests/modules/programs/opencode/default.nix index 62d780d5d..74bfc6a8b 100644 --- a/tests/modules/programs/opencode/default.nix +++ b/tests/modules/programs/opencode/default.nix @@ -11,4 +11,6 @@ opencode-mixed-content = ./mixed-content.nix; opencode-themes-inline = ./themes-inline.nix; opencode-themes-path = ./themes-path.nix; + opencode-mcp-integration = ./mcp-integration.nix; + opencode-mcp-integration-with-override = ./mcp-integration-with-override.nix; } diff --git a/tests/modules/programs/opencode/mcp-integration-with-override.json b/tests/modules/programs/opencode/mcp-integration-with-override.json new file mode 100644 index 000000000..46df26ad1 --- /dev/null +++ b/tests/modules/programs/opencode/mcp-integration-with-override.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "context7": { + "enabled": true, + "headers": { + "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" + }, + "type": "remote", + "url": "https://mcp.context7.com/mcp" + }, + "custom-server": { + "enabled": true, + "type": "remote", + "url": "https://example.com" + }, + "everything": { + "command": [ + "custom-command" + ], + "enabled": false, + "type": "local" + } + }, + "model": "anthropic/claude-sonnet-4-20250514", + "theme": "opencode" +} diff --git a/tests/modules/programs/opencode/mcp-integration-with-override.nix b/tests/modules/programs/opencode/mcp-integration-with-override.nix new file mode 100644 index 000000000..c66133b83 --- /dev/null +++ b/tests/modules/programs/opencode/mcp-integration-with-override.nix @@ -0,0 +1,48 @@ +{ + programs.mcp = { + enable = true; + servers = { + everything = { + command = "npx"; + args = [ + "-y" + "@modelcontextprotocol/server-everything" + ]; + }; + context7 = { + url = "https://mcp.context7.com/mcp"; + headers = { + CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}"; + }; + }; + }; + }; + + programs.opencode = { + enable = true; + enableMcpIntegration = true; + settings = { + theme = "opencode"; + model = "anthropic/claude-sonnet-4-20250514"; + # User's custom MCP settings should override generated ones + mcp = { + everything = { + enabled = false; # Override to disable + command = [ "custom-command" ]; + type = "local"; + }; + custom-server = { + enabled = true; + type = "remote"; + url = "https://example.com"; + }; + }; + }; + }; + + nmt.script = '' + assertFileExists home-files/.config/opencode/config.json + assertFileContent home-files/.config/opencode/config.json \ + ${./mcp-integration-with-override.json} + ''; +} diff --git a/tests/modules/programs/opencode/mcp-integration.json b/tests/modules/programs/opencode/mcp-integration.json new file mode 100644 index 000000000..ddd917af9 --- /dev/null +++ b/tests/modules/programs/opencode/mcp-integration.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://opencode.ai/config.json", + "mcp": { + "context7": { + "enabled": true, + "headers": { + "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" + }, + "type": "remote", + "url": "https://mcp.context7.com/mcp" + }, + "disabled-server": { + "command": [ + "echo", + "test" + ], + "enabled": false, + "type": "local" + }, + "everything": { + "command": [ + "npx", + "-y", + "@modelcontextprotocol/server-everything" + ], + "enabled": true, + "type": "local" + } + } +} diff --git a/tests/modules/programs/opencode/mcp-integration.nix b/tests/modules/programs/opencode/mcp-integration.nix new file mode 100644 index 000000000..f3d0f1808 --- /dev/null +++ b/tests/modules/programs/opencode/mcp-integration.nix @@ -0,0 +1,36 @@ +{ + programs.mcp = { + enable = true; + servers = { + everything = { + command = "npx"; + args = [ + "-y" + "@modelcontextprotocol/server-everything" + ]; + }; + context7 = { + url = "https://mcp.context7.com/mcp"; + headers = { + CONTEXT7_API_KEY = "{env:CONTEXT7_API_KEY}"; + }; + }; + disabled-server = { + command = "echo"; + args = [ "test" ]; + disabled = true; + }; + }; + }; + + programs.opencode = { + enable = true; + enableMcpIntegration = true; + }; + + nmt.script = '' + assertFileExists home-files/.config/opencode/config.json + assertFileContent home-files/.config/opencode/config.json \ + ${./mcp-integration.json} + ''; +}