Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,22 @@ export function shellQuotePluginRootInCommand(command: string, fsPath: string, t
});
}

/**
* Extracts the MCP server map from a raw JSON value. Accepts both the
* wrapped format `{ mcpServers: { … } }` (Claude `.mcp.json`) and the
* flat format where server entries are at the top level.
* Returns `undefined` when the input is not a usable object.
*/
export function resolveMcpServersMap(raw: unknown): Record<string, unknown> | undefined {
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
return undefined;
}
const obj = raw as Record<string, unknown>;
return Object.hasOwn(obj, 'mcpServers')
? (obj.mcpServers as Record<string, unknown>)
: obj;
}

/**
* Replaces `${token}` references in MCP server definition string fields
* (command, args, cwd, env values, url, envFile, headers) with the plugin
Expand Down Expand Up @@ -671,12 +687,13 @@ export abstract class AbstractAgentPluginDiscovery extends Disposable implements
}

private _parseMcpServerDefinitionMap(raw: unknown, pluginFsPath: string, adapter: IAgentPluginFormatAdapter): IAgentPluginMcpServerDefinition[] {
if (!raw || typeof raw !== 'object' || !raw.hasOwnProperty('mcpServers')) {
const mcpServers = resolveMcpServersMap(raw);
if (!mcpServers) {
return [];
}

const definitions: IAgentPluginMcpServerDefinition[] = [];
for (const [name, configValue] of Object.entries((raw as { mcpServers: Record<string, unknown> }).mcpServers)) {
for (const [name, configValue] of Object.entries(mcpServers)) {
const configuration = this._normalizeMcpServerConfiguration(configValue);
if (!configuration) {
continue;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import assert from 'assert';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
import { resolveMcpServersMap } from '../../../common/plugins/agentPluginServiceImpl.js';

suite('resolveMcpServersMap', () => {
ensureNoDisposablesAreLeakedInTestSuite();

test('returns undefined for null', () => {
assert.strictEqual(resolveMcpServersMap(null), undefined);
});

test('returns undefined for undefined', () => {
assert.strictEqual(resolveMcpServersMap(undefined), undefined);
});

test('returns undefined for non-object primitives', () => {
assert.strictEqual(resolveMcpServersMap('string'), undefined);
assert.strictEqual(resolveMcpServersMap(42), undefined);
assert.strictEqual(resolveMcpServersMap(true), undefined);
});
Comment thread
connor4312 marked this conversation as resolved.

test('unwraps mcpServers property when present', () => {
const servers = { myServer: { command: 'node' } };
const result = resolveMcpServersMap({ mcpServers: servers });
assert.deepStrictEqual(result, servers);
});

test('returns the object directly when mcpServers is absent', () => {
const servers = { myServer: { command: 'node' }, otherServer: { url: 'http://localhost' } };
const result = resolveMcpServersMap(servers);
assert.deepStrictEqual(result, servers);
});

test('returns empty object for empty wrapped format', () => {
assert.deepStrictEqual(resolveMcpServersMap({ mcpServers: {} }), {});
});

test('returns empty object for empty flat format', () => {
assert.deepStrictEqual(resolveMcpServersMap({}), {});
});

test('prefers mcpServers property over other top-level keys', () => {
const inner = { realServer: { command: 'python' } };
const result = resolveMcpServersMap({ mcpServers: inner, someOtherKey: 'ignored' });
assert.deepStrictEqual(result, inner);
});
});
Loading