|
3 | 3 | * Licensed under the MIT License. See License.txt in the project root for license information. |
4 | 4 | *--------------------------------------------------------------------------------------------*/ |
5 | 5 |
|
6 | | -import type { Session } from '@github/copilot/sdk'; |
| 6 | +import type { Session, SessionOptions, SweCustomAgent } from '@github/copilot/sdk'; |
7 | 7 | import type { CancellationToken } from 'vscode'; |
8 | 8 | import { IAuthenticationService } from '../../../../platform/authentication/common/authentication'; |
9 | 9 | import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService'; |
10 | 10 | import { ILogService } from '../../../../platform/log/common/logService'; |
11 | 11 | import { IMcpService } from '../../../../platform/mcp/common/mcpService'; |
12 | 12 | import { createServiceIdentifier } from '../../../../util/common/services'; |
13 | 13 | import { Disposable, DisposableStore, IDisposable } from '../../../../util/vs/base/common/lifecycle'; |
| 14 | +import { hasKey } from '../../../../util/vs/base/common/types'; |
14 | 15 | import { URI } from '../../../../util/vs/base/common/uri'; |
15 | 16 | import { generateUuid } from '../../../../util/vs/base/common/uuid'; |
| 17 | +import type { LanguageModelToolInformation } from '../../../../vscodeTypes'; |
16 | 18 | import { GitHubMcpDefinitionProvider } from '../../../githubMcp/common/githubMcpDefinitionProvider'; |
17 | 19 |
|
18 | 20 | const toolInvalidCharRe = /[^a-z0-9_-]/gi; |
19 | 21 |
|
| 22 | +/** The user-facing display label of an MCP server (from VS Code settings). */ |
| 23 | +export type MCPDisplayName = string; |
| 24 | +/** The short server name as used in agent definition files (the prefix of `fullReferenceName`). */ |
| 25 | +export type MCPServerName = string; |
| 26 | + |
| 27 | +/** |
| 28 | + * A mapping from friendly MCP server names (as defined in custom agent files) |
| 29 | + * to VS Code MCP server display labels. |
| 30 | + */ |
| 31 | +export type McpServerMappings = Map<MCPServerName, MCPDisplayName>; |
| 32 | + |
20 | 33 | export type MCPServerConfig = NonNullable<Session['mcpServers']>[string]; |
21 | 34 |
|
22 | 35 | export interface ICopilotCLIMCPHandler { |
@@ -173,3 +186,85 @@ export class CopilotCLIMCPHandler implements ICopilotCLIMCPHandler { |
173 | 186 | } |
174 | 187 | } |
175 | 188 | } |
| 189 | + |
| 190 | +/** |
| 191 | + * Builds a mapping from friendly MCP server names (as defined in custom agent files) |
| 192 | + * to VS Code MCP server labels. |
| 193 | + * |
| 194 | + * Iterates through tools that have an MCP source (detected via structural typing using |
| 195 | + * {@link hasKey}) and a `fullReferenceName` in the format `<server name>/<tool name>`, |
| 196 | + * extracting the server name portion as the key and the source's `label` as the value. |
| 197 | + */ |
| 198 | +export function buildMcpServerMappings(tools: ReadonlyMap<LanguageModelToolInformation, boolean>): McpServerMappings { |
| 199 | + const mappings = new Map<string, string>(); |
| 200 | + for (const [tool] of tools) { |
| 201 | + if (!tool.source || !hasKey(tool.source, { name: true }) || !tool.fullReferenceName) { |
| 202 | + continue; |
| 203 | + } |
| 204 | + const slashIndex = tool.fullReferenceName.lastIndexOf('/'); |
| 205 | + if (slashIndex > 0) { |
| 206 | + const serverName = tool.fullReferenceName.substring(0, slashIndex); |
| 207 | + if (serverName && !mappings.has(serverName) && tool.source.label) { |
| 208 | + mappings.set(serverName, tool.source.label); |
| 209 | + } |
| 210 | + } |
| 211 | + } |
| 212 | + return mappings; |
| 213 | +} |
| 214 | + |
| 215 | +/** |
| 216 | + * Remaps tool references in custom agents from friendly MCP server names to gateway names. |
| 217 | + * |
| 218 | + * Agent definition files reference tools as `<friendly server name>/<tool name>`, but the SDK |
| 219 | + * expects `<gateway name>/<tool name>` where gateway names are the Record keys in the MCP |
| 220 | + * server config. |
| 221 | + * |
| 222 | + * @param customAgents The list of custom agents whose tools will be remapped in place. |
| 223 | + * @param mcpServerMappings Maps friendly server names (from agent files) → VS Code MCP display labels. |
| 224 | + * @param mcpServers The MCP server config, keyed by gateway name. |
| 225 | + * @param selectedAgent Optional selected agent to also remap. |
| 226 | + */ |
| 227 | +export function remapCustomAgentTools( |
| 228 | + customAgents: SweCustomAgent[], |
| 229 | + mcpServerMappings: McpServerMappings, |
| 230 | + mcpServers: SessionOptions['mcpServers'], |
| 231 | + selectedAgent: SweCustomAgent | undefined, |
| 232 | +): void { |
| 233 | + if (!mcpServerMappings.size || !mcpServers) { |
| 234 | + return; |
| 235 | + } |
| 236 | + // Build a map from display name → gateway name (the Record key in mcpServers). |
| 237 | + const displayNameToGatewayName = new Map<string, string>(); |
| 238 | + for (const [gatewayName, config] of Object.entries(mcpServers)) { |
| 239 | + if (config.displayName) { |
| 240 | + displayNameToGatewayName.set(config.displayName, gatewayName); |
| 241 | + } |
| 242 | + } |
| 243 | + |
| 244 | + const agentsToRemap = selectedAgent ? [...customAgents, selectedAgent] : customAgents; |
| 245 | + for (const agent of agentsToRemap) { |
| 246 | + if (!agent.tools?.length) { |
| 247 | + continue; |
| 248 | + } |
| 249 | + for (let i = 0; i < agent.tools.length; i++) { |
| 250 | + const tool = agent.tools[i]; |
| 251 | + const slashIndex = tool.lastIndexOf('/'); // Tool names cannot contain '/', so the last slash separates server from tool |
| 252 | + if (slashIndex < 1) { |
| 253 | + continue; |
| 254 | + } |
| 255 | + const serverName = tool.substring(0, slashIndex); |
| 256 | + const toolName = tool.substring(slashIndex + 1); |
| 257 | + if (!serverName || !toolName) { |
| 258 | + continue; |
| 259 | + } |
| 260 | + // First try: map through mcpServerMappings (friendly name → display name) then to gateway name. |
| 261 | + const displayName = mcpServerMappings.get(serverName); |
| 262 | + // Also try to look up the server name directly as a display name in the gateway map. |
| 263 | + const gatewayName = displayName ? displayNameToGatewayName.get(displayName) : displayNameToGatewayName.get(serverName); |
| 264 | + |
| 265 | + if (gatewayName) { |
| 266 | + agent.tools[i] = `${gatewayName}/${toolName}`; |
| 267 | + } |
| 268 | + } |
| 269 | + } |
| 270 | +} |
0 commit comments