fix(installer): copilot-vscode global entry drops ${workspaceFolder} — VS Code toasts an error in every folderless window
A user-level mcp.json entry using ${workspaceFolder} makes VS Code
refuse to start the server in ANY window without a folder open (loose
files, welcome tab), toasting "Variable workspaceFolder can not be
resolved" — recurring error-noise, hit live during validation.
The pin was never needed for VS Code: unlike Cursor, VS Code documents
stdio-server cwd as the workspace folder, and the codegraph server
resolves its project via roots/list with a cwd fallback. Global entries
are now variable-free (`serve --mcp`); local installs keep the absolute
--path. This supersedes the "open a folder" install note from the
previous commit, which is removed again.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
73313213e1
commit
9769d6be0f
@@ -1945,11 +1945,18 @@ describe('Installer targets — Copilot family', () => {
|
|||||||
expect(cfg.mcpServers).toBeUndefined();
|
expect(cfg.mcpServers).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('copilot-vscode: global install pins --path to ${workspaceFolder}', () => {
|
it('copilot-vscode: global install writes a variable-free entry — no --path, no ${workspaceFolder}', () => {
|
||||||
|
// VS Code refuses to start a user-level server whose entry uses
|
||||||
|
// ${workspaceFolder} in any window with no folder open, toasting
|
||||||
|
// "Variable workspaceFolder can not be resolved" (hit live). VS Code
|
||||||
|
// documents cwd = workspace folder for stdio servers, and the
|
||||||
|
// codegraph server resolves the project from roots/cwd — so the
|
||||||
|
// global entry must carry no --path and no variables at all.
|
||||||
const t = getTarget('copilot-vscode')!;
|
const t = getTarget('copilot-vscode')!;
|
||||||
const result = t.install('global', { autoAllow: true });
|
const result = t.install('global', { autoAllow: true });
|
||||||
const cfg = JSON.parse(fs.readFileSync(result.files[0].path, 'utf-8'));
|
const cfg = JSON.parse(fs.readFileSync(result.files[0].path, 'utf-8'));
|
||||||
expect(cfg.servers.codegraph.args).toEqual(['serve', '--mcp', '--path', '${workspaceFolder}']);
|
expect(cfg.servers.codegraph.args).toEqual(['serve', '--mcp']);
|
||||||
|
expect(JSON.stringify(cfg)).not.toContain('${');
|
||||||
});
|
});
|
||||||
|
|
||||||
it.runIf(process.platform === 'darwin')('copilot-vscode: global path is ~/Library/Application Support/Code/User/mcp.json on macOS', () => {
|
it.runIf(process.platform === 'darwin')('copilot-vscode: global path is ~/Library/Application Support/Code/User/mcp.json on macOS', () => {
|
||||||
@@ -2095,15 +2102,6 @@ describe('Installer targets — Copilot family', () => {
|
|||||||
expect(result.notes?.join(' ')).toMatch(/[Rr]estart VS Code/);
|
expect(result.notes?.join(' ')).toMatch(/[Rr]estart VS Code/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('copilot-vscode: global install warns that ${workspaceFolder} needs an open folder; local does not', () => {
|
|
||||||
const t = getTarget('copilot-vscode')!;
|
|
||||||
// VS Code refuses to start a user-level server whose entry uses
|
|
||||||
// ${workspaceFolder} when no folder is open — surface that up front.
|
|
||||||
const globalNotes = t.install('global', { autoAllow: true }).notes?.join(' ');
|
|
||||||
expect(globalNotes).toMatch(/open a folder/i);
|
|
||||||
const localNotes = t.install('local', { autoAllow: true }).notes?.join(' ');
|
|
||||||
expect(localNotes).not.toMatch(/open a folder/i);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ---- copilot-cli ----
|
// ---- copilot-cli ----
|
||||||
|
|
||||||
|
|||||||
@@ -16,17 +16,24 @@
|
|||||||
* instructions, the single source of truth (#529).
|
* instructions, the single source of truth (#529).
|
||||||
* - No permissions concept — `autoAllow` is silently ignored.
|
* - No permissions concept — `autoAllow` is silently ignored.
|
||||||
*
|
*
|
||||||
* ## Why we inject `--path` (mirrors Cursor)
|
* ## Why `--path` only for local installs (NOT the Cursor pattern)
|
||||||
*
|
*
|
||||||
* VS Code's docs don't specify the working directory stdio MCP servers
|
* Unlike Cursor, VS Code DOCUMENTS the launch cwd for stdio MCP
|
||||||
* are launched with, and (like Cursor) we can't rely on it being the
|
* servers: "Working directory for the server command. Defaults to the
|
||||||
* workspace root. Rather than depend on undocumented cwd behavior we
|
* workspace folder when run in a workspace" (mcp-configuration
|
||||||
* pin the project explicitly:
|
* reference). The codegraph server resolves its project via the MCP
|
||||||
|
* roots/list dance with a cwd fallback, so cwd alone is sufficient:
|
||||||
*
|
*
|
||||||
* - `local` install: absolute path (known at install time).
|
* - `local` install: absolute `--path` (known at install time) —
|
||||||
* - `global` install: `${workspaceFolder}` — VS Code expands its
|
* deterministic, and free of variables.
|
||||||
* standard variables inside mcp.json, giving per-workspace behavior
|
* - `global` install: NO `--path`. Do not be tempted to pin it with
|
||||||
* from a single user-level config.
|
* `${workspaceFolder}`: VS Code refuses to start a user-level
|
||||||
|
* server whose entry uses that variable whenever a window has no
|
||||||
|
* folder open (loose files, welcome tab), surfacing an error toast
|
||||||
|
* "Variable workspaceFolder can not be resolved" in every such
|
||||||
|
* window — exactly the error-noise that teaches users to disable
|
||||||
|
* the server. With no `--path`, a folderless window still starts
|
||||||
|
* the server fine and it serves the "no project" guidance.
|
||||||
*
|
*
|
||||||
* ## JSONC
|
* ## JSONC
|
||||||
*
|
*
|
||||||
@@ -78,13 +85,16 @@ function mcpJsonPath(loc: Location): string {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Build the codegraph server entry for VS Code at the given location.
|
* Build the codegraph server entry for VS Code at the given location.
|
||||||
* Shared `{type, command, args}` shape plus the `--path` pin — see
|
* Local installs pin `--path`; global installs rely on VS Code's
|
||||||
* file header for why we don't trust VS Code's launch cwd.
|
* documented workspace-folder cwd — see file header for why the global
|
||||||
|
* entry must stay variable-free.
|
||||||
*/
|
*/
|
||||||
function buildVscodeServerEntry(loc: Location): { type: string; command: string; args: string[] } {
|
function buildVscodeServerEntry(loc: Location): { type: string; command: string; args: string[] } {
|
||||||
const base = getMcpServerConfig();
|
const base = getMcpServerConfig();
|
||||||
const pathArg = loc === 'local' ? process.cwd() : '${workspaceFolder}';
|
if (loc === 'local') {
|
||||||
return { ...base, args: [...base.args, '--path', pathArg] };
|
return { ...base, args: [...base.args, '--path', process.cwd()] };
|
||||||
|
}
|
||||||
|
return { ...base, args: [...base.args] };
|
||||||
}
|
}
|
||||||
|
|
||||||
function readConfigText(file: string): string {
|
function readConfigText(file: string): string {
|
||||||
@@ -127,16 +137,9 @@ class CopilotVscodeTarget implements AgentTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
install(loc: Location, _opts: InstallOptions): WriteResult {
|
install(loc: Location, _opts: InstallOptions): WriteResult {
|
||||||
const notes = ['Restart VS Code for MCP changes to take effect.'];
|
|
||||||
if (loc === 'global') {
|
|
||||||
// The global entry pins --path via ${workspaceFolder}; VS Code
|
|
||||||
// refuses to start it in a window with no folder open, with a
|
|
||||||
// cryptic "Variable workspaceFolder can not be resolved" toast.
|
|
||||||
notes.push('VS Code: the server starts per-workspace — open a folder (File → Open Folder) before starting it; a no-folder window reports "Variable workspaceFolder can not be resolved".');
|
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
files: [writeMcpEntry(loc)],
|
files: [writeMcpEntry(loc)],
|
||||||
notes,
|
notes: ['Restart VS Code for MCP changes to take effect.'],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user