claude-code: reject disabled mcp servers instead of dropping

Filtering disabled shared servers out of `.mcp.json` (#9457) collapsed
"present but disabled" and "absent" into the same output, so a server
set
`enabled = false` for OpenCode could no longer remain present in Claude
Code. `.mcp.json` has no per-server `enabled` field, but settings.json
does via `disabledMcpjsonServers`.

Keep disabled servers in `.mcp.json` and list them under
`disabledMcpjsonServers` (merged with any user-set value). This honors
the
disabled intent without dropping the server, and covers both shared and
Claude Code-native servers.
This commit is contained in:
Austin Horstman 2026-06-23 11:29:41 -05:00
parent 1ac720f007
commit 062581938b
4 changed files with 74 additions and 6 deletions

View file

@ -38,10 +38,15 @@ let
];
};
transformedMcpServers = lib.optionalAttrs (cfg.enableMcpIntegration && config.programs.mcp.enable) (
lib.mapAttrs transformMcpServer (
lib.filterAttrs (_: isMcpServerEnabled) config.programs.mcp.servers
)
# Shared MCP servers participate only when both integrations are enabled.
sharedMcpServers = lib.optionalAttrs (
cfg.enableMcpIntegration && config.programs.mcp.enable
) config.programs.mcp.servers;
transformedMcpServers = lib.mapAttrs transformMcpServer sharedMcpServers;
disabledMcpServerNames = lib.attrNames (
lib.filterAttrs (_: server: !(isMcpServerEnabled server)) (sharedMcpServers // cfg.mcpServers)
);
mkContentOption =
@ -111,6 +116,12 @@ in
Note: Settings defined in {option}`programs.mcp.servers` are merged
with {option}`programs.claude-code.mcpServers`, with Claude Code servers
taking precedence.
Servers marked disabled (via `enabled = false` or `disabled = true`)
are still written to {file}`.mcp.json` but rejected through
`disabledMcpjsonServers` in {file}`settings.json`, since Claude Code
has no per-server `enabled` field. This keeps a disabled server
present (and re-enableable) instead of dropping it.
'';
};
@ -701,7 +712,7 @@ in
};
file = lib.mkMerge [
(lib.mkIf (cfg.settings != { } || cfg.marketplaces != { }) {
(lib.mkIf (cfg.settings != { } || cfg.marketplaces != { } || disabledMcpServerNames != [ ]) {
"${cfg.configDir}/settings.json".source = jsonFormat.generate "claude-code-settings.json" (
cfg.settings
// {
@ -710,6 +721,11 @@ in
// optionalAttrs (cfg.marketplaces != { }) {
extraKnownMarketplaces = lib.mapAttrs mkMarketplaceEntry cfg.marketplaces;
}
// optionalAttrs (disabledMcpServerNames != [ ]) {
disabledMcpjsonServers = lib.unique (
(cfg.settings.disabledMcpjsonServers or [ ]) ++ disabledMcpServerNames
);
}
);
})
(

View file

@ -0,0 +1,43 @@
{
"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"
},
"disabled-server": {
"args": [
"test"
],
"command": "echo",
"type": "stdio"
},
"filesystem": {
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/tmp"
],
"command": "npx",
"type": "stdio"
},
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/"
}
}
}

View file

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"disabledMcpjsonServers": [
"disabled-server"
]
}

View file

@ -79,7 +79,10 @@
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/.mcp.json" ${./expected-mcp-integration-plugin.json}
assertPathNotExists "$pluginDir/.lsp.json"
settingsPath="$TESTED/home-files/.claude/settings.json"
assertFileContent "$settingsPath" ${./expected-mcp-settings.json}
'';
}