豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 6fc43d8

Browse files
feat: Fix MCP server mapping integration in Copilot CLI sessions (#4709)
* feat: Fix MCP server mapping integration in Copilot CLI sessions * Updates * Updates * Updates --------- Co-authored-by: Raymond Zhao <7199958+rzhao271@users.noreply.github.com>
1 parent cf6e54a commit 6fc43d8

File tree

10 files changed

+374
-31
lines changed

10 files changed

+374
-31
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6368,5 +6368,5 @@
63686368
"node-gyp": "npm:node-gyp@10.3.1",
63696369
"zod": "3.25.76"
63706370
},
6371-
"vscodeCommit": "cdf4f2f548f0cd60f7804b6837a0512a15a02889"
6371+
"vscodeCommit": "e1ab55e1215fa458d1aeabcf8afd1a9478d2c47f"
63726372
}

src/extension/chatSessions/copilotcli/node/copilotCli.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { IInstantiationService } from '../../../../util/vs/platform/instantiatio
2525
import { IChatPromptFileService } from '../../common/chatPromptFileService';
2626
import { getWorkingDirectory, IWorkspaceInfo } from '../../common/workspaceInfo';
2727
import { getCopilotLogger } from './logger';
28+
import { remapCustomAgentTools, type McpServerMappings } from './mcpHandler';
2829
import { ensureNodePtyShim } from './nodePtyShim';
2930
import { ensureRipgrepShim } from './ripgrepShim';
3031

@@ -60,7 +61,7 @@ export class CopilotCLISessionOptions {
6061
return this.agent?.name;
6162
}
6263

63-
public toSessionOptions(): Readonly<SessionOptions> {
64+
public toSessionOptions(mcpServerMappings?: McpServerMappings): Readonly<SessionOptions> {
6465
const allOptions: SessionOptions = {
6566
clientName: 'vscode',
6667
};
@@ -84,6 +85,9 @@ export class CopilotCLISessionOptions {
8485
if (this.skillLocations) {
8586
allOptions.skillDirectories = this.skillLocations.map(uri => uri.fsPath);
8687
}
88+
if (mcpServerMappings?.size && this.customAgents && this.mcpServers) {
89+
remapCustomAgentTools(this.customAgents, mcpServerMappings, this.mcpServers, this.agent);
90+
}
8791
if (this.agent) {
8892
allOptions.selectedCustomAgent = this.agent;
8993
}
@@ -334,7 +338,7 @@ export class CopilotCLIAgents extends Disposable implements ICopilotCLIAgents {
334338
});
335339
}
336340

337-
return this._agentsPromise;
341+
return this._agentsPromise.then(agents => agents.map(agent => this.cloneAgent(agent)));
338342
}
339343

340344
async getAgentsImpl(): Promise<Readonly<SweCustomAgent>[]> {

src/extension/chatSessions/copilotcli/node/copilotcliSessionService.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import { CopilotCLISessionOptions, ICopilotCLIAgents, ICopilotCLISDK } from './c
4646
import { CopilotCliBridgeSpanProcessor } from './copilotCliBridgeSpanProcessor';
4747
import { CopilotCLISession, ICopilotCLISession } from './copilotcliSession';
4848
import { ICopilotCLISkills } from './copilotCLISkills';
49-
import { ICopilotCLIMCPHandler } from './mcpHandler';
49+
import { ICopilotCLIMCPHandler, McpServerMappings } from './mcpHandler';
5050
import { IChatDebugFileLoggerService } from '../../../../platform/chat/common/chatDebugFileLoggerService';
5151

5252
const COPILOT_CLI_WORKSPACE_JSON_FILE_KEY = 'github.copilot.cli.workspaceSessionFile';
@@ -84,8 +84,8 @@ export interface ICopilotCLISessionService {
8484
renameSession(sessionId: string, title: string): Promise<void>;
8585

8686
// Session wrapper tracking
87-
getSession(options: { sessionId: string; model?: string; workspaceInfo: IWorkspaceInfo; readonly: boolean; agent?: SweCustomAgent }, token: CancellationToken): Promise<IReference<ICopilotCLISession> | undefined>;
88-
createSession(options: { model?: string; workspaceInfo: IWorkspaceInfo; agent?: SweCustomAgent; sessionId?: string }, token: CancellationToken): Promise<IReference<ICopilotCLISession>>;
87+
getSession(options: { sessionId: string; model?: string; workspaceInfo: IWorkspaceInfo; readonly: boolean; agent?: SweCustomAgent; mcpServerMappings?: McpServerMappings }, token: CancellationToken): Promise<IReference<ICopilotCLISession> | undefined>;
88+
createSession(options: { model?: string; workspaceInfo: IWorkspaceInfo; agent?: SweCustomAgent; sessionId?: string; mcpServerMappings?: McpServerMappings }, token: CancellationToken): Promise<IReference<ICopilotCLISession>>;
8989
forkSession(sessionId: string, requestId: string | undefined, options: { workspaceInfo: IWorkspaceInfo }, token: CancellationToken): Promise<string>;
9090
tryGetPartialSesionHistory(sessionId: string): Promise<readonly (ChatRequestTurn2 | ChatResponseTurn2)[] | undefined>;
9191
}
@@ -511,13 +511,13 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
511511
}
512512
}
513513

