Path: blob/main/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts
3296 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 { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';6import { ILocalMcpServer, IMcpManagementService, IGalleryMcpServer, InstallOptions, InstallMcpServerEvent, UninstallMcpServerEvent, DidUninstallMcpServerEvent, InstallMcpServerResult, IInstallableMcpServer, IMcpGalleryService, UninstallOptions, IAllowedMcpServersService } from '../../../../platform/mcp/common/mcpManagement.js';7import { IInstantiationService, refineServiceDecorator } from '../../../../platform/instantiation/common/instantiation.js';8import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js';9import { Emitter, Event } from '../../../../base/common/event.js';10import { IMcpResourceScannerService, McpResourceTarget } from '../../../../platform/mcp/common/mcpResourceScannerService.js';11import { isWorkspaceFolder, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent } from '../../../../platform/workspace/common/workspace.js';12import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';13import { MCP_CONFIGURATION_KEY, WORKSPACE_STANDALONE_CONFIGURATIONS } from '../../configuration/common/configuration.js';14import { ILogService } from '../../../../platform/log/common/log.js';15import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js';16import { URI } from '../../../../base/common/uri.js';17import { ConfigurationTarget } from '../../../../platform/configuration/common/configuration.js';18import { IChannel } from '../../../../base/parts/ipc/common/ipc.js';19import { McpManagementChannelClient } from '../../../../platform/mcp/common/mcpManagementIpc.js';20import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';21import { IRemoteUserDataProfilesService } from '../../userDataProfile/common/remoteUserDataProfiles.js';22import { AbstractMcpManagementService, AbstractMcpResourceManagementService, ILocalMcpServerInfo } from '../../../../platform/mcp/common/mcpManagementService.js';23import { IFileService } from '../../../../platform/files/common/files.js';24import { ResourceMap } from '../../../../base/common/map.js';2526export const USER_CONFIG_ID = 'usrlocal';27export const REMOTE_USER_CONFIG_ID = 'usrremote';28export const WORKSPACE_CONFIG_ID = 'workspace';29export const WORKSPACE_FOLDER_CONFIG_ID_PREFIX = 'ws';3031export interface IWorkbencMcpServerInstallOptions extends InstallOptions {32target?: ConfigurationTarget | IWorkspaceFolder;33}3435export const enum LocalMcpServerScope {36User = 'user',37RemoteUser = 'remoteUser',38Workspace = 'workspace',39}4041export interface IWorkbenchLocalMcpServer extends ILocalMcpServer {42readonly id: string;43readonly scope: LocalMcpServerScope;44}4546export interface InstallWorkbenchMcpServerEvent extends InstallMcpServerEvent {47readonly scope: LocalMcpServerScope;48}4950export interface IWorkbenchMcpServerInstallResult extends InstallMcpServerResult {51readonly local?: IWorkbenchLocalMcpServer;52}5354export interface UninstallWorkbenchMcpServerEvent extends UninstallMcpServerEvent {55readonly scope: LocalMcpServerScope;56}5758export interface DidUninstallWorkbenchMcpServerEvent extends DidUninstallMcpServerEvent {59readonly scope: LocalMcpServerScope;60}6162export const IWorkbenchMcpManagementService = refineServiceDecorator<IMcpManagementService, IWorkbenchMcpManagementService>(IMcpManagementService);63export interface IWorkbenchMcpManagementService extends IMcpManagementService {64readonly _serviceBrand: undefined;6566readonly onInstallMcpServerInCurrentProfile: Event<InstallWorkbenchMcpServerEvent>;67readonly onDidInstallMcpServersInCurrentProfile: Event<readonly IWorkbenchMcpServerInstallResult[]>;68readonly onDidUpdateMcpServersInCurrentProfile: Event<readonly IWorkbenchMcpServerInstallResult[]>;69readonly onUninstallMcpServerInCurrentProfile: Event<UninstallWorkbenchMcpServerEvent>;70readonly onDidUninstallMcpServerInCurrentProfile: Event<DidUninstallWorkbenchMcpServerEvent>;71readonly onDidChangeProfile: Event<void>;7273getInstalled(): Promise<IWorkbenchLocalMcpServer[]>;74install(server: IInstallableMcpServer | URI, options?: IWorkbencMcpServerInstallOptions): Promise<IWorkbenchLocalMcpServer>;75installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise<IWorkbenchLocalMcpServer>;76updateMetadata(local: ILocalMcpServer, server: IGalleryMcpServer, profileLocation?: URI): Promise<IWorkbenchLocalMcpServer>;77}7879export class WorkbenchMcpManagementService extends AbstractMcpManagementService implements IWorkbenchMcpManagementService {8081private _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>());82readonly onInstallMcpServer = this._onInstallMcpServer.event;8384private _onDidInstallMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());85readonly onDidInstallMcpServers = this._onDidInstallMcpServers.event;8687private _onDidUpdateMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());88readonly onDidUpdateMcpServers = this._onDidUpdateMcpServers.event;8990private _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>());91readonly onUninstallMcpServer = this._onUninstallMcpServer.event;9293private _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>());94readonly onDidUninstallMcpServer = this._onDidUninstallMcpServer.event;9596private readonly _onInstallMcpServerInCurrentProfile = this._register(new Emitter<InstallWorkbenchMcpServerEvent>());97readonly onInstallMcpServerInCurrentProfile = this._onInstallMcpServerInCurrentProfile.event;9899private readonly _onDidInstallMcpServersInCurrentProfile = this._register(new Emitter<readonly IWorkbenchMcpServerInstallResult[]>());100readonly onDidInstallMcpServersInCurrentProfile = this._onDidInstallMcpServersInCurrentProfile.event;101102private readonly _onDidUpdateMcpServersInCurrentProfile = this._register(new Emitter<readonly IWorkbenchMcpServerInstallResult[]>());103readonly onDidUpdateMcpServersInCurrentProfile = this._onDidUpdateMcpServersInCurrentProfile.event;104105private readonly _onUninstallMcpServerInCurrentProfile = this._register(new Emitter<UninstallWorkbenchMcpServerEvent>());106readonly onUninstallMcpServerInCurrentProfile = this._onUninstallMcpServerInCurrentProfile.event;107108private readonly _onDidUninstallMcpServerInCurrentProfile = this._register(new Emitter<DidUninstallWorkbenchMcpServerEvent>());109readonly onDidUninstallMcpServerInCurrentProfile = this._onDidUninstallMcpServerInCurrentProfile.event;110111private readonly _onDidChangeProfile = this._register(new Emitter<void>());112readonly onDidChangeProfile = this._onDidChangeProfile.event;113114private readonly workspaceMcpManagementService: IMcpManagementService;115private readonly remoteMcpManagementService: IMcpManagementService | undefined;116117constructor(118private readonly mcpManagementService: IMcpManagementService,119@IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService,120@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,121@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,122@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,123@IRemoteAgentService remoteAgentService: IRemoteAgentService,124@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,125@IRemoteUserDataProfilesService private readonly remoteUserDataProfilesService: IRemoteUserDataProfilesService,126@IInstantiationService instantiationService: IInstantiationService,127) {128super(allowedMcpServersService);129130this.workspaceMcpManagementService = this._register(instantiationService.createInstance(WorkspaceMcpManagementService));131const remoteAgentConnection = remoteAgentService.getConnection();132if (remoteAgentConnection) {133this.remoteMcpManagementService = this._register(instantiationService.createInstance(McpManagementChannelClient, remoteAgentConnection.getChannel<IChannel>('mcpManagement')));134}135136this._register(this.mcpManagementService.onInstallMcpServer(e => {137this._onInstallMcpServer.fire(e);138if (uriIdentityService.extUri.isEqual(e.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {139this._onInstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.User });140}141}));142143this._register(this.mcpManagementService.onDidInstallMcpServers(e => {144const { mcpServerInstallResult, mcpServerInstallResultInCurrentProfile } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.User);145this._onDidInstallMcpServers.fire(mcpServerInstallResult);146if (mcpServerInstallResultInCurrentProfile.length) {147this._onDidInstallMcpServersInCurrentProfile.fire(mcpServerInstallResultInCurrentProfile);148}149}));150151this._register(this.mcpManagementService.onDidUpdateMcpServers(e => {152const { mcpServerInstallResult, mcpServerInstallResultInCurrentProfile } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.User);153this._onDidUpdateMcpServers.fire(mcpServerInstallResult);154if (mcpServerInstallResultInCurrentProfile.length) {155this._onDidUpdateMcpServersInCurrentProfile.fire(mcpServerInstallResultInCurrentProfile);156}157}));158159this._register(this.mcpManagementService.onUninstallMcpServer(e => {160this._onUninstallMcpServer.fire(e);161if (uriIdentityService.extUri.isEqual(e.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {162this._onUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.User });163}164}));165166this._register(this.mcpManagementService.onDidUninstallMcpServer(e => {167this._onDidUninstallMcpServer.fire(e);168if (uriIdentityService.extUri.isEqual(e.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {169this._onDidUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.User });170}171}));172173this._register(this.workspaceMcpManagementService.onInstallMcpServer(async e => {174this._onInstallMcpServer.fire(e);175this._onInstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.Workspace });176}));177178this._register(this.workspaceMcpManagementService.onDidInstallMcpServers(async e => {179const { mcpServerInstallResult } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.Workspace);180this._onDidInstallMcpServers.fire(mcpServerInstallResult);181this._onDidInstallMcpServersInCurrentProfile.fire(mcpServerInstallResult);182}));183184this._register(this.workspaceMcpManagementService.onUninstallMcpServer(async e => {185this._onUninstallMcpServer.fire(e);186this._onUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.Workspace });187}));188189this._register(this.workspaceMcpManagementService.onDidUninstallMcpServer(async e => {190this._onDidUninstallMcpServer.fire(e);191this._onDidUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.Workspace });192}));193194this._register(this.workspaceMcpManagementService.onDidUpdateMcpServers(e => {195const { mcpServerInstallResult } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.Workspace);196this._onDidUpdateMcpServers.fire(mcpServerInstallResult);197this._onDidUpdateMcpServersInCurrentProfile.fire(mcpServerInstallResult);198}));199200if (this.remoteMcpManagementService) {201this._register(this.remoteMcpManagementService.onInstallMcpServer(async e => {202this._onInstallMcpServer.fire(e);203const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);204if (remoteMcpResource ? uriIdentityService.extUri.isEqual(e.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {205this._onInstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.RemoteUser });206}207}));208209this._register(this.remoteMcpManagementService.onDidInstallMcpServers(e => this.handleRemoteInstallMcpServerResultsFromEvent(e, this._onDidInstallMcpServers, this._onDidInstallMcpServersInCurrentProfile)));210this._register(this.remoteMcpManagementService.onDidUpdateMcpServers(e => this.handleRemoteInstallMcpServerResultsFromEvent(e, this._onDidInstallMcpServers, this._onDidInstallMcpServersInCurrentProfile)));211212this._register(this.remoteMcpManagementService.onUninstallMcpServer(async e => {213this._onUninstallMcpServer.fire(e);214const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);215if (remoteMcpResource ? uriIdentityService.extUri.isEqual(e.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {216this._onUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.RemoteUser });217}218}));219220this._register(this.remoteMcpManagementService.onDidUninstallMcpServer(async e => {221this._onDidUninstallMcpServer.fire(e);222const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);223if (remoteMcpResource ? uriIdentityService.extUri.isEqual(e.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {224this._onDidUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.RemoteUser });225}226}));227}228229this._register(userDataProfileService.onDidChangeCurrentProfile(e => {230if (!this.uriIdentityService.extUri.isEqual(e.previous.mcpResource, e.profile.mcpResource)) {231this._onDidChangeProfile.fire();232}233}));234}235236private createInstallMcpServerResultsFromEvent(e: readonly InstallMcpServerResult[], scope: LocalMcpServerScope): { mcpServerInstallResult: IWorkbenchMcpServerInstallResult[]; mcpServerInstallResultInCurrentProfile: IWorkbenchMcpServerInstallResult[] } {237const mcpServerInstallResult: IWorkbenchMcpServerInstallResult[] = [];238const mcpServerInstallResultInCurrentProfile: IWorkbenchMcpServerInstallResult[] = [];239for (const result of e) {240const workbenchResult = {241...result,242local: result.local ? this.toWorkspaceMcpServer(result.local, scope) : undefined243};244mcpServerInstallResult.push(workbenchResult);245if (this.uriIdentityService.extUri.isEqual(result.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {246mcpServerInstallResultInCurrentProfile.push(workbenchResult);247}248}249250return { mcpServerInstallResult, mcpServerInstallResultInCurrentProfile };251}252253private async handleRemoteInstallMcpServerResultsFromEvent(e: readonly InstallMcpServerResult[], emitter: Emitter<readonly InstallMcpServerResult[]>, currentProfileEmitter: Emitter<readonly IWorkbenchMcpServerInstallResult[]>): Promise<void> {254const mcpServerInstallResult: IWorkbenchMcpServerInstallResult[] = [];255const mcpServerInstallResultInCurrentProfile: IWorkbenchMcpServerInstallResult[] = [];256const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);257for (const result of e) {258const workbenchResult = {259...result,260local: result.local ? this.toWorkspaceMcpServer(result.local, LocalMcpServerScope.RemoteUser) : undefined261};262mcpServerInstallResult.push(workbenchResult);263if (remoteMcpResource ? this.uriIdentityService.extUri.isEqual(result.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {264mcpServerInstallResultInCurrentProfile.push(workbenchResult);265}266}267268emitter.fire(mcpServerInstallResult);269if (mcpServerInstallResultInCurrentProfile.length) {270currentProfileEmitter.fire(mcpServerInstallResultInCurrentProfile);271}272}273274async getInstalled(): Promise<IWorkbenchLocalMcpServer[]> {275const installed: IWorkbenchLocalMcpServer[] = [];276const [userServers, remoteServers, workspaceServers] = await Promise.all([277this.mcpManagementService.getInstalled(this.userDataProfileService.currentProfile.mcpResource),278this.remoteMcpManagementService?.getInstalled(await this.getRemoteMcpResource()) ?? Promise.resolve<ILocalMcpServer[]>([]),279this.workspaceMcpManagementService?.getInstalled() ?? Promise.resolve<ILocalMcpServer[]>([]),280]);281282for (const server of userServers) {283installed.push(this.toWorkspaceMcpServer(server, LocalMcpServerScope.User));284}285for (const server of remoteServers) {286installed.push(this.toWorkspaceMcpServer(server, LocalMcpServerScope.RemoteUser));287}288for (const server of workspaceServers) {289installed.push(this.toWorkspaceMcpServer(server, LocalMcpServerScope.Workspace));290}291292return installed;293}294295private toWorkspaceMcpServer(server: ILocalMcpServer, scope: LocalMcpServerScope): IWorkbenchLocalMcpServer {296return { ...server, id: `mcp.config.${this.getConfigId(server, scope)}.${server.name}`, scope };297}298299private getConfigId(server: ILocalMcpServer, scope: LocalMcpServerScope): string {300if (scope === LocalMcpServerScope.User) {301return USER_CONFIG_ID;302}303304if (scope === LocalMcpServerScope.RemoteUser) {305return REMOTE_USER_CONFIG_ID;306}307308if (scope === LocalMcpServerScope.Workspace) {309const workspace = this.workspaceContextService.getWorkspace();310if (workspace.configuration && this.uriIdentityService.extUri.isEqual(workspace.configuration, server.mcpResource)) {311return WORKSPACE_CONFIG_ID;312}313314const workspaceFolders = workspace.folders;315for (let index = 0; index < workspaceFolders.length; index++) {316const workspaceFolder = workspaceFolders[index];317if (this.uriIdentityService.extUri.isEqual(this.uriIdentityService.extUri.joinPath(workspaceFolder.uri, WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]), server.mcpResource)) {318return `${WORKSPACE_FOLDER_CONFIG_ID_PREFIX}${index}`;319}320}321}322return 'unknown';323}324325async install(server: IInstallableMcpServer, options?: IWorkbencMcpServerInstallOptions): Promise<IWorkbenchLocalMcpServer> {326options = options ?? {};327328if (options.target === ConfigurationTarget.WORKSPACE || isWorkspaceFolder(options.target)) {329const mcpResource = options.target === ConfigurationTarget.WORKSPACE ? this.workspaceContextService.getWorkspace().configuration : options.target.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]);330if (!mcpResource) {331throw new Error(`Illegal target: ${options.target}`);332}333options.mcpResource = mcpResource;334const result = await this.workspaceMcpManagementService.install(server, options);335return this.toWorkspaceMcpServer(result, LocalMcpServerScope.Workspace);336}337338if (options.target === ConfigurationTarget.USER_REMOTE) {339if (!this.remoteMcpManagementService) {340throw new Error(`Illegal target: ${options.target}`);341}342options.mcpResource = await this.getRemoteMcpResource(options.mcpResource);343const result = await this.remoteMcpManagementService.install(server, options);344return this.toWorkspaceMcpServer(result, LocalMcpServerScope.RemoteUser);345}346347if (options.target && options.target !== ConfigurationTarget.USER && options.target !== ConfigurationTarget.USER_LOCAL) {348throw new Error(`Illegal target: ${options.target}`);349}350351options.mcpResource = this.userDataProfileService.currentProfile.mcpResource;352const result = await this.mcpManagementService.install(server, options);353return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User);354}355356async installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise<IWorkbenchLocalMcpServer> {357options = options ?? {};358if (!options.mcpResource) {359options.mcpResource = this.userDataProfileService.currentProfile.mcpResource;360}361const result = await this.mcpManagementService.installFromGallery(server, options);362return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User);363}364365async updateMetadata(local: IWorkbenchLocalMcpServer, server: IGalleryMcpServer, profileLocation: URI): Promise<IWorkbenchLocalMcpServer> {366if (local.scope === LocalMcpServerScope.Workspace) {367const result = await this.workspaceMcpManagementService.updateMetadata(local, server, profileLocation);368return this.toWorkspaceMcpServer(result, LocalMcpServerScope.Workspace);369}370371if (local.scope === LocalMcpServerScope.RemoteUser) {372if (!this.remoteMcpManagementService) {373throw new Error(`Illegal target: ${local.scope}`);374}375const result = await this.remoteMcpManagementService.updateMetadata(local, server, profileLocation);376return this.toWorkspaceMcpServer(result, LocalMcpServerScope.RemoteUser);377}378379const result = await this.mcpManagementService.updateMetadata(local, server, profileLocation);380return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User);381}382383async uninstall(server: IWorkbenchLocalMcpServer): Promise<void> {384if (server.scope === LocalMcpServerScope.Workspace) {385return this.workspaceMcpManagementService.uninstall(server);386}387388if (server.scope === LocalMcpServerScope.RemoteUser) {389if (!this.remoteMcpManagementService) {390throw new Error(`Illegal target: ${server.scope}`);391}392return this.remoteMcpManagementService.uninstall(server);393}394395return this.mcpManagementService.uninstall(server, { mcpResource: this.userDataProfileService.currentProfile.mcpResource });396}397398private async getRemoteMcpResource(mcpResource?: URI): Promise<URI | undefined> {399if (!mcpResource && this.userDataProfileService.currentProfile.isDefault) {400return undefined;401}402mcpResource = mcpResource ?? this.userDataProfileService.currentProfile.mcpResource;403let profile = this.userDataProfilesService.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.mcpResource, mcpResource));404if (profile) {405profile = await this.remoteUserDataProfilesService.getRemoteProfile(profile);406} else {407profile = (await this.remoteUserDataProfilesService.getRemoteProfiles()).find(p => this.uriIdentityService.extUri.isEqual(p.mcpResource, mcpResource));408}409return profile?.mcpResource;410}411}412413class WorkspaceMcpResourceManagementService extends AbstractMcpResourceManagementService {414415constructor(416mcpResource: URI,417target: McpResourceTarget,418@IMcpGalleryService mcpGalleryService: IMcpGalleryService,419@IFileService fileService: IFileService,420@IUriIdentityService uriIdentityService: IUriIdentityService,421@ILogService logService: ILogService,422@IMcpResourceScannerService mcpResourceScannerService: IMcpResourceScannerService,423) {424super(mcpResource, target, mcpGalleryService, fileService, uriIdentityService, logService, mcpResourceScannerService);425}426427override installFromGallery(): Promise<ILocalMcpServer> {428throw new Error('Not supported');429}430431override updateMetadata(): Promise<ILocalMcpServer> {432throw new Error('Not supported');433}434435protected override installFromUri(): Promise<ILocalMcpServer> {436throw new Error('Not supported');437}438439protected override async getLocalServerInfo(): Promise<ILocalMcpServerInfo | undefined> {440return undefined;441}442}443444class WorkspaceMcpManagementService extends AbstractMcpManagementService implements IMcpManagementService {445446private readonly _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>());447readonly onInstallMcpServer = this._onInstallMcpServer.event;448449private readonly _onDidInstallMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());450readonly onDidInstallMcpServers = this._onDidInstallMcpServers.event;451452private readonly _onDidUpdateMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());453readonly onDidUpdateMcpServers = this._onDidUpdateMcpServers.event;454455private readonly _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>());456readonly onUninstallMcpServer = this._onUninstallMcpServer.event;457458private readonly _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>());459readonly onDidUninstallMcpServer = this._onDidUninstallMcpServer.event;460461private allMcpServers: ILocalMcpServer[] = [];462463private workspaceConfiguration?: URI | null;464private readonly workspaceMcpManagementServices = new ResourceMap<{ service: WorkspaceMcpResourceManagementService } & IDisposable>();465466constructor(467@IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService,468@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,469@ILogService private readonly logService: ILogService,470@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,471@IInstantiationService private readonly instantiationService: IInstantiationService,472) {473super(allowedMcpServersService);474this.initialize();475}476477private async initialize(): Promise<void> {478try {479await this.onDidChangeWorkbenchState();480await this.onDidChangeWorkspaceFolders({ added: this.workspaceContextService.getWorkspace().folders, removed: [], changed: [] });481this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(e => this.onDidChangeWorkspaceFolders(e)));482this._register(this.workspaceContextService.onDidChangeWorkbenchState(e => this.onDidChangeWorkbenchState()));483} catch (error) {484this.logService.error('Failed to initialize workspace folders', error);485}486}487488private async onDidChangeWorkbenchState(): Promise<void> {489if (this.workspaceConfiguration) {490await this.removeWorkspaceService(this.workspaceConfiguration);491}492this.workspaceConfiguration = this.workspaceContextService.getWorkspace().configuration;493if (this.workspaceConfiguration) {494await this.addWorkspaceService(this.workspaceConfiguration, ConfigurationTarget.WORKSPACE);495}496}497498private async onDidChangeWorkspaceFolders(e: IWorkspaceFoldersChangeEvent): Promise<void> {499try {500await Promise.allSettled(e.removed.map(folder => this.removeWorkspaceService(folder.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]))));501} catch (error) {502this.logService.error(error);503}504try {505await Promise.allSettled(e.added.map(folder => this.addWorkspaceService(folder.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]), ConfigurationTarget.WORKSPACE_FOLDER)));506} catch (error) {507this.logService.error(error);508}509}510511private async addWorkspaceService(mcpResource: URI, target: McpResourceTarget): Promise<void> {512if (this.workspaceMcpManagementServices.has(mcpResource)) {513return;514}515516const disposables = new DisposableStore();517const service = disposables.add(this.instantiationService.createInstance(WorkspaceMcpResourceManagementService, mcpResource, target));518519try {520const installedServers = await service.getInstalled();521this.allMcpServers.push(...installedServers);522if (installedServers.length > 0) {523const installResults: InstallMcpServerResult[] = installedServers.map(server => ({524name: server.name,525local: server,526mcpResource: server.mcpResource527}));528this._onDidInstallMcpServers.fire(installResults);529}530} catch (error) {531this.logService.warn('Failed to get installed servers from', mcpResource.toString(), error);532}533534disposables.add(service.onInstallMcpServer(e => this._onInstallMcpServer.fire(e)));535disposables.add(service.onDidInstallMcpServers(e => {536for (const { local } of e) {537if (local) {538this.allMcpServers.push(local);539}540}541this._onDidInstallMcpServers.fire(e);542}));543disposables.add(service.onDidUpdateMcpServers(e => {544for (const { local, mcpResource } of e) {545if (local) {546const index = this.allMcpServers.findIndex(server => this.uriIdentityService.extUri.isEqual(server.mcpResource, mcpResource) && server.name === local.name);547if (index !== -1) {548this.allMcpServers.splice(index, 1, local);549}550}551}552this._onDidUpdateMcpServers.fire(e);553}));554disposables.add(service.onUninstallMcpServer(e => this._onUninstallMcpServer.fire(e)));555disposables.add(service.onDidUninstallMcpServer(e => {556const index = this.allMcpServers.findIndex(server => this.uriIdentityService.extUri.isEqual(server.mcpResource, e.mcpResource) && server.name === e.name);557if (index !== -1) {558this.allMcpServers.splice(index, 1);559this._onDidUninstallMcpServer.fire(e);560}561}));562this.workspaceMcpManagementServices.set(mcpResource, { service, dispose: () => disposables.dispose() });563}564565private async removeWorkspaceService(mcpResource: URI): Promise<void> {566const serviceItem = this.workspaceMcpManagementServices.get(mcpResource);567if (serviceItem) {568try {569const installedServers = await serviceItem.service.getInstalled();570this.allMcpServers = this.allMcpServers.filter(server => !installedServers.some(uninstalled => this.uriIdentityService.extUri.isEqual(uninstalled.mcpResource, server.mcpResource)));571for (const server of installedServers) {572this._onDidUninstallMcpServer.fire({573name: server.name,574mcpResource: server.mcpResource575});576}577} catch (error) {578this.logService.warn('Failed to get installed servers from', mcpResource.toString(), error);579}580this.workspaceMcpManagementServices.delete(mcpResource);581serviceItem.dispose();582}583}584585async getInstalled(): Promise<ILocalMcpServer[]> {586return this.allMcpServers;587}588589async install(server: IInstallableMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {590if (!options?.mcpResource) {591throw new Error('MCP resource is required');592}593594const mcpManagementServiceItem = this.workspaceMcpManagementServices.get(options?.mcpResource);595if (!mcpManagementServiceItem) {596throw new Error(`No MCP management service found for resource: ${options?.mcpResource.toString()}`);597}598599return mcpManagementServiceItem.service.install(server, options);600}601602async uninstall(server: ILocalMcpServer, options?: UninstallOptions): Promise<void> {603const mcpResource = server.mcpResource;604605const mcpManagementServiceItem = this.workspaceMcpManagementServices.get(mcpResource);606if (!mcpManagementServiceItem) {607throw new Error(`No MCP management service found for resource: ${mcpResource.toString()}`);608}609610return mcpManagementServiceItem.service.uninstall(server, options);611}612613installFromGallery(): Promise<ILocalMcpServer> {614throw new Error('Not supported');615}616617updateMetadata(): Promise<ILocalMcpServer> {618throw new Error('Not supported');619}620621override dispose(): void {622this.workspaceMcpManagementServices.forEach(service => service.dispose());623this.workspaceMcpManagementServices.clear();624super.dispose();625}626}627628629