From 7c6f7377ccca88c45a28875e7755c38b604c5ff3 Mon Sep 17 00:00:00 2001 From: "Jacob S. Steward" Date: Fri, 11 Jul 2025 21:33:17 -0400 Subject: [PATCH] vscode: enable defining `mcp.json` separate from `settings.json` (#7441) VS Code 1.102 separates MCP configuration from `settings.json` to a profile-specific `mcp.json`. VS Code automatically performs this separation if MCP configuration is detected inside `settings.json` which conflicts with the immutability of the settings.json that home-manager supplies. --- modules/programs/vscode/default.nix | 26 +++++++++ tests/modules/programs/vscode/default.nix | 1 + tests/modules/programs/vscode/mcp.nix | 71 +++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 tests/modules/programs/vscode/mcp.nix diff --git a/modules/programs/vscode/default.nix b/modules/programs/vscode/default.nix index e961ab5f9..c680ba489 100644 --- a/modules/programs/vscode/default.nix +++ b/modules/programs/vscode/default.nix @@ -54,6 +54,7 @@ let name: "${userDir}/${optionalString (name != "default") "profiles/${name}/"}settings.json"; tasksFilePath = name: "${userDir}/${optionalString (name != "default") "profiles/${name}/"}tasks.json"; + mcpFilePath = name: "${userDir}/${optionalString (name != "default") "profiles/${name}/"}mcp.json"; keybindingsFilePath = name: "${userDir}/${optionalString (name != "default") "profiles/${name}/"}keybindings.json"; @@ -123,6 +124,25 @@ let ''; }; + userMcp = mkOption { + type = types.either types.path jsonFormat.type; + default = { }; + example = literalExpression '' + { + "servers": { + "Github": { + "url": "https://api.githubcopilot.com/mcp/" + } + } + } + ''; + description = '' + Configuration written to Visual Studio Code's + {file}`mcp.json`. + This can be a JSON object or a path to a custom JSON file. + ''; + }; + keybindings = mkOption { type = types.either types.path ( types.listOf ( @@ -270,6 +290,7 @@ in "enableExtensionUpdateCheck" "userSettings" "userTasks" + "userMcp" "keybindings" "extensions" "languageSnippets" @@ -383,6 +404,11 @@ in if isPath v.userTasks then v.userTasks else jsonFormat.generate "vscode-user-tasks" v.userTasks; }) + (mkIf (v.userMcp != { }) { + "${mcpFilePath n}".source = + if isPath v.userMcp then v.userMcp else jsonFormat.generate "vscode-user-mcp" v.userMcp; + }) + (mkIf (v.keybindings != [ ]) { "${keybindingsFilePath n}".source = if isPath v.keybindings then diff --git a/tests/modules/programs/vscode/default.nix b/tests/modules/programs/vscode/default.nix index a32768a92..a79753230 100644 --- a/tests/modules/programs/vscode/default.nix +++ b/tests/modules/programs/vscode/default.nix @@ -1,6 +1,7 @@ { vscode-keybindings = ./keybindings.nix; vscode-tasks = ./tasks.nix; + vscode-mcp = ./mcp.nix; vscode-update-checks = ./update-checks.nix; vscode-snippets = ./snippets.nix; } diff --git a/tests/modules/programs/vscode/mcp.nix b/tests/modules/programs/vscode/mcp.nix new file mode 100644 index 000000000..0c86c0ff5 --- /dev/null +++ b/tests/modules/programs/vscode/mcp.nix @@ -0,0 +1,71 @@ +{ pkgs, lib, ... }: + +let + + mcpFilePath = + name: + if pkgs.stdenv.hostPlatform.isDarwin then + "Library/Application Support/Code/User/${ + lib.optionalString (name != "default") "profiles/${name}/" + }mcp.json" + else + ".config/Code/User/${lib.optionalString (name != "default") "profiles/${name}/"}mcp.json"; + + content = '' + { + "servers": { + "Github": { + "url": "https://api.githubcopilot.com/mcp/" + } + } + } + ''; + + mcp = { + servers = { + Github = { + url = "https://api.githubcopilot.com/mcp/"; + }; + }; + }; + + customMcpPath = pkgs.writeText "custom.json" content; + + expectedMcp = pkgs.writeText "mcp-expected.json" '' + { + "servers": { + "Github": { + "url": "https://api.githubcopilot.com/mcp/" + } + } + } + ''; + + expectedCustomMcp = pkgs.writeText "custom-expected.json" content; + +in +{ + programs.vscode = { + enable = true; + package = pkgs.writeScriptBin "vscode" "" // { + pname = "vscode"; + version = "1.75.0"; + }; + profiles = { + default.userMcp = mcp; + test.userMcp = mcp; + custom.userMcp = customMcpPath; + }; + }; + + nmt.script = '' + assertFileExists "home-files/${mcpFilePath "default"}" + assertFileContent "home-files/${mcpFilePath "default"}" "${expectedMcp}" + + assertFileExists "home-files/${mcpFilePath "test"}" + assertFileContent "home-files/${mcpFilePath "test"}" "${expectedMcp}" + + assertFileExists "home-files/${mcpFilePath "custom"}" + assertFileContent "home-files/${mcpFilePath "custom"}" "${expectedCustomMcp}" + ''; +}