Fix MCP server not restarting when version changes (#278473)

* Initial plan

* Fix MCP server not restarting when cacheNonce/version changes

Add cacheNonce comparison to McpServerDefinition.equals() to ensure servers
are properly stopped and restarted when the version changes (e.g., due to
authentication changes). Add comprehensive tests for the equals function.

Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>

* Fix tests

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: TylerLeonhardt <2644648+TylerLeonhardt@users.noreply.github.com>
Co-authored-by: Tyler Leonhardt <tyleonha@microsoft.com>
This commit is contained in:
Copilot
2025-11-21 06:20:02 +00:00
committed by GitHub
parent f1d6788835
commit 76fb5a42b1
2 changed files with 80 additions and 1 deletions

View File

@@ -184,6 +184,7 @@ export namespace McpServerDefinition {
export function equals(a: McpServerDefinition, b: McpServerDefinition): boolean { export function equals(a: McpServerDefinition, b: McpServerDefinition): boolean {
return a.id === b.id return a.id === b.id
&& a.label === b.label && a.label === b.label
&& a.cacheNonce === b.cacheNonce
&& arraysEqual(a.roots, b.roots, (a, b) => a.toString() === b.toString()) && arraysEqual(a.roots, b.roots, (a, b) => a.toString() === b.toString())
&& objectsEqual(a.launch, b.launch) && objectsEqual(a.launch, b.launch)
&& objectsEqual(a.presentation, b.presentation) && objectsEqual(a.presentation, b.presentation)

View File

@@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
import { McpResourceURI } from '../../common/mcpTypes.js'; import { McpResourceURI, McpServerDefinition, McpServerTransportType } from '../../common/mcpTypes.js';
import * as assert from 'assert'; import * as assert from 'assert';
import { URI } from '../../../../../base/common/uri.js';
suite('MCP Types', () => { suite('MCP Types', () => {
ensureNoDisposablesAreLeakedInTestSuite(); ensureNoDisposablesAreLeakedInTestSuite();
@@ -27,4 +28,81 @@ suite('MCP Types', () => {
roundTrip('custom-scheme:///my-path'); roundTrip('custom-scheme:///my-path');
roundTrip('custom-scheme:///my-path/foo/?with=query&params=here'); roundTrip('custom-scheme:///my-path/foo/?with=query&params=here');
}); });
suite('McpServerDefinition.equals', () => {
const createBasicDefinition = (overrides?: Partial<McpServerDefinition>): McpServerDefinition => ({
id: 'test-server',
label: 'Test Server',
cacheNonce: 'v1.0.0',
launch: {
type: McpServerTransportType.Stdio,
cwd: undefined,
command: 'test-command',
args: [],
env: {},
envFile: undefined
},
...overrides
});
test('returns true for identical definitions', () => {
const def1 = createBasicDefinition();
const def2 = createBasicDefinition();
assert.strictEqual(McpServerDefinition.equals(def1, def2), true);
});
test('returns false when cacheNonce differs', () => {
const def1 = createBasicDefinition({ cacheNonce: 'v1.0.0' });
const def2 = createBasicDefinition({ cacheNonce: 'v2.0.0' });
assert.strictEqual(McpServerDefinition.equals(def1, def2), false);
});
test('returns false when id differs', () => {
const def1 = createBasicDefinition({ id: 'server-1' });
const def2 = createBasicDefinition({ id: 'server-2' });
assert.strictEqual(McpServerDefinition.equals(def1, def2), false);
});
test('returns false when label differs', () => {
const def1 = createBasicDefinition({ label: 'Server A' });
const def2 = createBasicDefinition({ label: 'Server B' });
assert.strictEqual(McpServerDefinition.equals(def1, def2), false);
});
test('returns false when roots differ', () => {
const def1 = createBasicDefinition({ roots: [URI.file('/path1')] });
const def2 = createBasicDefinition({ roots: [URI.file('/path2')] });
assert.strictEqual(McpServerDefinition.equals(def1, def2), false);
});
test('returns true when roots are both undefined', () => {
const def1 = createBasicDefinition({ roots: undefined });
const def2 = createBasicDefinition({ roots: undefined });
assert.strictEqual(McpServerDefinition.equals(def1, def2), true);
});
test('returns false when launch differs', () => {
const def1 = createBasicDefinition({
launch: {
type: McpServerTransportType.Stdio,
cwd: undefined,
command: 'command1',
args: [],
env: {},
envFile: undefined
}
});
const def2 = createBasicDefinition({
launch: {
type: McpServerTransportType.Stdio,
cwd: undefined,
command: 'command2',
args: [],
env: {},
envFile: undefined
}
});
assert.strictEqual(McpServerDefinition.equals(def1, def2), false);
});
});
}); });