fix: ql-mcp server must handle vscode workspace folder changes#196
fix: ql-mcp server must handle vscode workspace folder changes#196data-douser merged 11 commits intomainfrom
ql-mcp server must handle vscode workspace folder changes#196Conversation
When workspace folders were added or removed, fireDidChange() notified VS Code that the MCP server definitions changed, causing it to stop the running server. However, because the returned McpStdioServerDefinition had the same version as before, VS Code did not restart the server. Add a monotonically increasing revision counter to McpProvider that increments on each fireDidChange() call and is appended to the version string (e.g. 2.25.1.1, 2.25.1.2). This signals VS Code that the definition has genuinely changed and triggers a server restart with the updated environment instead of only stopping the server. - Add _revision counter and getEffectiveVersion() to McpProvider - Add 4 unit tests for version-bumping behaviour - Add integration test suite for workspace folder change scenarios
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
There was a problem hiding this comment.
Pull request overview
This PR fixes a VS Code MCP lifecycle issue where workspace folder changes would stop (but not restart) the ql-mcp server by ensuring the provided server definition’s version changes on each definition refresh.
Changes:
- Add a monotonically increasing revision counter to
McpProviderand append it to the effective server definition version afterfireDidChange(). - Add unit tests validating version bumping behavior across
fireDidChange()calls. - Add an extension-host integration test suite covering workspace folder add/remove scenarios and env refresh.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| extensions/vscode/src/server/mcp-provider.ts | Adds _revision and computes an “effective” version that changes after fireDidChange() to force VS Code to restart the server. |
| extensions/vscode/test/server/mcp-provider.test.ts | Adds unit tests verifying version changes and revision suffixing for pinned/latest cases. |
| extensions/vscode/test/suite/workspace-folder-change.integration.test.ts | Adds integration coverage for workspace-folder-driven definition changes and environment refresh behavior. |
Comments suppressed due to low confidence (2)
extensions/vscode/test/suite/workspace-folder-change.integration.test.ts:201
fs.rmdirSyncis deprecated in newer Node versions. Preferfs.rmSync(tempDir, { recursive: true, force: true })here as well (and wrap in try/catch if you want best-effort cleanup).
if (!added) {
fs.rmdirSync(tempDir);
this.skip();
extensions/vscode/test/suite/workspace-folder-change.integration.test.ts:132
- The comment says Mocha's timeout is 60s, but the test runner sets Mocha's timeout to 30s. Either update this comment or set a longer timeout for this test to avoid confusion / unexpected timeouts on slow CI.
// Listen for the onDidChangeMcpServerDefinitions event.
// Mocha's test timeout (60 s) handles the failure case.
const changePromise = new Promise<void>((resolve) => {
- Invalidate cached environment when workspace folders change so the next server start picks up updated folder list - getEffectiveVersion() always returns a concrete string (never undefined) so VS Code has a reliable baseline for version comparison - Add multi-root workspace integration tests verifying environment correctness (CODEQL_MCP_WORKSPACE_FOLDERS includes/excludes folders) - Add unit test for workspace folder change handler behavior - Add unit test for always-defined version string - Add multiRoot test profile with 4 workspace folders
Agent-Logs-Url: https://github.com/advanced-security/codeql-development-mcp-server/sessions/bc6e5960-6b48-4be3-addb-49a4f14b6efa Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
|
@copilot apply changes based on the comments in this thread |
… vi.spyOn cleanup Agent-Logs-Url: https://github.com/advanced-security/codeql-development-mcp-server/sessions/b3b84dce-0b52-4c7d-9391-014500df1b9a Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
Applied all changes from the review thread in commit a4ebae2:
Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
…on.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Nathan Randall <70299490+data-douser@users.noreply.github.com>
ql-mcp server on vscode workspace folder changesql-mcp server must handle vscode workspace folder changes
…on.test.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Nathan Randall <70299490+data-douser@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 9 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
extensions/vscode/test/suite/workspace-folder-change.integration.test.ts:67
removeWorkspaceFolder()has the same listener-leak risk asaddWorkspaceFolder(): it subscribes toonDidChangeWorkspaceFoldersbefore verifyingupdateWorkspaceFoldersreturnedtrue. IfupdateWorkspaceFoldersreturnsfalse, the thrown assertion leaves the event subscription active. Restructure to only subscribe after a successful update or ensure disposal on failure.
async function removeWorkspaceFolder(fsPath: string): Promise<void> {
const folders = vscode.workspace.workspaceFolders ?? [];
const idx = folders.findIndex((f) => f.uri.fsPath === fsPath);
if (idx < 0) return;
const changePromise = waitForWorkspaceFolderChange();
const ok = vscode.workspace.updateWorkspaceFolders(idx, 1);
assert.ok(ok, 'updateWorkspaceFolders (remove) returned false');
assert.ok(ok, 'updateWorkspaceFolders (remove) returned false');
await changePromise;
|
@copilot apply changes based on the comments in this thread |
…sion version Agent-Logs-Url: https://github.com/advanced-security/codeql-development-mcp-server/sessions/28160745-507f-46aa-b350-6fd6d19315ba Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
Applied all three changes from the review thread in commit 5e06cd6:
|
|
@copilot apply changes based on the comments in this thread |
…vider after mock change Agent-Logs-Url: https://github.com/advanced-security/codeql-development-mcp-server/sessions/a42c54f5-5445-42e7-bad8-f86e1aa717f7 Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
Applied both changes from the review thread in commit 9b6d56c:
|
📝 Update Information
Primitive Details
Type: Both
Name:
McpProvider/ VS Code extension workspace folder handlingUpdate Category: Bug Fix
Impact Scope: Moderate
Update Metadata
readFileSynccalls🎯 Changes Description
Current Behavior
When workspace folders were added or removed,
fireDidChange()notified VS Code that the MCP server definitions changed, causing it to stop the running server. Because the returnedMcpStdioServerDefinitionhad the same version as before, VS Code did not restart theql-mcpserver, leaving it in an inconsistent, partially running state. Additionally,requestRestart()did not invalidate the environment cache, meaning a server restart could reuse a stale cached environment. The multi-root workspace test fixture contained a machine-specific absolute temp folder path causing non-reproducible test failures on other machines and CI.Updated Behavior
The extension now correctly invalidates its environment cache on workspace folder changes and uses a monotonically increasing revision counter (via
requestRestart()) to ensure VS Code reliably detects version changes and restarts the server when needed.requestRestart()now internally callsenvBuilder.invalidate(), so the env cache is always fresh on restart. The version suffix uses the+rNformat (e.g.2.25.1+r1) to keep the base version shape intact.McpProvidercaches the extension version in its constructor to avoid repeated synchronousreadFileSynccalls on everygetEffectiveVersion()invocation. Integration test spy setup usestry/finallyto guarantee restoration even if assertions throw. The integration test for folder-add now populates the env cache before mutating workspace folders, properly verifying that cache invalidation occurs ononDidChangeWorkspaceFolders. The unit test for the "latest version" case re-instantiatesMcpProviderafter configuring thegetExtensionVersionmock so the constructor picks up the correct value.Motivation
The previous implementation left the
ql-mcpserver in a broken state after workspace folder changes.requestRestart()could trigger a restart while still reusing a stale cached environment. Repeated synchronous I/O (readFileSyncofpackage.json) on every definition query unnecessarily blocked the extension host. Test mocks leaked state between tests when spy restoration was not guaranteed. The folder-add integration test could pass vacuously (without ever populating the cache before the add), masking a real regression. The unit test for the "latest version" path set thegetExtensionVersionmock after the provider was already constructed, giving a false sense that the test was configuring the base version.🔄 Before vs. After Comparison
Functionality Changes
API Changes
Output Format Changes
🧪 Testing & Validation
Test Coverage Updates
+rNrevision suffixing, env cache invalidation, and workspace folder change handlingtry/finally, andMcpProviderconstructor caching behaviorValidation Scenarios
requestRestart()correctly invalidates env cache, increments revision, and fires change event; version string changes on each call using+rNformatvi.spyOn()withtry/finallyensures mock cleanup even if assertions throwreadFileSyncon every definition queryTest Results
📋 Implementation Details
Files Modified
extensions/vscode/src/server/mcp-provider.tsextensions/vscode/src/extension.tsextensions/vscode/test/server/mcp-provider.test.ts,extensions/vscode/test/extension.test.ts,extensions/vscode/test/suite/workspace-folder-change.integration.test.tsextensions/vscode/test/fixtures/multi-root-workspace/test.code-workspace(removed machine-specific temp folder entry)Code Changes Summary
requestRestart()atomically invalidates env cache and bumps revision; version suffix uses+rNformat; extension version cached in constructorfs.rmdirSyncreplaced withfs.rmSync; spy restoration guaranteed viatry/finally; duplicate assertion removed fromremoveWorkspaceFoldergetEffectiveVersion()callrequestRestart()method properly typed and self-containedCODEQL_MCP_WORKSPACE_FOLDERSassertions;updateWorkspaceFoldersreturn value asserted before awaiting change promiseMcpProviderafter configuringgetExtensionVersionmockDependencies
🔍 Quality Improvements
Bug Fixes (if applicable)
ql-mcpserver stopped but not restarted after workspace folder changes;requestRestart()did not invalidate env cache as documented; version suffix produced non-semver strings; test fixture contained machine-specific path; test mock leaked state; duplicate assertion inremoveWorkspaceFolder; extension version read synchronously on every query; integration test for folder-add did not pre-populate cache (vacuous test); unit test configuredgetExtensionVersionmock after provider construction (no effect)fireDidChange()called with identical version string; env invalidation was caller's responsibility but undocumented;.Ndot-suffix breaks semver shape; fixture committed with absolute temp path; direct property assignment had no restore mechanism; copy-paste error introduced duplicate assertion;getExtensionVersion()called repeatedly without caching;build()called for the first time after the folder add (cache never populated before mutation); mock changed after constructor already captured the valuerequestRestart()callsenvBuilder.invalidate()internally; version suffix changed to+rN; machine-specific entry removed from fixture;vi.spyOn()withtry/finallyreplaces direct assignment; duplicate assertion removed; extension version cached in constructor field; integration test callsbuild()beforeaddWorkspaceFolder()and asserts precondition; unit test re-instantiatesMcpProviderafter setting mockrequestRestart()call; regex patterns enforce+rNformat; fixture limited to repo-relative folders; precondition assertion in integration test ensures the test cannot pass vacuouslyPerformance Improvements
getExtensionVersion()(readFileSyncofpackage.json) called on everyprovideMcpServerDefinitions()andgetEffectiveVersion()invocationCode Quality Enhancements
requestRestart()is now self-contained — callers cannot forget to invalidate the env cacherequestRestart()can be safely called from any handler that needs to force a server restart with fresh environment🔗 References
Validation Materials
mcp-provider.test.ts(unit),extension.test.ts(unit),workspace-folder-change.integration.test.ts(integration)🚀 Compatibility & Migration
Backward Compatibility
API Evolution
requestRestart()method added toMcpProvider; now also invalidates env cache internally+rNsuffix for reliable change detection while keeping base version shape intact👥 Review Guidelines
For Reviewers
Please verify:
requestRestart()(which now handles env invalidation internally); extension version cached to avoid repeated sync I/Otry/finally; integration test correctly pre-populates cache before folder mutation; unit test re-instantiates provider after mock configurationTesting Instructions
Validation Checklist
requestRestart()invalidates env cache, increments revision, and changes version string to+rNformatreadFileSyncper query)📊 Impact Assessment
Performance Impact
readFileSyncon everygetEffectiveVersion()callServer Impact
AI Assistant Impact
🔄 Deployment Strategy
Rollout Considerations
Update Methodology: This update follows best practices: