Path: blob/main/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts
5251 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, RegistryType } 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';25import { IMarkdownString } from '../../../../base/common/htmlContent.js';26import { IMcpServerConfiguration } from '../../../../platform/mcp/common/mcpPlatformTypes.js';2728export const USER_CONFIG_ID = 'usrlocal';29export const REMOTE_USER_CONFIG_ID = 'usrremote';30export const WORKSPACE_CONFIG_ID = 'workspace';31export const WORKSPACE_FOLDER_CONFIG_ID_PREFIX = 'ws';3233export interface IWorkbencMcpServerInstallOptions extends InstallOptions {34target?: ConfigurationTarget | IWorkspaceFolder;35}3637export const enum LocalMcpServerScope {38User = 'user',39RemoteUser = 'remoteUser',40Workspace = 'workspace',41}4243export interface IWorkbenchLocalMcpServer extends ILocalMcpServer {44readonly id: string;45readonly scope: LocalMcpServerScope;46}4748export interface InstallWorkbenchMcpServerEvent extends InstallMcpServerEvent {49readonly scope: LocalMcpServerScope;50}5152export interface IWorkbenchMcpServerInstallResult extends InstallMcpServerResult {53readonly local?: IWorkbenchLocalMcpServer;54}5556export interface UninstallWorkbenchMcpServerEvent extends UninstallMcpServerEvent {57readonly scope: LocalMcpServerScope;58}5960export interface DidUninstallWorkbenchMcpServerEvent extends DidUninstallMcpServerEvent {61readonly scope: LocalMcpServerScope;62}6364export const IWorkbenchMcpManagementService = refineServiceDecorator<IMcpManagementService, IWorkbenchMcpManagementService>(IMcpManagementService);65export interface IWorkbenchMcpManagementService extends IMcpManagementService {66readonly _serviceBrand: undefined;6768readonly onInstallMcpServerInCurrentProfile: Event<InstallWorkbenchMcpServerEvent>;69readonly onDidInstallMcpServersInCurrentProfile: Event<readonly IWorkbenchMcpServerInstallResult[]>;70readonly onDidUpdateMcpServersInCurrentProfile: Event<readonly IWorkbenchMcpServerInstallResult[]>;71readonly onUninstallMcpServerInCurrentProfile: Event<UninstallWorkbenchMcpServerEvent>;72readonly onDidUninstallMcpServerInCurrentProfile: Event<DidUninstallWorkbenchMcpServerEvent>;73readonly onDidChangeProfile: Event<void>;7475getInstalled(): Promise<IWorkbenchLocalMcpServer[]>;76install(server: IInstallableMcpServer | URI, options?: IWorkbencMcpServerInstallOptions): Promise<IWorkbenchLocalMcpServer>;77installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise<IWorkbenchLocalMcpServer>;78updateMetadata(local: ILocalMcpServer, server: IGalleryMcpServer, profileLocation?: URI): Promise<IWorkbenchLocalMcpServer>;79}8081export class WorkbenchMcpManagementService extends AbstractMcpManagementService implements IWorkbenchMcpManagementService {8283private _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>());84readonly onInstallMcpServer = this._onInstallMcpServer.event;8586private _onDidInstallMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());87readonly onDidInstallMcpServers = this._onDidInstallMcpServers.event;8889private _onDidUpdateMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());90readonly onDidUpdateMcpServers = this._onDidUpdateMcpServers.event;9192private _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>());93readonly onUninstallMcpServer = this._onUninstallMcpServer.event;9495private _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>());96readonly onDidUninstallMcpServer = this._onDidUninstallMcpServer.event;9798private readonly _onInstallMcpServerInCurrentProfile = this._register(new Emitter<InstallWorkbenchMcpServerEvent>());99readonly onInstallMcpServerInCurrentProfile = this._onInstallMcpServerInCurrentProfile.event;100101private readonly _onDidInstallMcpServersInCurrentProfile = this._register(new Emitter<readonly IWorkbenchMcpServerInstallResult[]>());102readonly onDidInstallMcpServersInCurrentProfile = this._onDidInstallMcpServersInCurrentProfile.event;103104private readonly _onDidUpdateMcpServersInCurrentProfile = this._register(new Emitter<readonly IWorkbenchMcpServerInstallResult[]>());105readonly onDidUpdateMcpServersInCurrentProfile = this._onDidUpdateMcpServersInCurrentProfile.event;106107private readonly _onUninstallMcpServerInCurrentProfile = this._register(new Emitter<UninstallWorkbenchMcpServerEvent>());108readonly onUninstallMcpServerInCurrentProfile = this._onUninstallMcpServerInCurrentProfile.event;109110private readonly _onDidUninstallMcpServerInCurrentProfile = this._register(new Emitter<DidUninstallWorkbenchMcpServerEvent>());111readonly onDidUninstallMcpServerInCurrentProfile = this._onDidUninstallMcpServerInCurrentProfile.event;112113private readonly _onDidChangeProfile = this._register(new Emitter<void>());114readonly onDidChangeProfile = this._onDidChangeProfile.event;115116private readonly workspaceMcpManagementService: IMcpManagementService;117private readonly remoteMcpManagementService: IMcpManagementService | undefined;118119constructor(120private readonly mcpManagementService: IMcpManagementService,121@IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService,122@ILogService logService: ILogService,123@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,124@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,125@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,126@IRemoteAgentService remoteAgentService: IRemoteAgentService,127@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,128@IRemoteUserDataProfilesService private readonly remoteUserDataProfilesService: IRemoteUserDataProfilesService,129@IInstantiationService instantiationService: IInstantiationService,130) {131super(allowedMcpServersService, logService);132133this.workspaceMcpManagementService = this._register(instantiationService.createInstance(WorkspaceMcpManagementService));134const remoteAgentConnection = remoteAgentService.getConnection();135if (remoteAgentConnection) {136this.remoteMcpManagementService = this._register(instantiationService.createInstance(McpManagementChannelClient, remoteAgentConnection.getChannel<IChannel>('mcpManagement')));137}138139this._register(this.mcpManagementService.onInstallMcpServer(e => {140this._onInstallMcpServer.fire(e);141if (uriIdentityService.extUri.isEqual(e.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {142this._onInstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.User });143}144}));145146this._register(this.mcpManagementService.onDidInstallMcpServers(e => {147const { mcpServerInstallResult, mcpServerInstallResultInCurrentProfile } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.User);148this._onDidInstallMcpServers.fire(mcpServerInstallResult);149if (mcpServerInstallResultInCurrentProfile.length) {150this._onDidInstallMcpServersInCurrentProfile.fire(mcpServerInstallResultInCurrentProfile);151}152}));153154this._register(this.mcpManagementService.onDidUpdateMcpServers(e => {155const { mcpServerInstallResult, mcpServerInstallResultInCurrentProfile } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.User);156this._onDidUpdateMcpServers.fire(mcpServerInstallResult);157if (mcpServerInstallResultInCurrentProfile.length) {158this._onDidUpdateMcpServersInCurrentProfile.fire(mcpServerInstallResultInCurrentProfile);159}160}));161162this._register(this.mcpManagementService.onUninstallMcpServer(e => {163this._onUninstallMcpServer.fire(e);164if (uriIdentityService.extUri.isEqual(e.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {165this._onUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.User });166}167}));168169this._register(this.mcpManagementService.onDidUninstallMcpServer(e => {170this._onDidUninstallMcpServer.fire(e);171if (uriIdentityService.extUri.isEqual(e.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {172this._onDidUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.User });173}174}));175176this._register(this.workspaceMcpManagementService.onInstallMcpServer(async e => {177this._onInstallMcpServer.fire(e);178this._onInstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.Workspace });179}));180181this._register(this.workspaceMcpManagementService.onDidInstallMcpServers(async e => {182const { mcpServerInstallResult } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.Workspace);183this._onDidInstallMcpServers.fire(mcpServerInstallResult);184this._onDidInstallMcpServersInCurrentProfile.fire(mcpServerInstallResult);185}));186187this._register(this.workspaceMcpManagementService.onUninstallMcpServer(async e => {188this._onUninstallMcpServer.fire(e);189this._onUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.Workspace });190}));191192this._register(this.workspaceMcpManagementService.onDidUninstallMcpServer(async e => {193this._onDidUninstallMcpServer.fire(e);194this._onDidUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.Workspace });195}));196197this._register(this.workspaceMcpManagementService.onDidUpdateMcpServers(e => {198const { mcpServerInstallResult } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.Workspace);199this._onDidUpdateMcpServers.fire(mcpServerInstallResult);200this._onDidUpdateMcpServersInCurrentProfile.fire(mcpServerInstallResult);201}));202203if (this.remoteMcpManagementService) {204this._register(this.remoteMcpManagementService.onInstallMcpServer(async e => {205this._onInstallMcpServer.fire(e);206const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);207if (remoteMcpResource ? uriIdentityService.extUri.isEqual(e.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {208this._onInstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.RemoteUser });209}210}));211212this._register(this.remoteMcpManagementService.onDidInstallMcpServers(e => this.handleRemoteInstallMcpServerResultsFromEvent(e, this._onDidInstallMcpServers, this._onDidInstallMcpServersInCurrentProfile)));213this._register(this.remoteMcpManagementService.onDidUpdateMcpServers(e => this.handleRemoteInstallMcpServerResultsFromEvent(e, this._onDidInstallMcpServers, this._onDidInstallMcpServersInCurrentProfile)));214215this._register(this.remoteMcpManagementService.onUninstallMcpServer(async e => {216this._onUninstallMcpServer.fire(e);217const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);218if (remoteMcpResource ? uriIdentityService.extUri.isEqual(e.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {219this._onUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.RemoteUser });220}221}));222223this._register(this.remoteMcpManagementService.onDidUninstallMcpServer(async e => {224this._onDidUninstallMcpServer.fire(e);225const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);226if (remoteMcpResource ? uriIdentityService.extUri.isEqual(e.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {227this._onDidUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.RemoteUser });228}229}));230}231232this._register(userDataProfileService.onDidChangeCurrentProfile(e => {233if (!this.uriIdentityService.extUri.isEqual(e.previous.mcpResource, e.profile.mcpResource)) {234this._onDidChangeProfile.fire();235}236}));237}238239private createInstallMcpServerResultsFromEvent(e: readonly InstallMcpServerResult[], scope: LocalMcpServerScope): { mcpServerInstallResult: IWorkbenchMcpServerInstallResult[]; mcpServerInstallResultInCurrentProfile: IWorkbenchMcpServerInstallResult[] } {240const mcpServerInstallResult: IWorkbenchMcpServerInstallResult[] = [];241const mcpServerInstallResultInCurrentProfile: IWorkbenchMcpServerInstallResult[] = [];242for (const result of e) {243const workbenchResult = {244...result,245local: result.local ? this.toWorkspaceMcpServer(result.local, scope) : undefined246};247mcpServerInstallResult.push(workbenchResult);248if (this.uriIdentityService.extUri.isEqual(result.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {249mcpServerInstallResultInCurrentProfile.push(workbenchResult);250}251}252253return { mcpServerInstallResult, mcpServerInstallResultInCurrentProfile };254}255256private async handleRemoteInstallMcpServerResultsFromEvent(e: readonly InstallMcpServerResult[], emitter: Emitter<readonly InstallMcpServerResult[]>, currentProfileEmitter: Emitter<readonly IWorkbenchMcpServerInstallResult[]>): Promise<void> {257const mcpServerInstallResult: IWorkbenchMcpServerInstallResult[] = [];258const mcpServerInstallResultInCurrentProfile: IWorkbenchMcpServerInstallResult[] = [];259const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);260for (const result of e) {261const workbenchResult = {262...result,263local: result.local ? this.toWorkspaceMcpServer(result.local, LocalMcpServerScope.RemoteUser) : undefined264};265mcpServerInstallResult.push(workbenchResult);266if (remoteMcpResource ? this.uriIdentityService.extUri.isEqual(result.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {267mcpServerInstallResultInCurrentProfile.push(workbenchResult);268}269}270271emitter.fire(mcpServerInstallResult);272if (mcpServerInstallResultInCurrentProfile.length) {273currentProfileEmitter.fire(mcpServerInstallResultInCurrentProfile);274}275}276277async getInstalled(): Promise<IWorkbenchLocalMcpServer[]> {278const installed: IWorkbenchLocalMcpServer[] = [];279const [userServers, remoteServers, workspaceServers] = await Promise.all([280this.mcpManagementService.getInstalled(this.userDataProfileService.currentProfile.mcpResource),281this.remoteMcpManagementService?.getInstalled(await this.getRemoteMcpResource()) ?? Promise.resolve<ILocalMcpServer[]>([]),282this.workspaceMcpManagementService?.getInstalled() ?? Promise.resolve<ILocalMcpServer[]>([]),283]);284285for (const server of userServers) {286installed.push(this.toWorkspaceMcpServer(server, LocalMcpServerScope.User));287}288for (const server of remoteServers) {289installed.push(this.toWorkspaceMcpServer(server, LocalMcpServerScope.RemoteUser));290}291for (const server of workspaceServers) {292installed.push(this.toWorkspaceMcpServer(server, LocalMcpServerScope.Workspace));293}294295return installed;296}297298private toWorkspaceMcpServer(server: ILocalMcpServer, scope: LocalMcpServerScope): IWorkbenchLocalMcpServer {299return { ...server, id: `mcp.config.${this.getConfigId(server, scope)}.${server.name}`, scope };300}301302private getConfigId(server: ILocalMcpServer, scope: LocalMcpServerScope): string {303if (scope === LocalMcpServerScope.User) {304return USER_CONFIG_ID;305}306307if (scope === LocalMcpServerScope.RemoteUser) {308return REMOTE_USER_CONFIG_ID;309}310311if (scope === LocalMcpServerScope.Workspace) {312const workspace = this.workspaceContextService.getWorkspace();313if (workspace.configuration && this.uriIdentityService.extUri.isEqual(workspace.configuration, server.mcpResource)) {314return WORKSPACE_CONFIG_ID;315}316317const workspaceFolders = workspace.folders;318for (let index = 0; index < workspaceFolders.length; index++) {319const workspaceFolder = workspaceFolders[index];320if (this.uriIdentityService.extUri.isEqual(this.uriIdentityService.extUri.joinPath(workspaceFolder.uri, WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]), server.mcpResource)) {321return `${WORKSPACE_FOLDER_CONFIG_ID_PREFIX}${index}`;322}323}324}325return 'unknown';326}327328async install(server: IInstallableMcpServer, options?: IWorkbencMcpServerInstallOptions): Promise<IWorkbenchLocalMcpServer> {329options = options ?? {};330331if (options.target === ConfigurationTarget.WORKSPACE || isWorkspaceFolder(options.target)) {332const mcpResource = options.target === ConfigurationTarget.WORKSPACE ? this.workspaceContextService.getWorkspace().configuration : options.target.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]);333if (!mcpResource) {334throw new Error(`Illegal target: ${options.target}`);335}336options.mcpResource = mcpResource;337const result = await this.workspaceMcpManagementService.install(server, options);338return this.toWorkspaceMcpServer(result, LocalMcpServerScope.Workspace);339}340341if (options.target === ConfigurationTarget.USER_REMOTE) {342if (!this.remoteMcpManagementService) {343throw new Error(`Illegal target: ${options.target}`);344}345options.mcpResource = await this.getRemoteMcpResource(options.mcpResource);346const result = await this.remoteMcpManagementService.install(server, options);347return this.toWorkspaceMcpServer(result, LocalMcpServerScope.RemoteUser);348}349350if (options.target && options.target !== ConfigurationTarget.USER && options.target !== ConfigurationTarget.USER_LOCAL) {351throw new Error(`Illegal target: ${options.target}`);352}353354options.mcpResource = this.userDataProfileService.currentProfile.mcpResource;355const result = await this.mcpManagementService.install(server, options);356return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User);357}358359async installFromGallery(server: IGalleryMcpServer, options?: IWorkbencMcpServerInstallOptions): Promise<IWorkbenchLocalMcpServer> {360options = options ?? {};361362if (options.target === ConfigurationTarget.WORKSPACE || isWorkspaceFolder(options.target)) {363const mcpResource = options.target === ConfigurationTarget.WORKSPACE ? this.workspaceContextService.getWorkspace().configuration : options.target.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]);364if (!mcpResource) {365throw new Error(`Illegal target: ${options.target}`);366}367options.mcpResource = mcpResource;368const result = await this.workspaceMcpManagementService.installFromGallery(server, options);369return this.toWorkspaceMcpServer(result, LocalMcpServerScope.Workspace);370}371372if (options.target === ConfigurationTarget.USER_REMOTE) {373if (!this.remoteMcpManagementService) {374throw new Error(`Illegal target: ${options.target}`);375}376options.mcpResource = await this.getRemoteMcpResource(options.mcpResource);377const result = await this.remoteMcpManagementService.installFromGallery(server, options);378return this.toWorkspaceMcpServer(result, LocalMcpServerScope.RemoteUser);379}380381if (options.target && options.target !== ConfigurationTarget.USER && options.target !== ConfigurationTarget.USER_LOCAL) {382throw new Error(`Illegal target: ${options.target}`);383}384385if (!options.mcpResource) {386options.mcpResource = this.userDataProfileService.currentProfile.mcpResource;387}388const result = await this.mcpManagementService.installFromGallery(server, options);389return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User);390}391392async updateMetadata(local: IWorkbenchLocalMcpServer, server: IGalleryMcpServer, profileLocation: URI): Promise<IWorkbenchLocalMcpServer> {393if (local.scope === LocalMcpServerScope.Workspace) {394const result = await this.workspaceMcpManagementService.updateMetadata(local, server, profileLocation);395return this.toWorkspaceMcpServer(result, LocalMcpServerScope.Workspace);396}397398if (local.scope === LocalMcpServerScope.RemoteUser) {399if (!this.remoteMcpManagementService) {400throw new Error(`Illegal target: ${local.scope}`);401}402const result = await this.remoteMcpManagementService.updateMetadata(local, server, profileLocation);403return this.toWorkspaceMcpServer(result, LocalMcpServerScope.RemoteUser);404}405406const result = await this.mcpManagementService.updateMetadata(local, server, profileLocation);407return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User);408}409410async uninstall(server: IWorkbenchLocalMcpServer): Promise<void> {411if (server.scope === LocalMcpServerScope.Workspace) {412return this.workspaceMcpManagementService.uninstall(server);413}414415if (server.scope === LocalMcpServerScope.RemoteUser) {416if (!this.remoteMcpManagementService) {417throw new Error(`Illegal target: ${server.scope}`);418}419return this.remoteMcpManagementService.uninstall(server);420}421422return this.mcpManagementService.uninstall(server, { mcpResource: this.userDataProfileService.currentProfile.mcpResource });423}424425private async getRemoteMcpResource(mcpResource?: URI): Promise<URI | undefined> {426if (!mcpResource && this.userDataProfileService.currentProfile.isDefault) {427return undefined;428}429mcpResource = mcpResource ?? this.userDataProfileService.currentProfile.mcpResource;430let profile = this.userDataProfilesService.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.mcpResource, mcpResource));431if (profile) {432profile = await this.remoteUserDataProfilesService.getRemoteProfile(profile);433} else {434profile = (await this.remoteUserDataProfilesService.getRemoteProfiles()).find(p => this.uriIdentityService.extUri.isEqual(p.mcpResource, mcpResource));435}436return profile?.mcpResource;437}438}439440class WorkspaceMcpResourceManagementService extends AbstractMcpResourceManagementService {441442constructor(443mcpResource: URI,444target: McpResourceTarget,445@IMcpGalleryService mcpGalleryService: IMcpGalleryService,446@IFileService fileService: IFileService,447@IUriIdentityService uriIdentityService: IUriIdentityService,448@ILogService logService: ILogService,449@IMcpResourceScannerService mcpResourceScannerService: IMcpResourceScannerService,450) {451super(mcpResource, target, mcpGalleryService, fileService, uriIdentityService, logService, mcpResourceScannerService);452}453454override async installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {455this.logService.trace('MCP Management Service: installGallery', server.name, server.galleryUrl);456457this._onInstallMcpServer.fire({ name: server.name, mcpResource: this.mcpResource });458459try {460const packageType = options?.packageType ?? server.configuration.packages?.[0]?.registryType ?? RegistryType.REMOTE;461462const { mcpServerConfiguration, notices } = this.getMcpServerConfigurationFromManifest(server.configuration, packageType);463464if (notices.length > 0) {465this.logService.warn(`MCP Management Service: Warnings while installing ${server.name}`, notices);466}467468const installable: IInstallableMcpServer = {469name: server.name,470config: {471...mcpServerConfiguration.config,472gallery: server.galleryUrl ?? true,473version: server.version474},475inputs: mcpServerConfiguration.inputs476};477478await this.mcpResourceScannerService.addMcpServers([installable], this.mcpResource, this.target);479480await this.updateLocal();481const local = (await this.getInstalled()).find(s => s.name === server.name);482if (!local) {483throw new Error(`Failed to install MCP server: ${server.name}`);484}485return local;486} catch (e) {487this._onDidInstallMcpServers.fire([{ name: server.name, source: server, error: e, mcpResource: this.mcpResource }]);488throw e;489}490}491492override updateMetadata(): Promise<ILocalMcpServer> {493throw new Error('Not supported');494}495496protected override installFromUri(): Promise<ILocalMcpServer> {497throw new Error('Not supported');498}499500protected override async getLocalServerInfo(name: string, mcpServerConfig: IMcpServerConfiguration): Promise<ILocalMcpServerInfo | undefined> {501if (!mcpServerConfig.gallery) {502return undefined;503}504505const [mcpServer] = await this.mcpGalleryService.getMcpServersFromGallery([{ name }]);506if (!mcpServer) {507return undefined;508}509510return {511name: mcpServer.name,512version: mcpServerConfig.version,513displayName: mcpServer.displayName,514description: mcpServer.description,515galleryUrl: mcpServer.galleryUrl,516manifest: mcpServer.configuration,517publisher: mcpServer.publisher,518publisherDisplayName: mcpServer.publisherDisplayName,519repositoryUrl: mcpServer.repositoryUrl,520icon: mcpServer.icon,521};522}523524override canInstall(server: IGalleryMcpServer | IInstallableMcpServer): true | IMarkdownString {525throw new Error('Not supported');526}527}528529class WorkspaceMcpManagementService extends AbstractMcpManagementService implements IMcpManagementService {530531private readonly _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>());532readonly onInstallMcpServer = this._onInstallMcpServer.event;533534private readonly _onDidInstallMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());535readonly onDidInstallMcpServers = this._onDidInstallMcpServers.event;536537private readonly _onDidUpdateMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());538readonly onDidUpdateMcpServers = this._onDidUpdateMcpServers.event;539540private readonly _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>());541readonly onUninstallMcpServer = this._onUninstallMcpServer.event;542543private readonly _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>());544readonly onDidUninstallMcpServer = this._onDidUninstallMcpServer.event;545546private allMcpServers: ILocalMcpServer[] = [];547548private workspaceConfiguration?: URI | null;549private readonly workspaceMcpManagementServices = new ResourceMap<{ service: WorkspaceMcpResourceManagementService } & IDisposable>();550551constructor(552@IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService,553@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,554@ILogService logService: ILogService,555@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,556@IInstantiationService private readonly instantiationService: IInstantiationService,557) {558super(allowedMcpServersService, logService);559this.initialize();560}561562private async initialize(): Promise<void> {563try {564await this.onDidChangeWorkbenchState();565await this.onDidChangeWorkspaceFolders({ added: this.workspaceContextService.getWorkspace().folders, removed: [], changed: [] });566this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(e => this.onDidChangeWorkspaceFolders(e)));567this._register(this.workspaceContextService.onDidChangeWorkbenchState(e => this.onDidChangeWorkbenchState()));568} catch (error) {569this.logService.error('Failed to initialize workspace folders', error);570}571}572573private async onDidChangeWorkbenchState(): Promise<void> {574if (this.workspaceConfiguration) {575await this.removeWorkspaceService(this.workspaceConfiguration);576}577this.workspaceConfiguration = this.workspaceContextService.getWorkspace().configuration;578if (this.workspaceConfiguration) {579await this.addWorkspaceService(this.workspaceConfiguration, ConfigurationTarget.WORKSPACE);580}581}582583private async onDidChangeWorkspaceFolders(e: IWorkspaceFoldersChangeEvent): Promise<void> {584try {585await Promise.allSettled(e.removed.map(folder => this.removeWorkspaceService(folder.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]))));586} catch (error) {587this.logService.error(error);588}589try {590await Promise.allSettled(e.added.map(folder => this.addWorkspaceService(folder.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]), ConfigurationTarget.WORKSPACE_FOLDER)));591} catch (error) {592this.logService.error(error);593}594}595596private async addWorkspaceService(mcpResource: URI, target: McpResourceTarget): Promise<void> {597if (this.workspaceMcpManagementServices.has(mcpResource)) {598return;599}600601const disposables = new DisposableStore();602const service = disposables.add(this.instantiationService.createInstance(WorkspaceMcpResourceManagementService, mcpResource, target));603604try {605const installedServers = await service.getInstalled();606this.allMcpServers.push(...installedServers);607if (installedServers.length > 0) {608const installResults: InstallMcpServerResult[] = installedServers.map(server => ({609name: server.name,610local: server,611mcpResource: server.mcpResource612}));613this._onDidInstallMcpServers.fire(installResults);614}615} catch (error) {616this.logService.warn('Failed to get installed servers from', mcpResource.toString(), error);617}618619disposables.add(service.onInstallMcpServer(e => this._onInstallMcpServer.fire(e)));620disposables.add(service.onDidInstallMcpServers(e => {621for (const { local } of e) {622if (local) {623this.allMcpServers.push(local);624}625}626this._onDidInstallMcpServers.fire(e);627}));628disposables.add(service.onDidUpdateMcpServers(e => {629for (const { local, mcpResource } of e) {630if (local) {631const index = this.allMcpServers.findIndex(server => this.uriIdentityService.extUri.isEqual(server.mcpResource, mcpResource) && server.name === local.name);632if (index !== -1) {633this.allMcpServers.splice(index, 1, local);634}635}636}637this._onDidUpdateMcpServers.fire(e);638}));639disposables.add(service.onUninstallMcpServer(e => this._onUninstallMcpServer.fire(e)));640disposables.add(service.onDidUninstallMcpServer(e => {641const index = this.allMcpServers.findIndex(server => this.uriIdentityService.extUri.isEqual(server.mcpResource, e.mcpResource) && server.name === e.name);642if (index !== -1) {643this.allMcpServers.splice(index, 1);644this._onDidUninstallMcpServer.fire(e);645}646}));647this.workspaceMcpManagementServices.set(mcpResource, { service, dispose: () => disposables.dispose() });648}649650private async removeWorkspaceService(mcpResource: URI): Promise<void> {651const serviceItem = this.workspaceMcpManagementServices.get(mcpResource);652if (serviceItem) {653try {654const installedServers = await serviceItem.service.getInstalled();655this.allMcpServers = this.allMcpServers.filter(server => !installedServers.some(uninstalled => this.uriIdentityService.extUri.isEqual(uninstalled.mcpResource, server.mcpResource)));656for (const server of installedServers) {657this._onDidUninstallMcpServer.fire({658name: server.name,659mcpResource: server.mcpResource660});661}662} catch (error) {663this.logService.warn('Failed to get installed servers from', mcpResource.toString(), error);664}665this.workspaceMcpManagementServices.delete(mcpResource);666serviceItem.dispose();667}668}669670async getInstalled(): Promise<ILocalMcpServer[]> {671return this.allMcpServers;672}673674async install(server: IInstallableMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {675if (!options?.mcpResource) {676throw new Error('MCP resource is required');677}678679const mcpManagementServiceItem = this.workspaceMcpManagementServices.get(options?.mcpResource);680if (!mcpManagementServiceItem) {681throw new Error(`No MCP management service found for resource: ${options?.mcpResource.toString()}`);682}683684return mcpManagementServiceItem.service.install(server, options);685}686687async uninstall(server: ILocalMcpServer, options?: UninstallOptions): Promise<void> {688const mcpResource = server.mcpResource;689690const mcpManagementServiceItem = this.workspaceMcpManagementServices.get(mcpResource);691if (!mcpManagementServiceItem) {692throw new Error(`No MCP management service found for resource: ${mcpResource.toString()}`);693}694695return mcpManagementServiceItem.service.uninstall(server, options);696}697698installFromGallery(gallery: IGalleryMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {699if (!options?.mcpResource) {700throw new Error('MCP resource is required');701}702703const mcpManagementServiceItem = this.workspaceMcpManagementServices.get(options?.mcpResource);704if (!mcpManagementServiceItem) {705throw new Error(`No MCP management service found for resource: ${options?.mcpResource.toString()}`);706}707708return mcpManagementServiceItem.service.installFromGallery(gallery, options);709}710711updateMetadata(): Promise<ILocalMcpServer> {712throw new Error('Not supported');713}714715override dispose(): void {716this.workspaceMcpManagementServices.forEach(service => service.dispose());717this.workspaceMcpManagementServices.clear();718super.dispose();719}720}721722723