514-
public async createSession({ model, workspaceInfo, agent, sessionId }: { model?: string; workspaceInfo: IWorkspaceInfo; agent?: SweCustomAgent; sessionId?: string }, token: CancellationToken): Promise<RefCountedSession> {
514+
public async createSession({ model, workspaceInfo, agent, sessionId, mcpServerMappings }: { model?: string; workspaceInfo: IWorkspaceInfo; agent?: SweCustomAgent; sessionId?: string; mcpServerMappings?: McpServerMappings }, token: CancellationToken): Promise<RefCountedSession> {
515515
const { mcpConfig: mcpServers, disposable: mcpGateway } = await this.mcpHandler.loadMcpConfig();
516516
try {
517517
const copilotUrl = this.configurationService.getConfig(ConfigKey.Shared.DebugOverrideProxyUrl) || undefined;
518518
const options = await this.createSessionsOptions({ model, workspaceInfo, mcpServers, agent, copilotUrl });
519519
const sessionManager = await raceCancellationError(this.getSessionManager(), token);
520-
const sdkSession = await sessionManager.createSession({ ...options.toSessionOptions(), sessionId });
520+
const sdkSession = await sessionManager.createSession({ ...options.toSessionOptions(mcpServerMappings), sessionId });
521521
this._newSessionIds.delete(sdkSession.sessionId);
522522
// After the first session creation, the SDK's OTel TracerProvider is
523523
// initialized. Install the bridge processor so SDK-native spans flow
@@ -637,7 +637,7 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
637637
return new CopilotCLISessionOptions({ ...options, customAgents, skillLocations }, this.logService);
638638
}
639639

640-
public async getSession({ sessionId, model, workspaceInfo, readonly, agent }: { sessionId: string; model?: string; workspaceInfo: IWorkspaceInfo; readonly: boolean; agent?: SweCustomAgent }, token: CancellationToken): Promise<RefCountedSession | undefined> {
640+
public async getSession({ sessionId, model, workspaceInfo, readonly, agent, mcpServerMappings }: { sessionId: string; model?: string; workspaceInfo: IWorkspaceInfo; readonly: boolean; agent?: SweCustomAgent; mcpServerMappings?: McpServerMappings }, token: CancellationToken): Promise<RefCountedSession | undefined> {
641641
// https://github.com/microsoft/vscode/issues/276573
642642
const lock = this.sessionMutexForGetSession.get(sessionId) ?? new Mutex();
643643
this.sessionMutexForGetSession.set(sessionId, lock);
@@ -668,7 +668,7 @@ export class CopilotCLISessionService extends Disposable implements ICopilotCLIS
668668
const copilotUrl = this.configurationService.getConfig(ConfigKey.Shared.DebugOverrideProxyUrl) || undefined;
669669
const options = await this.createSessionsOptions({ model, agent, workspaceInfo, mcpServers, copilotUrl }, readonly);
670670

671-
const sdkSession = await sessionManager.getSession({ ...options.toSessionOptions(), sessionId }, !readonly);
671+
const sdkSession = await sessionManager.getSession({ ...options.toSessionOptions(mcpServerMappings), sessionId }, !readonly);
672672
if (!sdkSession) {
673673
this.logService.error(`[CopilotCLISession] CopilotCLI failed to get session ${sessionId}.`);
674674
return undefined;

src/extension/chatSessions/copilotcli/node/mcpHandler.ts

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,33 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import type { Session } from '@github/copilot/sdk';
6+
import type { Session, SessionOptions, SweCustomAgent } from '@github/copilot/sdk';
77
import type { CancellationToken } from 'vscode';
88
import { IAuthenticationService } from '../../../../platform/authentication/common/authentication';
99
import { ConfigKey, IConfigurationService } from '../../../../platform/configuration/common/configurationService';
1010
import { ILogService } from '../../../../platform/log/common/logService';
1111
import { IMcpService } from '../../../../platform/mcp/common/mcpService';
1212
import { createServiceIdentifier } from '../../../../util/common/services';
1313
import { Disposable, DisposableStore, IDisposable } from '../../../../util/vs/base/common/lifecycle';
14+
import { hasKey } from '../../../../util/vs/base/common/types';
1415
import { URI } from '../../../../util/vs/base/common/uri';
1516
import { generateUuid } from '../../../../util/vs/base/common/uuid';
17+
import type { LanguageModelToolInformation } from '../../../../vscodeTypes';
1618
import { GitHubMcpDefinitionProvider } from '../../../githubMcp/common/githubMcpDefinitionProvider';
1719

1820
const toolInvalidCharRe = /[^a-z0-9_-]/gi;
1921

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+
2033
export type MCPServerConfig = NonNullable<Session['mcpServers']>[string];
2134

2235
export interface ICopilotCLIMCPHandler {
@@ -173,3 +186,85 @@ export class CopilotCLIMCPHandler implements ICopilotCLIMCPHandler {
173186
}
174187
}
175188
}
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

Comments
 (0)