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 <khaneliman12@gmail.com>
This commit is contained in:
parent
d47357a4c8
commit
4fcef56c15
11 changed files with 205 additions and 39 deletions
|
|
@ -3,6 +3,7 @@
|
|||
claude-code-full-config = ./full-config.nix;
|
||||
claude-code-lsp = ./lsp.nix;
|
||||
claude-code-mcp = ./mcp.nix;
|
||||
claude-code-mcp-lsp = ./mcp-lsp.nix;
|
||||
claude-code-mcp-integration = ./mcp-integration.nix;
|
||||
claude-code-assertion = ./assertion.nix;
|
||||
claude-code-memory-management = ./memory-management.nix;
|
||||
|
|
|
|||
21
tests/modules/programs/claude-code/expected-lsp-plugin.json
Normal file
21
tests/modules/programs/claude-code/expected-lsp-plugin.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"go": {
|
||||
"args": [
|
||||
"serve"
|
||||
],
|
||||
"command": "gopls",
|
||||
"extensionToLanguage": {
|
||||
".go": "go"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"args": [
|
||||
"--stdio"
|
||||
],
|
||||
"command": "typescript-language-server",
|
||||
"extensionToLanguage": {
|
||||
".ts": "typescript",
|
||||
".tsx": "typescriptreact"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
#! /nix/store/00000000000000000000000000000000-bash/bin/bash -e
|
||||
exec -a "$0" "/nix/store/00000000000000000000000000000000-claude-code/bin/.claude-wrapped" "$@" --plugin-dir /nix/store/00000000000000000000000000000000-claude-code-lsp-plugin
|
||||
#! @bash-interactive@/bin/bash -e
|
||||
exec -a "$0" "/nix/store/00000000000000000000000000000000-claude-code/bin/.claude-wrapped" --plugin-dir /nix/store/00000000000000000000000000000000-claude-code-hm-plugin "$@"
|
||||
|
|
|
|||
36
tests/modules/programs/claude-code/expected-mcp-plugin.json
Normal file
36
tests/modules/programs/claude-code/expected-mcp-plugin.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"mcpServers": {
|
||||
"customTransport": {
|
||||
"customOption": "value",
|
||||
"timeout": 5000,
|
||||
"type": "websocket",
|
||||
"url": "wss://example.com/mcp"
|
||||
},
|
||||
"database": {
|
||||
"args": [
|
||||
"-y",
|
||||
"@bytebase/dbhub",
|
||||
"--dsn",
|
||||
"postgresql://user:pass@localhost:5432/db"
|
||||
],
|
||||
"command": "npx",
|
||||
"env": {
|
||||
"DATABASE_URL": "postgresql://user:pass@localhost:5432/db"
|
||||
},
|
||||
"type": "stdio"
|
||||
},
|
||||
"filesystem": {
|
||||
"args": [
|
||||
"-y",
|
||||
"@modelcontextprotocol/server-filesystem",
|
||||
"/tmp"
|
||||
],
|
||||
"command": "npx",
|
||||
"type": "stdio"
|
||||
},
|
||||
"github": {
|
||||
"type": "http",
|
||||
"url": "https://api.githubcopilot.com/mcp/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
#! /nix/store/00000000000000000000000000000000-bash/bin/bash -e
|
||||
exec -a "$0" "/nix/store/00000000000000000000000000000000-claude-code/bin/.claude-wrapped" "$@" --mcp-config /nix/store/00000000000000000000000000000000-claude-code-mcp-config.json
|
||||
#! @bash-interactive@/bin/bash -e
|
||||
exec -a "$0" "/nix/store/00000000000000000000000000000000-claude-code/bin/.claude-wrapped" --plugin-dir /nix/store/00000000000000000000000000000000-claude-code-hm-plugin "$@"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "claude-code-home-manager"
|
||||
}
|
||||
|
|
@ -32,7 +32,14 @@
|
|||
};
|
||||
|
||||
nmt.script = ''
|
||||
normalizedWrapper=$(normalizeStorePaths home-path/bin/claude)
|
||||
assertFileContent $normalizedWrapper ${./expected-lsp-wrapper}
|
||||
wrapperPath="$TESTED/home-path/bin/claude"
|
||||
normalizedWrapper=$(normalizeStorePaths "$wrapperPath")
|
||||
assertFileContent "$normalizedWrapper" ${./expected-lsp-wrapper}
|
||||
|
||||
pluginDir=$(grep -o -- '--plugin-dir /nix/store/[^ ]*' "$wrapperPath")
|
||||
pluginDir="''${pluginDir#--plugin-dir }"
|
||||
assertFileContent "$pluginDir/.claude-plugin/plugin.json" ${./expected-plugin-manifest.json}
|
||||
assertFileContent "$pluginDir/.lsp.json" ${./expected-lsp-plugin.json}
|
||||
assertPathNotExists "$pluginDir/.mcp.json"
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
};
|
||||
enable = true;
|
||||
|
||||
enableMcpIntagretion = true;
|
||||
enableMcpIntegration = true;
|
||||
|
||||
mcpServers = {
|
||||
github = {
|
||||
|
|
@ -66,7 +66,17 @@
|
|||
};
|
||||
|
||||
nmt.script = ''
|
||||
normalizedWrapper=$(normalizeStorePaths home-path/bin/claude)
|
||||
assertFileContent $normalizedWrapper ${./expected-mcp-wrapper}
|
||||
wrapperPath="$TESTED/home-path/bin/claude"
|
||||
normalizedWrapper=$(normalizeStorePaths "$wrapperPath")
|
||||
assertFileContent "$normalizedWrapper" ${./expected-mcp-wrapper}
|
||||
|
||||
pluginDir=$(grep -o -- '--plugin-dir /nix/store/[^ ]*' "$wrapperPath")
|
||||
pluginDir="''${pluginDir#--plugin-dir }"
|
||||
assertFileContent "$pluginDir/.claude-plugin/plugin.json" ${./expected-plugin-manifest.json}
|
||||
assertFileRegex "$pluginDir/.mcp.json" '"github"'
|
||||
assertFileRegex "$pluginDir/.mcp.json" '"database"'
|
||||
assertFileRegex "$pluginDir/.mcp.json" '"/tmp"'
|
||||
(! grep -q -- '/other-tmp' "$pluginDir/.mcp.json")
|
||||
assertPathNotExists "$pluginDir/.lsp.json"
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
81
tests/modules/programs/claude-code/mcp-lsp.nix
Normal file
81
tests/modules/programs/claude-code/mcp-lsp.nix
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
{ 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;
|
||||
|
||||
lspServers = {
|
||||
go = {
|
||||
command = "gopls";
|
||||
args = [ "serve" ];
|
||||
extensionToLanguage = {
|
||||
".go" = "go";
|
||||
};
|
||||
};
|
||||
typescript = {
|
||||
command = "typescript-language-server";
|
||||
args = [ "--stdio" ];
|
||||
extensionToLanguage = {
|
||||
".ts" = "typescript";
|
||||
".tsx" = "typescriptreact";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
mcpServers = {
|
||||
github = {
|
||||
type = "http";
|
||||
url = "https://api.githubcopilot.com/mcp/";
|
||||
};
|
||||
filesystem = {
|
||||
type = "stdio";
|
||||
command = "npx";
|
||||
args = [
|
||||
"-y"
|
||||
"@modelcontextprotocol/server-filesystem"
|
||||
"/tmp"
|
||||
];
|
||||
};
|
||||
database = {
|
||||
type = "stdio";
|
||||
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 = ''
|
||||
wrapperPath="$TESTED/home-path/bin/claude"
|
||||
normalizedWrapper=$(normalizeStorePaths "$wrapperPath")
|
||||
assertFileContent "$normalizedWrapper" ${./expected-mcp-wrapper}
|
||||
|
||||
test "$(grep -o -- '--plugin-dir ' "$wrapperPath" | wc -l)" -eq 1
|
||||
pluginDir=$(grep -o -- '--plugin-dir /nix/store/[^ ]*' "$wrapperPath")
|
||||
pluginDir="''${pluginDir#--plugin-dir }"
|
||||
assertFileContent "$pluginDir/.claude-plugin/plugin.json" ${./expected-plugin-manifest.json}
|
||||
assertFileContent "$pluginDir/.mcp.json" ${./expected-mcp-plugin.json}
|
||||
assertFileContent "$pluginDir/.lsp.json" ${./expected-lsp-plugin.json}
|
||||
'';
|
||||
}
|
||||
|
|
@ -49,7 +49,14 @@
|
|||
};
|
||||
|
||||
nmt.script = ''
|
||||
normalizedWrapper=$(normalizeStorePaths home-path/bin/claude)
|
||||
assertFileContent $normalizedWrapper ${./expected-mcp-wrapper}
|
||||
wrapperPath="$TESTED/home-path/bin/claude"
|
||||
normalizedWrapper=$(normalizeStorePaths "$wrapperPath")
|
||||
assertFileContent "$normalizedWrapper" ${./expected-mcp-wrapper}
|
||||
|
||||
pluginDir=$(grep -o -- '--plugin-dir /nix/store/[^ ]*' "$wrapperPath")
|
||||
pluginDir="''${pluginDir#--plugin-dir }"
|
||||
assertFileContent "$pluginDir/.claude-plugin/plugin.json" ${./expected-plugin-manifest.json}
|
||||
assertFileContent "$pluginDir/.mcp.json" ${./expected-mcp-plugin.json}
|
||||
assertPathNotExists "$pluginDir/.lsp.json"
|
||||
'';
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue