From 4fcef56c15326af5e419fae4e059bb099c6c3800 Mon Sep 17 00:00:00 2001 From: Austin Horstman Date: Wed, 18 Mar 2026 16:57:42 -0500 Subject: [PATCH] claude-code: load MCP config via plugin dir Claude Code rejects `--mcp-config` once the Home Manager wrapper injects it around subcommands, which breaks commands like `claude mcp list`. Claude Code 2.1.76 fixed `--plugin-dir` so it no longer consumes following subcommands, so use that path for the generated MCP config instead. Generate a plugin directory with a manifest and `.mcp.json`, wrap `claude` with `--plugin-dir` before user arguments, and snapshot that wrapper directly in the tests. Keep the existing LSP support in the generated plugin directory as well, and add coverage for the combined MCP+LSP case plus the MCP integration merge path. Signed-off-by: Austin Horstman --- modules/programs/claude-code.nix | 56 ++++++------- .../modules/programs/claude-code/default.nix | 1 + .../claude-code/expected-lsp-plugin.json | 21 +++++ .../programs/claude-code/expected-lsp-wrapper | 4 +- .../claude-code/expected-mcp-plugin.json | 36 +++++++++ .../programs/claude-code/expected-mcp-wrapper | 4 +- .../claude-code/expected-plugin-manifest.json | 3 + tests/modules/programs/claude-code/lsp.nix | 11 ++- .../programs/claude-code/mcp-integration.nix | 16 +++- .../modules/programs/claude-code/mcp-lsp.nix | 81 +++++++++++++++++++ tests/modules/programs/claude-code/mcp.nix | 11 ++- 11 files changed, 205 insertions(+), 39 deletions(-) create mode 100644 tests/modules/programs/claude-code/expected-lsp-plugin.json create mode 100644 tests/modules/programs/claude-code/expected-mcp-plugin.json create mode 100644 tests/modules/programs/claude-code/expected-plugin-manifest.json create mode 100644 tests/modules/programs/claude-code/mcp-lsp.nix diff --git a/modules/programs/claude-code.nix b/modules/programs/claude-code.nix index ceadba99..9700416e 100644 --- a/modules/programs/claude-code.nix +++ b/modules/programs/claude-code.nix @@ -469,39 +469,39 @@ in programs.claude-code.finalPackage = let mergedMcpServers = transformedMcpServers // cfg.mcpServers; - makeWrapperArgs = lib.flatten ( - lib.filter (x: x != [ ]) [ - (lib.optional (cfg.mcpServers != { } || transformedMcpServers != { }) [ - "--append-flags" - "--mcp-config ${ - jsonFormat.generate "claude-code-mcp-config.json" { mcpServers = mergedMcpServers; } - }" - ]) - (lib.optional (cfg.lspServers != { }) [ - "--append-flags" - "--plugin-dir ${ - pkgs.runCommand "claude-code-lsp-plugin" { } '' - install -Dm644 ${ - jsonFormat.generate "claude-code-lsp-plugin.json" { - name = "claude-code-lsp"; - lspServers = cfg.lspServers; - } - } $out/.claude-plugin/plugin.json - '' - }" - ]) - ] - ); - - hasWrapperArgs = makeWrapperArgs != [ ]; + hasMcpServers = mergedMcpServers != { }; + hasLspServers = cfg.lspServers != { }; + pluginDir = + if hasMcpServers || hasLspServers then + pkgs.runCommand "claude-code-hm-plugin" { } '' + install -Dm644 ${ + jsonFormat.generate "claude-code-plugin.json" { + name = "claude-code-home-manager"; + } + } $out/.claude-plugin/plugin.json + ${lib.optionalString hasMcpServers '' + install -Dm644 ${ + jsonFormat.generate "claude-code-mcp.json" { mcpServers = mergedMcpServers; } + } $out/.mcp.json + ''} + ${lib.optionalString hasLspServers '' + install -Dm644 ${jsonFormat.generate "claude-code-lsp.json" cfg.lspServers} $out/.lsp.json + ''} + '' + else + null; in - if hasWrapperArgs then + if pluginDir != null then pkgs.symlinkJoin { name = "claude-code"; paths = [ cfg.package ]; - nativeBuildInputs = [ pkgs.makeWrapper ]; postBuild = '' - wrapProgram $out/bin/claude ${lib.escapeShellArgs makeWrapperArgs} + mv $out/bin/claude $out/bin/.claude-wrapped + cat > $out/bin/claude <