Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/chatSessions/copilotcli/node/test/mcpHandler.spec.ts
13406 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import type { SweCustomAgent } from '@github/copilot/sdk';
7
import { describe, expect, it } from 'vitest';
8
import type { LanguageModelToolInformation } from '../../../../../vscodeTypes';
9
import { buildMcpServerMappings, type MCPServerConfig, type McpServerMappings, remapCustomAgentTools } from '../mcpHandler';
10
11
function makeAgent(partial: { slug: string; tools?: string[] }): SweCustomAgent {
12
return partial as unknown as SweCustomAgent;
13
}
14
15
function makeTool(fullReferenceName: string | undefined, sourceLabel?: string): LanguageModelToolInformation {
16
return {
17
name: fullReferenceName ?? 'no-ref',
18
fullReferenceName,
19
source: sourceLabel ? { label: sourceLabel, name: sourceLabel } : undefined,
20
} as unknown as LanguageModelToolInformation;
21
}
22
23
function makeToolsMap(...entries: [LanguageModelToolInformation, boolean][]): ReadonlyMap<LanguageModelToolInformation, boolean> {
24
return new Map(entries);
25
}
26
27
describe('buildMcpServerMappings', () => {
28
it('should extract simple server name from fullReferenceName', () => {
29
const tools = makeToolsMap(
30
[makeTool('myServer/myTool', 'My Server'), true],
31
);
32
const mappings = buildMcpServerMappings(tools);
33
expect(mappings.get('myServer')).toBe('My Server');
34
});
35
36
it('should use the last slash to split server name from tool name', () => {
37
const tools = makeToolsMap(
38
[makeTool('scope/myServer/myTool', 'Scoped Server'), true],
39
);
40
const mappings = buildMcpServerMappings(tools);
41
expect(mappings.get('scope/myServer')).toBe('Scoped Server');
42
expect(mappings.has('scope')).toBe(false);
43
});
44
45
it('should handle server names with multiple slashes', () => {
46
const tools = makeToolsMap(
47
[makeTool('a/b/c/toolName', 'Deep Server'), true],
48
);
49
const mappings = buildMcpServerMappings(tools);
50
expect(mappings.get('a/b/c')).toBe('Deep Server');
51
});
52
53
it('should skip tools without source', () => {
54
const tools = makeToolsMap(
55
[makeTool('server/tool'), true],
56
);
57
const mappings = buildMcpServerMappings(tools);
58
expect(mappings.size).toBe(0);
59
});
60
61
it('should skip tools without fullReferenceName', () => {
62
const tools = makeToolsMap(
63
[makeTool(undefined, 'Some Server'), true],
64
);
65
const mappings = buildMcpServerMappings(tools);
66
expect(mappings.size).toBe(0);
67
});
68
69
it('should skip tools with no slash in fullReferenceName', () => {
70
const tools = makeToolsMap(
71
[makeTool('toolOnly', 'Server'), true],
72
);
73
const mappings = buildMcpServerMappings(tools);
74
expect(mappings.size).toBe(0);
75
});
76
77
it('should not overwrite existing mappings for the same server name', () => {
78
const tools = makeToolsMap(
79
[makeTool('server/tool1', 'First Label'), true],
80
[makeTool('server/tool2', 'Second Label'), true],
81
);
82
const mappings = buildMcpServerMappings(tools);
83
expect(mappings.get('server')).toBe('First Label');
84
});
85
86
it('should handle multiple different servers', () => {
87
const tools = makeToolsMap(
88
[makeTool('serverA/tool1', 'Server A'), true],
89
[makeTool('serverB/tool2', 'Server B'), true],
90
);
91
const mappings = buildMcpServerMappings(tools);
92
expect(mappings.get('serverA')).toBe('Server A');
93
expect(mappings.get('serverB')).toBe('Server B');
94
});
95
});
96
97
describe('remapCustomAgentTools', () => {
98
function makeMcpServers(entries: Record<string, { displayName?: string }>): Record<string, MCPServerConfig> {
99
const result: Record<string, MCPServerConfig> = {};
100
for (const [key, val] of Object.entries(entries)) {
101
result[key] = { type: 'http' as const, url: 'http://localhost', tools: ['*'], ...val };
102
}
103
return result;
104
}
105
106
it('should remap simple server/tool references', () => {
107
const agents: SweCustomAgent[] = [
108
makeAgent({ slug: 'agent1', tools: ['friendlyName/toolA'] }),
109
];
110
const mcpMappings: McpServerMappings = new Map([['friendlyName', 'Display Name']]);
111
const mcpServers = makeMcpServers({ 'gateway_name': { displayName: 'Display Name' } });
112
113
remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);
114
115
expect(agents[0].tools).toEqual(['gateway_name/toolA']);
116
});
117
118
it('should remap tools with slashes in server name using last slash', () => {
119
const agents: SweCustomAgent[] = [
120
makeAgent({ slug: 'agent1', tools: ['org/server/toolA'] }),
121
];
122
const mcpMappings: McpServerMappings = new Map([['org/server', 'Org Server Display']]);
123
const mcpServers = makeMcpServers({ 'org_server_gw': { displayName: 'Org Server Display' } });
124
125
remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);
126
127
expect(agents[0].tools).toEqual(['org_server_gw/toolA']);
128
});
129
130
it('should remap tools with multiple slashes in server name', () => {
131
const agents: SweCustomAgent[] = [
132
makeAgent({ slug: 'agent1', tools: ['a/b/c/myTool'] }),
133
];
134
const mcpMappings: McpServerMappings = new Map([['a/b/c', 'ABC Server']]);
135
const mcpServers = makeMcpServers({ 'abc_gateway': { displayName: 'ABC Server' } });
136
137
remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);
138
139
expect(agents[0].tools).toEqual(['abc_gateway/myTool']);
140
});
141
142
it('should also remap selectedAgent tools', () => {
143
const agents: SweCustomAgent[] = [];
144
const selectedAgent = makeAgent({ slug: 'selected', tools: ['server/tool1'] });
145
const mcpMappings: McpServerMappings = new Map([['server', 'Server Display']]);
146
const mcpServers = makeMcpServers({ 'gw': { displayName: 'Server Display' } });
147
148
remapCustomAgentTools(agents, mcpMappings, mcpServers, selectedAgent);
149
150
expect(selectedAgent.tools).toEqual(['gw/tool1']);
151
});
152
153
it('should not remap tools without a slash', () => {
154
const agents: SweCustomAgent[] = [
155
makeAgent({ slug: 'agent1', tools: ['plainTool'] }),
156
];
157
const mcpMappings: McpServerMappings = new Map([['server', 'Display']]);
158
const mcpServers = makeMcpServers({ 'gw': { displayName: 'Display' } });
159
160
remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);
161
162
expect(agents[0].tools).toEqual(['plainTool']);
163
});
164
165
it('should not remap when server name has no mapping', () => {
166
const agents: SweCustomAgent[] = [
167
makeAgent({ slug: 'agent1', tools: ['unknown/toolA'] }),
168
];
169
const mcpMappings: McpServerMappings = new Map([['other', 'Other Display']]);
170
const mcpServers = makeMcpServers({ 'gw': { displayName: 'Other Display' } });
171
172
remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);
173
174
expect(agents[0].tools).toEqual(['unknown/toolA']);
175
});
176
177
it('should skip agents without tools', () => {
178
const agents: SweCustomAgent[] = [
179
makeAgent({ slug: 'agent1' }),
180
];
181
const mcpMappings: McpServerMappings = new Map([['server', 'Display']]);
182
const mcpServers = makeMcpServers({ 'gw': { displayName: 'Display' } });
183
184
remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);
185
186
expect(agents[0].tools).toBeUndefined();
187
});
188
189
it('should do nothing when mcpServerMappings is empty', () => {
190
const agents: SweCustomAgent[] = [
191
makeAgent({ slug: 'agent1', tools: ['server/toolA'] }),
192
];
193
const mcpMappings: McpServerMappings = new Map();
194
const mcpServers = makeMcpServers({ 'gw': { displayName: 'Display' } });
195
196
remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);
197
198
expect(agents[0].tools).toEqual(['server/toolA']);
199
});
200
201
it('should do nothing when mcpServers is undefined', () => {
202
const agents: SweCustomAgent[] = [
203
makeAgent({ slug: 'agent1', tools: ['server/toolA'] }),
204
];
205
const mcpMappings: McpServerMappings = new Map([['server', 'Display']]);
206
207
remapCustomAgentTools(agents, mcpMappings, undefined, undefined);
208
209
expect(agents[0].tools).toEqual(['server/toolA']);
210
});
211
212
it('should fall back to direct display name lookup when no friendly mapping exists', () => {
213
const agents: SweCustomAgent[] = [
214
makeAgent({ slug: 'agent1', tools: ['Display Name/toolA'] }),
215
];
216
// No friendly → display mapping for "Display Name", but it matches a gateway displayName directly.
217
const mcpMappings: McpServerMappings = new Map([['other', 'Other']]);
218
const mcpServers = makeMcpServers({ 'gw': { displayName: 'Display Name' } });
219
220
remapCustomAgentTools(agents, mcpMappings, mcpServers, undefined);
221
222
expect(agents[0].tools).toEqual(['gw/toolA']);
223
});
224
});
225
226