Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/node/test/mcpHandler.spec.ts
13406 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import type { SweCustomAgent } from '@github/copilot/sdk';6import { describe, expect, it } from 'vitest';7import type { LanguageModelToolInformation } from '../../../../../vscodeTypes';8import { buildMcpServerMappings, type MCPServerConfig, type McpServerMappings, remapCustomAgentTools } from '../mcpHandler';910function makeAgent(partial: { slug: string; tools?: string[] }): SweCustomAgent {11return partial as unknown as SweCustomAgent;12}1314function makeTool(fullReferenceName: string | undefined, sourceLabel?: string): LanguageModelToolInformation {15return {16name: fullReferenceName ?? 'no-ref',17fullReferenceName,18source: sourceLabel ? { label: sourceLabel, name: sourceLabel } : undefined,19} as unknown as LanguageModelToolInformation;20}2122function makeToolsMap(...entries: [LanguageModelToolInformation, boolean][]): ReadonlyMap<LanguageModelToolInformation, boolean> {23return new Map(entries);24}2526describe('buildMcpServerMappings', () => {27it('should extract simple server name from fullReferenceName', () => {28const tools = makeToolsMap(29[makeTool('myServer/myTool', 'My Server'), true],30);31const mappings = buildMcpServerMappings(tools);32expect(mappings.get('myServer')).toBe('My Server');33});3435it('should use the last slash to split server name from tool name', () => {36const tools = makeToolsMap(37[makeTool('scope/myServer/myTool', 'Scoped Server'), true],38);39const mappings = buildMcpServerMappings(tools);40expect(mappings.get('scope/myServer')).toBe('Scoped Server');41expect(mappings.has('scope')).toBe(false);42});4344it('should handle server names with multiple slashes', () => {45const tools = makeToolsMap(46[makeTool('a/b/c/toolName', 'Deep Server'), true],47);48const mappings = buildMcpServerMappings(tools);49expect(mappings.get('a/b/c')).toBe('Deep Server');50});5152it('should skip tools without source', () => {53const tools = makeToolsMap(54[makeTool('server/tool'), true],55);56const mappings = buildMcpServerMappings(tools);57expect(mappings.size).toBe(0);58});5960it('should skip tools without fullReferenceName', () => {61const tools = makeToolsMap(62[makeTool(undefined, 'Some Server'), true],63);64const mappings = buildMcpServerMappings(tools);65expect(mappings.size).toBe(0);66});6768it('should skip tools with no slash in fullReferenceName', () => {69const tools = makeToolsMap(70[makeTool('toolOnly', 'Server'), true],71);72const mappings = buildMcpServerMappings(tools);73expect(mappings.size).toBe(0);74});7576it('should not overwrite existing mappings for the same server name', () => {77const tools = makeToolsMap(78[makeTool('server/tool1', 'First Label'), true],79[makeTool('server/tool2', 'Second Label'), true],80);81const mappings = buildMcpServerMappings(tools);82expect(mappings.get('server')).toBe('First Label');83});8485it('should handle multiple different servers', () => {86const tools = makeToolsMap(87[makeTool('serverA/tool1', 'Server A'), true],88[makeTool('serverB/tool2', 'Server B'), true],89);90const mappings = buildMcpServerMappings(tools);91expect(mappings.get('serverA')).toBe('Server A');92expect(mappings.get('serverB')).toBe('Server B');93});94});9596describe('remapCustomAgentTools', () => {97function makeMcpServers(entries: Record<string, { displayName?: string }>): Record<string, MCPServerConfig> {98const result: Record<string, MCPServerConfig> = {};99for (const [key, val] of Object.entries(entries)) {100result[key] = { type: 'http' as const, url: 'http://localhost', tools: ['*'], ...val };101}102return result;103}104105it('should remap simple server/tool references', () => {106const agents: SweCustomAgent[] = [107makeAgent({ slug: 'agent1', tools: ['friendlyName/toolA'] }),108];109const mcpMappings: McpServerMappings = new Map([['friendlyName', 'Display Name']]);110const mcpServers = makeMcpServers({ 'gateway_name': { displayName: 'Display Name' } });111112remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);113114expect(agents[0].tools).toEqual(['gateway_name/toolA']);115});116117it('should remap tools with slashes in server name using last slash', () => {118const agents: SweCustomAgent[] = [119makeAgent({ slug: 'agent1', tools: ['org/server/toolA'] }),120];121const mcpMappings: McpServerMappings = new Map([['org/server', 'Org Server Display']]);122const mcpServers = makeMcpServers({ 'org_server_gw': { displayName: 'Org Server Display' } });123124remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);125126expect(agents[0].tools).toEqual(['org_server_gw/toolA']);127});128129it('should remap tools with multiple slashes in server name', () => {130const agents: SweCustomAgent[] = [131makeAgent({ slug: 'agent1', tools: ['a/b/c/myTool'] }),132];133const mcpMappings: McpServerMappings = new Map([['a/b/c', 'ABC Server']]);134const mcpServers = makeMcpServers({ 'abc_gateway': { displayName: 'ABC Server' } });135136remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);137138expect(agents[0].tools).toEqual(['abc_gateway/myTool']);139});140141it('should also remap selectedAgent tools', () => {142const agents: SweCustomAgent[] = [];143const selectedAgent = makeAgent({ slug: 'selected', tools: ['server/tool1'] });144const mcpMappings: McpServerMappings = new Map([['server', 'Server Display']]);145const mcpServers = makeMcpServers({ 'gw': { displayName: 'Server Display' } });146147remapCustomAgentTools(agents, mcpMappings, mcpServers, selectedAgent);148149expect(selectedAgent.tools).toEqual(['gw/tool1']);150});151152it('should not remap tools without a slash', () => {153const agents: SweCustomAgent[] = [154makeAgent({ slug: 'agent1', tools: ['plainTool'] }),155];156const mcpMappings: McpServerMappings = new Map([['server', 'Display']]);157const mcpServers = makeMcpServers({ 'gw': { displayName: 'Display' } });158159remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);160161expect(agents[0].tools).toEqual(['plainTool']);162});163164it('should not remap when server name has no mapping', () => {165const agents: SweCustomAgent[] = [166makeAgent({ slug: 'agent1', tools: ['unknown/toolA'] }),167];168const mcpMappings: McpServerMappings = new Map([['other', 'Other Display']]);169const mcpServers = makeMcpServers({ 'gw': { displayName: 'Other Display' } });170171remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);172173expect(agents[0].tools).toEqual(['unknown/toolA']);174});175176it('should skip agents without tools', () => {177const agents: SweCustomAgent[] = [178makeAgent({ slug: 'agent1' }),179];180const mcpMappings: McpServerMappings = new Map([['server', 'Display']]);181const mcpServers = makeMcpServers({ 'gw': { displayName: 'Display' } });182183remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);184185expect(agents[0].tools).toBeUndefined();186});187188it('should do nothing when mcpServerMappings is empty', () => {189const agents: SweCustomAgent[] = [190makeAgent({ slug: 'agent1', tools: ['server/toolA'] }),191];192const mcpMappings: McpServerMappings = new Map();193const mcpServers = makeMcpServers({ 'gw': { displayName: 'Display' } });194195remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);196197expect(agents[0].tools).toEqual(['server/toolA']);198});199200it('should do nothing when mcpServers is undefined', () => {201const agents: SweCustomAgent[] = [202makeAgent({ slug: 'agent1', tools: ['server/toolA'] }),203];204const mcpMappings: McpServerMappings = new Map([['server', 'Display']]);205206remapCustomAgentTools(agents, mcpMappings, undefined, undefined);207208expect(agents[0].tools).toEqual(['server/toolA']);209});210211it('should fall back to direct display name lookup when no friendly mapping exists', () => {212const agents: SweCustomAgent[] = [213makeAgent({ slug: 'agent1', tools: ['Display Name/toolA'] }),214];215// No friendly → display mapping for "Display Name", but it matches a gateway displayName directly.216const mcpMappings: McpServerMappings = new Map([['other', 'Other']]);217const mcpServers = makeMcpServers({ 'gw': { displayName: 'Display Name' } });218219remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);220221expect(agents[0].tools).toEqual(['gw/toolA']);222});223});224225226