mirror of
https://github.com/microsoft/vscode.git
synced 2026-08-18 05:06:39 +01:00
agentHost: exclude Copilot Chat GitHub MCP sync (#329623)
Copilot CLI agent hosts already configure the SDK's built-in GitHub MCP server with form deferral disabled. Avoid syncing the extension-contributed duplicate so it cannot override that behavior, while retaining user servers and non-Copilot agent-host support.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+22
-5
@@ -7,8 +7,10 @@ import { CancellationToken } from '../../../../../../base/common/cancellation.js
|
||||
import { Iterable } from '../../../../../../base/common/iterator.js';
|
||||
import { basename, isEqualOrParent } from '../../../../../../base/common/resources.js';
|
||||
import { URI } from '../../../../../../base/common/uri.js';
|
||||
import { parseRemoteAgentHostHarness } from '../../../../../../platform/agentHost/common/agentHostSessionType.js';
|
||||
import { type AgentCustomization, CustomizationType, type URI as ProtocolURI } from '../../../../../../platform/agentHost/common/state/protocol/state.js';
|
||||
import { customizationId, type ClientPluginCustomization } from '../../../../../../platform/agentHost/common/state/sessionState.js';
|
||||
import { ExtensionIdentifier } from '../../../../../../platform/extensions/common/extensions.js';
|
||||
import { IMcpServerConfiguration, McpServerType } from '../../../../../../platform/mcp/common/mcpPlatformTypes.js';
|
||||
import { AICustomizationSource, AICustomizationSources } from '../../../common/aiCustomizationWorkspaceService.js';
|
||||
import { PromptsType } from '../../../common/promptSyntax/promptTypes.js';
|
||||
@@ -17,7 +19,7 @@ import { type ICustomizationSyncProvider } from '../../../common/customizationHa
|
||||
import { IAgentPlugin, IAgentPluginService } from '../../../common/plugins/agentPluginService.js';
|
||||
import { isContributionEnabled } from '../../../common/enablement.js';
|
||||
import { MCP_PLUGIN_COLLECTION_ID_PREFIX } from '../../../../mcp/common/discovery/pluginMcpDiscovery.js';
|
||||
import { IMcpService, McpCollectionDefinition, McpServerLaunch, McpServerTransportType } from '../../../../mcp/common/mcpTypes.js';
|
||||
import { extensionPrefixedIdentifier, IMcpService, McpCollectionDefinition, McpServerLaunch, McpServerTransportType } from '../../../../mcp/common/mcpTypes.js';
|
||||
import { IConfigurationResolverService } from '../../../../../services/configurationResolver/common/configurationResolver.js';
|
||||
import { ConfigurationResolverExpression } from '../../../../../services/configurationResolver/common/configurationResolverExpression.js';
|
||||
import { IWorkspaceFolderData } from '../../../../../../platform/workspace/common/workspace.js';
|
||||
@@ -27,6 +29,13 @@ import { IFileService } from '../../../../../../platform/files/common/files.js';
|
||||
import { isDefined } from '../../../../../../base/common/types.js';
|
||||
import { PromptFileParser } from '../../../common/promptSyntax/promptFileParser.js';
|
||||
|
||||
const COPILOT_CHAT_EXTENSION_ID = 'github.copilot-chat';
|
||||
const COPILOT_CHAT_GITHUB_MCP_COLLECTION_ID = extensionPrefixedIdentifier(new ExtensionIdentifier(COPILOT_CHAT_EXTENSION_ID), 'github');
|
||||
|
||||
function hasBuiltInGitHubMcpServer(sessionType: string): boolean {
|
||||
return sessionType === AGENT_HOST_COPILOT_CLI_SESSION_TYPE || parseRemoteAgentHostHarness(sessionType) === 'copilotcli';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt types that participate in auto-sync to an agent host harness.
|
||||
*
|
||||
@@ -289,7 +298,9 @@ export function shouldSyncWorkspaceDotMcp(sessionType: string, workspaceFolderCo
|
||||
* exception is `.vscode/mcp.json`, which the agent host does not discover
|
||||
* (despite what the SDK's `enableConfigDiscovery` docs imply) — those are
|
||||
* synced, but only when their config can be resolved without requiring user
|
||||
* interaction.
|
||||
* interaction. For Copilot CLI agent-host sessions, the Copilot Chat
|
||||
* extension's GitHub MCP provider is excluded because the SDK supplies its own
|
||||
* built-in GitHub server.
|
||||
*
|
||||
* When {@link includeWorkspaceDotMcp} is `true` (multi-root Copilot Agent Host
|
||||
* gate), folder-root `.mcp.json` servers are additionally synced so servers
|
||||
@@ -299,7 +310,7 @@ export function shouldSyncWorkspaceDotMcp(sessionType: string, workspaceFolderCo
|
||||
* are passed as-is: `.mcp.json` supports no `${...}` variables and already
|
||||
* carries an explicit absolute `cwd`.
|
||||
*/
|
||||
export async function collectNonPluginMcpServers(mcpService: IMcpService, configurationResolverService: IConfigurationResolverService, includeWorkspaceDotMcp: boolean): Promise<ISyncableMcpServer[]> {
|
||||
export async function collectNonPluginMcpServers(mcpService: IMcpService, configurationResolverService: IConfigurationResolverService, sessionType: string, includeWorkspaceDotMcp: boolean): Promise<ISyncableMcpServer[]> {
|
||||
const result: ISyncableMcpServer[] = [];
|
||||
for (const server of mcpService.servers.get()) {
|
||||
if (server.collection.id.startsWith(MCP_PLUGIN_COLLECTION_ID_PREFIX)) {
|
||||
@@ -314,11 +325,17 @@ export async function collectNonPluginMcpServers(mcpService: IMcpService, config
|
||||
if (!launch) {
|
||||
continue;
|
||||
}
|
||||
const collection = definitions.collection;
|
||||
if (hasBuiltInGitHubMcpServer(sessionType)
|
||||
&& collection?.id === COPILOT_CHAT_GITHUB_MCP_COLLECTION_ID
|
||||
&& collection.source instanceof ExtensionIdentifier
|
||||
&& ExtensionIdentifier.equals(collection.source, COPILOT_CHAT_EXTENSION_ID)) {
|
||||
continue;
|
||||
}
|
||||
let configuration = launchToMcpServerConfiguration(launch);
|
||||
if (!configuration) {
|
||||
continue;
|
||||
}
|
||||
const collection = definitions.collection;
|
||||
if (collection && McpCollectionDefinition.isWorkspaceDiscovered(collection)) {
|
||||
if (McpCollectionDefinition.isVscodeMcpJson(collection)) {
|
||||
const resolved = await resolveConfigurationForSync(configurationResolverService, definition.variableReplacement?.folder, configuration);
|
||||
@@ -439,7 +456,7 @@ export async function resolveCustomizationRefs(
|
||||
}
|
||||
|
||||
const refs: Promise<ClientPluginCustomization | undefined>[] = [...pluginRefs.values()];
|
||||
const mcpServers = await collectNonPluginMcpServers(mcpService, configurationResolverService, includeWorkspaceDotMcp);
|
||||
const mcpServers = await collectNonPluginMcpServers(mcpService, configurationResolverService, sessionType, includeWorkspaceDotMcp);
|
||||
if (looseFiles.length > 0 || mcpServers.length > 0) {
|
||||
refs.push(bundler.bundle(looseFiles, mcpServers).then(r => r?.ref));
|
||||
}
|
||||
|
||||
+81
-3
@@ -12,6 +12,7 @@ import { URI } from '../../../../../../base/common/uri.js';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
|
||||
import { PluginFormat } from '../../../../../../platform/agentPlugins/common/pluginParsers.js';
|
||||
import { ConfigurationTarget } from '../../../../../../platform/configuration/common/configuration.js';
|
||||
import { ExtensionIdentifier } from '../../../../../../platform/extensions/common/extensions.js';
|
||||
import { IFileService } from '../../../../../../platform/files/common/files.js';
|
||||
import { McpServerType } from '../../../../../../platform/mcp/common/mcpPlatformTypes.js';
|
||||
import { resolveCustomizationRefs, resolveLocalCustomAgents, shouldSyncWorkspaceDotMcp } from '../../../browser/agentSessions/agentHost/agentHostLocalCustomizations.js';
|
||||
@@ -114,9 +115,9 @@ function makeFileService(stats: ReadonlyMap<string, { mtime: number }> = new Map
|
||||
} as unknown as IFileService;
|
||||
}
|
||||
|
||||
function makeMcpServer(options: { id: string; collectionId: string; label?: string; enabled?: boolean; launch?: McpServerLaunch | undefined; configTarget?: ConfigurationTarget }): IMcpServer {
|
||||
const { id, collectionId, label = id, enabled = true, launch, configTarget = ConfigurationTarget.USER } = options;
|
||||
const collection = { id: collectionId, label: collectionId, order: 0, configTarget } as unknown as McpCollectionDefinition;
|
||||
function makeMcpServer(options: { id: string; collectionId: string; label?: string; enabled?: boolean; launch?: McpServerLaunch | undefined; configTarget?: ConfigurationTarget; collectionSource?: ExtensionIdentifier }): IMcpServer {
|
||||
const { id, collectionId, label = id, enabled = true, launch, configTarget = ConfigurationTarget.USER, collectionSource } = options;
|
||||
const collection = { id: collectionId, label: collectionId, order: 0, configTarget, source: collectionSource } as unknown as McpCollectionDefinition;
|
||||
const definitions = observableValue('definitions', { server: launch ? { launch } : undefined, collection });
|
||||
return {
|
||||
definition: { id, label },
|
||||
@@ -143,6 +144,16 @@ const stdioLaunch: McpServerLaunch = {
|
||||
sandbox: undefined,
|
||||
};
|
||||
|
||||
function makeCopilotChatGitHubMcpServer(): IMcpServer {
|
||||
return makeMcpServer({
|
||||
id: 'github.copilot-chat/GitHub',
|
||||
collectionId: 'github.copilot-chat/github',
|
||||
label: 'GitHub',
|
||||
launch: stdioLaunch,
|
||||
collectionSource: new ExtensionIdentifier('GitHub.copilot-chat'),
|
||||
});
|
||||
}
|
||||
|
||||
const stdioLaunchWithInput: McpServerLaunch = {
|
||||
type: McpServerTransportType.Stdio,
|
||||
command: 'my-server',
|
||||
@@ -480,6 +491,73 @@ suite('resolveCustomizationRefs - built-in skills', () => {
|
||||
assert.strictEqual(refs[0].name, 'Open Plugin');
|
||||
});
|
||||
|
||||
test('excludes the Copilot Chat GitHub MCP provider without excluding user or other extension servers', async () => {
|
||||
const bundler = new FakeBundler();
|
||||
const mcpService = makeMcpService([
|
||||
makeCopilotChatGitHubMcpServer(),
|
||||
makeMcpServer({ id: 'user.GitHub', collectionId: 'user', label: 'GitHub', launch: stdioLaunch }),
|
||||
makeMcpServer({
|
||||
id: 'publisher.extension/server',
|
||||
collectionId: 'publisher.extension/provider',
|
||||
label: 'extension-server',
|
||||
launch: stdioLaunch,
|
||||
collectionSource: new ExtensionIdentifier('publisher.extension'),
|
||||
}),
|
||||
]);
|
||||
|
||||
await resolveCustomizationRefs(
|
||||
makeFileService(),
|
||||
makePromptsService(new Map()),
|
||||
new FakeSyncProvider(),
|
||||
makeAgentPluginService(),
|
||||
mcpService,
|
||||
makeConfigurationResolverService(),
|
||||
bundler as unknown as SyncedCustomizationBundler,
|
||||
'agent-host-copilotcli',
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(bundler.receivedMcp, [[
|
||||
{ name: 'GitHub', configuration: { type: McpServerType.LOCAL, command: 'my-server', args: ['--flag'], env: undefined, envFile: undefined, cwd: undefined } },
|
||||
{ name: 'extension-server', configuration: { type: McpServerType.LOCAL, command: 'my-server', args: ['--flag'], env: undefined, envFile: undefined, cwd: undefined } },
|
||||
]]);
|
||||
});
|
||||
|
||||
test('excludes the Copilot Chat GitHub MCP provider from remote Copilot agent hosts', async () => {
|
||||
const bundler = new FakeBundler();
|
||||
|
||||
await resolveCustomizationRefs(
|
||||
makeFileService(),
|
||||
makePromptsService(new Map()),
|
||||
new FakeSyncProvider(),
|
||||
makeAgentPluginService(),
|
||||
makeMcpService([makeCopilotChatGitHubMcpServer()]),
|
||||
makeConfigurationResolverService(),
|
||||
bundler as unknown as SyncedCustomizationBundler,
|
||||
'remote-test-copilotcli',
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(bundler.receivedMcp, []);
|
||||
});
|
||||
|
||||
test('retains the Copilot Chat GitHub MCP provider for agent hosts without a built-in server', async () => {
|
||||
const bundler = new FakeBundler();
|
||||
|
||||
await resolveCustomizationRefs(
|
||||
makeFileService(),
|
||||
makePromptsService(new Map()),
|
||||
new FakeSyncProvider(),
|
||||
makeAgentPluginService(),
|
||||
makeMcpService([makeCopilotChatGitHubMcpServer()]),
|
||||
makeConfigurationResolverService(),
|
||||
bundler as unknown as SyncedCustomizationBundler,
|
||||
'agent-host-claude',
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(bundler.receivedMcp, [[
|
||||
{ name: 'GitHub', configuration: { type: McpServerType.LOCAL, command: 'my-server', args: ['--flag'], env: undefined, envFile: undefined, cwd: undefined } },
|
||||
]]);
|
||||
});
|
||||
|
||||
test('excludes plugin-sourced MCP servers from the bundle', async () => {
|
||||
const bundler = new FakeBundler();
|
||||
const mcpService = makeMcpService([
|
||||
|
||||
Reference in New Issue
Block a user