Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/services/mcp/common/mcpWorkbenchManagementService.ts
5251 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js';
7
import { ILocalMcpServer, IMcpManagementService, IGalleryMcpServer, InstallOptions, InstallMcpServerEvent, UninstallMcpServerEvent, DidUninstallMcpServerEvent, InstallMcpServerResult, IInstallableMcpServer, IMcpGalleryService, UninstallOptions, IAllowedMcpServersService, RegistryType } from '../../../../platform/mcp/common/mcpManagement.js';
8
import { IInstantiationService, refineServiceDecorator } from '../../../../platform/instantiation/common/instantiation.js';
9
import { IUserDataProfileService } from '../../../services/userDataProfile/common/userDataProfile.js';
10
import { Emitter, Event } from '../../../../base/common/event.js';
11
import { IMcpResourceScannerService, McpResourceTarget } from '../../../../platform/mcp/common/mcpResourceScannerService.js';
12
import { isWorkspaceFolder, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent } from '../../../../platform/workspace/common/workspace.js';
13
import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';
14
import { MCP_CONFIGURATION_KEY, WORKSPACE_STANDALONE_CONFIGURATIONS } from '../../configuration/common/configuration.js';
15
import { ILogService } from '../../../../platform/log/common/log.js';
16
import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js';
17
import { URI } from '../../../../base/common/uri.js';
18
import { ConfigurationTarget } from '../../../../platform/configuration/common/configuration.js';
19
import { IChannel } from '../../../../base/parts/ipc/common/ipc.js';
20
import { McpManagementChannelClient } from '../../../../platform/mcp/common/mcpManagementIpc.js';
21
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
22
import { IRemoteUserDataProfilesService } from '../../userDataProfile/common/remoteUserDataProfiles.js';
23
import { AbstractMcpManagementService, AbstractMcpResourceManagementService, ILocalMcpServerInfo } from '../../../../platform/mcp/common/mcpManagementService.js';
24
import { IFileService } from '../../../../platform/files/common/files.js';
25
import { ResourceMap } from '../../../../base/common/map.js';
26
import { IMarkdownString } from '../../../../base/common/htmlContent.js';
27
import { IMcpServerConfiguration } from '../../../../platform/mcp/common/mcpPlatformTypes.js';
28
29
export const USER_CONFIG_ID = 'usrlocal';
30
export const REMOTE_USER_CONFIG_ID = 'usrremote';
31
export const WORKSPACE_CONFIG_ID = 'workspace';
32
export const WORKSPACE_FOLDER_CONFIG_ID_PREFIX = 'ws';
33
34
export interface IWorkbencMcpServerInstallOptions extends InstallOptions {
35
target?: ConfigurationTarget | IWorkspaceFolder;
36
}
37
38
export const enum LocalMcpServerScope {
39
User = 'user',
40
RemoteUser = 'remoteUser',
41
Workspace = 'workspace',
42
}
43
44
export interface IWorkbenchLocalMcpServer extends ILocalMcpServer {
45
readonly id: string;
46
readonly scope: LocalMcpServerScope;
47
}
48
49
export interface InstallWorkbenchMcpServerEvent extends InstallMcpServerEvent {
50
readonly scope: LocalMcpServerScope;
51
}
52
53
export interface IWorkbenchMcpServerInstallResult extends InstallMcpServerResult {
54
readonly local?: IWorkbenchLocalMcpServer;
55
}
56
57
export interface UninstallWorkbenchMcpServerEvent extends UninstallMcpServerEvent {
58
readonly scope: LocalMcpServerScope;
59
}
60
61
export interface DidUninstallWorkbenchMcpServerEvent extends DidUninstallMcpServerEvent {
62
readonly scope: LocalMcpServerScope;
63
}
64
65
export const IWorkbenchMcpManagementService = refineServiceDecorator<IMcpManagementService, IWorkbenchMcpManagementService>(IMcpManagementService);
66
export interface IWorkbenchMcpManagementService extends IMcpManagementService {
67
readonly _serviceBrand: undefined;
68
69
readonly onInstallMcpServerInCurrentProfile: Event<InstallWorkbenchMcpServerEvent>;
70
readonly onDidInstallMcpServersInCurrentProfile: Event<readonly IWorkbenchMcpServerInstallResult[]>;
71
readonly onDidUpdateMcpServersInCurrentProfile: Event<readonly IWorkbenchMcpServerInstallResult[]>;
72
readonly onUninstallMcpServerInCurrentProfile: Event<UninstallWorkbenchMcpServerEvent>;
73
readonly onDidUninstallMcpServerInCurrentProfile: Event<DidUninstallWorkbenchMcpServerEvent>;
74
readonly onDidChangeProfile: Event<void>;
75
76
getInstalled(): Promise<IWorkbenchLocalMcpServer[]>;
77
install(server: IInstallableMcpServer | URI, options?: IWorkbencMcpServerInstallOptions): Promise<IWorkbenchLocalMcpServer>;
78
installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise<IWorkbenchLocalMcpServer>;
79
updateMetadata(local: ILocalMcpServer, server: IGalleryMcpServer, profileLocation?: URI): Promise<IWorkbenchLocalMcpServer>;
80
}
81
82
export class WorkbenchMcpManagementService extends AbstractMcpManagementService implements IWorkbenchMcpManagementService {
83
84
private _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>());
85
readonly onInstallMcpServer = this._onInstallMcpServer.event;
86
87
private _onDidInstallMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());
88
readonly onDidInstallMcpServers = this._onDidInstallMcpServers.event;
89
90
private _onDidUpdateMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());
91
readonly onDidUpdateMcpServers = this._onDidUpdateMcpServers.event;
92
93
private _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>());
94
readonly onUninstallMcpServer = this._onUninstallMcpServer.event;
95
96
private _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>());
97
readonly onDidUninstallMcpServer = this._onDidUninstallMcpServer.event;
98
99
private readonly _onInstallMcpServerInCurrentProfile = this._register(new Emitter<InstallWorkbenchMcpServerEvent>());
100
readonly onInstallMcpServerInCurrentProfile = this._onInstallMcpServerInCurrentProfile.event;
101
102
private readonly _onDidInstallMcpServersInCurrentProfile = this._register(new Emitter<readonly IWorkbenchMcpServerInstallResult[]>());
103
readonly onDidInstallMcpServersInCurrentProfile = this._onDidInstallMcpServersInCurrentProfile.event;
104
105
private readonly _onDidUpdateMcpServersInCurrentProfile = this._register(new Emitter<readonly IWorkbenchMcpServerInstallResult[]>());
106
readonly onDidUpdateMcpServersInCurrentProfile = this._onDidUpdateMcpServersInCurrentProfile.event;
107
108
private readonly _onUninstallMcpServerInCurrentProfile = this._register(new Emitter<UninstallWorkbenchMcpServerEvent>());
109
readonly onUninstallMcpServerInCurrentProfile = this._onUninstallMcpServerInCurrentProfile.event;
110
111
private readonly _onDidUninstallMcpServerInCurrentProfile = this._register(new Emitter<DidUninstallWorkbenchMcpServerEvent>());
112
readonly onDidUninstallMcpServerInCurrentProfile = this._onDidUninstallMcpServerInCurrentProfile.event;
113
114
private readonly _onDidChangeProfile = this._register(new Emitter<void>());
115
readonly onDidChangeProfile = this._onDidChangeProfile.event;
116
117
private readonly workspaceMcpManagementService: IMcpManagementService;
118
private readonly remoteMcpManagementService: IMcpManagementService | undefined;
119
120
constructor(
121
private readonly mcpManagementService: IMcpManagementService,
122
@IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService,
123
@ILogService logService: ILogService,
124
@IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService,
125
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
126
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
127
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
128
@IUserDataProfilesService private readonly userDataProfilesService: IUserDataProfilesService,
129
@IRemoteUserDataProfilesService private readonly remoteUserDataProfilesService: IRemoteUserDataProfilesService,
130
@IInstantiationService instantiationService: IInstantiationService,
131
) {
132
super(allowedMcpServersService, logService);
133
134
this.workspaceMcpManagementService = this._register(instantiationService.createInstance(WorkspaceMcpManagementService));
135
const remoteAgentConnection = remoteAgentService.getConnection();
136
if (remoteAgentConnection) {
137
this.remoteMcpManagementService = this._register(instantiationService.createInstance(McpManagementChannelClient, remoteAgentConnection.getChannel<IChannel>('mcpManagement')));
138
}
139
140
this._register(this.mcpManagementService.onInstallMcpServer(e => {
141
this._onInstallMcpServer.fire(e);
142
if (uriIdentityService.extUri.isEqual(e.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {
143
this._onInstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.User });
144
}
145
}));
146
147
this._register(this.mcpManagementService.onDidInstallMcpServers(e => {
148
const { mcpServerInstallResult, mcpServerInstallResultInCurrentProfile } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.User);
149
this._onDidInstallMcpServers.fire(mcpServerInstallResult);
150
if (mcpServerInstallResultInCurrentProfile.length) {
151
this._onDidInstallMcpServersInCurrentProfile.fire(mcpServerInstallResultInCurrentProfile);
152
}
153
}));
154
155
this._register(this.mcpManagementService.onDidUpdateMcpServers(e => {
156
const { mcpServerInstallResult, mcpServerInstallResultInCurrentProfile } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.User);
157
this._onDidUpdateMcpServers.fire(mcpServerInstallResult);
158
if (mcpServerInstallResultInCurrentProfile.length) {
159
this._onDidUpdateMcpServersInCurrentProfile.fire(mcpServerInstallResultInCurrentProfile);
160
}
161
}));
162
163
this._register(this.mcpManagementService.onUninstallMcpServer(e => {
164
this._onUninstallMcpServer.fire(e);
165
if (uriIdentityService.extUri.isEqual(e.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {
166
this._onUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.User });
167
}
168
}));
169
170
this._register(this.mcpManagementService.onDidUninstallMcpServer(e => {
171
this._onDidUninstallMcpServer.fire(e);
172
if (uriIdentityService.extUri.isEqual(e.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {
173
this._onDidUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.User });
174
}
175
}));
176
177
this._register(this.workspaceMcpManagementService.onInstallMcpServer(async e => {
178
this._onInstallMcpServer.fire(e);
179
this._onInstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.Workspace });
180
}));
181
182
this._register(this.workspaceMcpManagementService.onDidInstallMcpServers(async e => {
183
const { mcpServerInstallResult } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.Workspace);
184
this._onDidInstallMcpServers.fire(mcpServerInstallResult);
185
this._onDidInstallMcpServersInCurrentProfile.fire(mcpServerInstallResult);
186
}));
187
188
this._register(this.workspaceMcpManagementService.onUninstallMcpServer(async e => {
189
this._onUninstallMcpServer.fire(e);
190
this._onUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.Workspace });
191
}));
192
193
this._register(this.workspaceMcpManagementService.onDidUninstallMcpServer(async e => {
194
this._onDidUninstallMcpServer.fire(e);
195
this._onDidUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.Workspace });
196
}));
197
198
this._register(this.workspaceMcpManagementService.onDidUpdateMcpServers(e => {
199
const { mcpServerInstallResult } = this.createInstallMcpServerResultsFromEvent(e, LocalMcpServerScope.Workspace);
200
this._onDidUpdateMcpServers.fire(mcpServerInstallResult);
201
this._onDidUpdateMcpServersInCurrentProfile.fire(mcpServerInstallResult);
202
}));
203
204
if (this.remoteMcpManagementService) {
205
this._register(this.remoteMcpManagementService.onInstallMcpServer(async e => {
206
this._onInstallMcpServer.fire(e);
207
const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);
208
if (remoteMcpResource ? uriIdentityService.extUri.isEqual(e.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {
209
this._onInstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.RemoteUser });
210
}
211
}));
212
213
this._register(this.remoteMcpManagementService.onDidInstallMcpServers(e => this.handleRemoteInstallMcpServerResultsFromEvent(e, this._onDidInstallMcpServers, this._onDidInstallMcpServersInCurrentProfile)));
214
this._register(this.remoteMcpManagementService.onDidUpdateMcpServers(e => this.handleRemoteInstallMcpServerResultsFromEvent(e, this._onDidInstallMcpServers, this._onDidInstallMcpServersInCurrentProfile)));
215
216
this._register(this.remoteMcpManagementService.onUninstallMcpServer(async e => {
217
this._onUninstallMcpServer.fire(e);
218
const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);
219
if (remoteMcpResource ? uriIdentityService.extUri.isEqual(e.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {
220
this._onUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.RemoteUser });
221
}
222
}));
223
224
this._register(this.remoteMcpManagementService.onDidUninstallMcpServer(async e => {
225
this._onDidUninstallMcpServer.fire(e);
226
const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);
227
if (remoteMcpResource ? uriIdentityService.extUri.isEqual(e.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {
228
this._onDidUninstallMcpServerInCurrentProfile.fire({ ...e, scope: LocalMcpServerScope.RemoteUser });
229
}
230
}));
231
}
232
233
this._register(userDataProfileService.onDidChangeCurrentProfile(e => {
234
if (!this.uriIdentityService.extUri.isEqual(e.previous.mcpResource, e.profile.mcpResource)) {
235
this._onDidChangeProfile.fire();
236
}
237
}));
238
}
239
240
private createInstallMcpServerResultsFromEvent(e: readonly InstallMcpServerResult[], scope: LocalMcpServerScope): { mcpServerInstallResult: IWorkbenchMcpServerInstallResult[]; mcpServerInstallResultInCurrentProfile: IWorkbenchMcpServerInstallResult[] } {
241
const mcpServerInstallResult: IWorkbenchMcpServerInstallResult[] = [];
242
const mcpServerInstallResultInCurrentProfile: IWorkbenchMcpServerInstallResult[] = [];
243
for (const result of e) {
244
const workbenchResult = {
245
...result,
246
local: result.local ? this.toWorkspaceMcpServer(result.local, scope) : undefined
247
};
248
mcpServerInstallResult.push(workbenchResult);
249
if (this.uriIdentityService.extUri.isEqual(result.mcpResource, this.userDataProfileService.currentProfile.mcpResource)) {
250
mcpServerInstallResultInCurrentProfile.push(workbenchResult);
251
}
252
}
253
254
return { mcpServerInstallResult, mcpServerInstallResultInCurrentProfile };
255
}
256
257
private async handleRemoteInstallMcpServerResultsFromEvent(e: readonly InstallMcpServerResult[], emitter: Emitter<readonly InstallMcpServerResult[]>, currentProfileEmitter: Emitter<readonly IWorkbenchMcpServerInstallResult[]>): Promise<void> {
258
const mcpServerInstallResult: IWorkbenchMcpServerInstallResult[] = [];
259
const mcpServerInstallResultInCurrentProfile: IWorkbenchMcpServerInstallResult[] = [];
260
const remoteMcpResource = await this.getRemoteMcpResource(this.userDataProfileService.currentProfile.mcpResource);
261
for (const result of e) {
262
const workbenchResult = {
263
...result,
264
local: result.local ? this.toWorkspaceMcpServer(result.local, LocalMcpServerScope.RemoteUser) : undefined
265
};
266
mcpServerInstallResult.push(workbenchResult);
267
if (remoteMcpResource ? this.uriIdentityService.extUri.isEqual(result.mcpResource, remoteMcpResource) : this.userDataProfileService.currentProfile.isDefault) {
268
mcpServerInstallResultInCurrentProfile.push(workbenchResult);
269
}
270
}
271
272
emitter.fire(mcpServerInstallResult);
273
if (mcpServerInstallResultInCurrentProfile.length) {
274
currentProfileEmitter.fire(mcpServerInstallResultInCurrentProfile);
275
}
276
}
277
278
async getInstalled(): Promise<IWorkbenchLocalMcpServer[]> {
279
const installed: IWorkbenchLocalMcpServer[] = [];
280
const [userServers, remoteServers, workspaceServers] = await Promise.all([
281
this.mcpManagementService.getInstalled(this.userDataProfileService.currentProfile.mcpResource),
282
this.remoteMcpManagementService?.getInstalled(await this.getRemoteMcpResource()) ?? Promise.resolve<ILocalMcpServer[]>([]),
283
this.workspaceMcpManagementService?.getInstalled() ?? Promise.resolve<ILocalMcpServer[]>([]),
284
]);
285
286
for (const server of userServers) {
287
installed.push(this.toWorkspaceMcpServer(server, LocalMcpServerScope.User));
288
}
289
for (const server of remoteServers) {
290
installed.push(this.toWorkspaceMcpServer(server, LocalMcpServerScope.RemoteUser));
291
}
292
for (const server of workspaceServers) {
293
installed.push(this.toWorkspaceMcpServer(server, LocalMcpServerScope.Workspace));
294
}
295
296
return installed;
297
}
298
299
private toWorkspaceMcpServer(server: ILocalMcpServer, scope: LocalMcpServerScope): IWorkbenchLocalMcpServer {
300
return { ...server, id: `mcp.config.${this.getConfigId(server, scope)}.${server.name}`, scope };
301
}
302
303
private getConfigId(server: ILocalMcpServer, scope: LocalMcpServerScope): string {
304
if (scope === LocalMcpServerScope.User) {
305
return USER_CONFIG_ID;
306
}
307
308
if (scope === LocalMcpServerScope.RemoteUser) {
309
return REMOTE_USER_CONFIG_ID;
310
}
311
312
if (scope === LocalMcpServerScope.Workspace) {
313
const workspace = this.workspaceContextService.getWorkspace();
314
if (workspace.configuration && this.uriIdentityService.extUri.isEqual(workspace.configuration, server.mcpResource)) {
315
return WORKSPACE_CONFIG_ID;
316
}
317
318
const workspaceFolders = workspace.folders;
319
for (let index = 0; index < workspaceFolders.length; index++) {
320
const workspaceFolder = workspaceFolders[index];
321
if (this.uriIdentityService.extUri.isEqual(this.uriIdentityService.extUri.joinPath(workspaceFolder.uri, WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]), server.mcpResource)) {
322
return `${WORKSPACE_FOLDER_CONFIG_ID_PREFIX}${index}`;
323
}
324
}
325
}
326
return 'unknown';
327
}
328
329
async install(server: IInstallableMcpServer, options?: IWorkbencMcpServerInstallOptions): Promise<IWorkbenchLocalMcpServer> {
330
options = options ?? {};
331
332
if (options.target === ConfigurationTarget.WORKSPACE || isWorkspaceFolder(options.target)) {
333
const mcpResource = options.target === ConfigurationTarget.WORKSPACE ? this.workspaceContextService.getWorkspace().configuration : options.target.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]);
334
if (!mcpResource) {
335
throw new Error(`Illegal target: ${options.target}`);
336
}
337
options.mcpResource = mcpResource;
338
const result = await this.workspaceMcpManagementService.install(server, options);
339
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.Workspace);
340
}
341
342
if (options.target === ConfigurationTarget.USER_REMOTE) {
343
if (!this.remoteMcpManagementService) {
344
throw new Error(`Illegal target: ${options.target}`);
345
}
346
options.mcpResource = await this.getRemoteMcpResource(options.mcpResource);
347
const result = await this.remoteMcpManagementService.install(server, options);
348
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.RemoteUser);
349
}
350
351
if (options.target && options.target !== ConfigurationTarget.USER && options.target !== ConfigurationTarget.USER_LOCAL) {
352
throw new Error(`Illegal target: ${options.target}`);
353
}
354
355
options.mcpResource = this.userDataProfileService.currentProfile.mcpResource;
356
const result = await this.mcpManagementService.install(server, options);
357
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User);
358
}
359
360
async installFromGallery(server: IGalleryMcpServer, options?: IWorkbencMcpServerInstallOptions): Promise<IWorkbenchLocalMcpServer> {
361
options = options ?? {};
362
363
if (options.target === ConfigurationTarget.WORKSPACE || isWorkspaceFolder(options.target)) {
364
const mcpResource = options.target === ConfigurationTarget.WORKSPACE ? this.workspaceContextService.getWorkspace().configuration : options.target.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]);
365
if (!mcpResource) {
366
throw new Error(`Illegal target: ${options.target}`);
367
}
368
options.mcpResource = mcpResource;
369
const result = await this.workspaceMcpManagementService.installFromGallery(server, options);
370
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.Workspace);
371
}
372
373
if (options.target === ConfigurationTarget.USER_REMOTE) {
374
if (!this.remoteMcpManagementService) {
375
throw new Error(`Illegal target: ${options.target}`);
376
}
377
options.mcpResource = await this.getRemoteMcpResource(options.mcpResource);
378
const result = await this.remoteMcpManagementService.installFromGallery(server, options);
379
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.RemoteUser);
380
}
381
382
if (options.target && options.target !== ConfigurationTarget.USER && options.target !== ConfigurationTarget.USER_LOCAL) {
383
throw new Error(`Illegal target: ${options.target}`);
384
}
385
386
if (!options.mcpResource) {
387
options.mcpResource = this.userDataProfileService.currentProfile.mcpResource;
388
}
389
const result = await this.mcpManagementService.installFromGallery(server, options);
390
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User);
391
}
392
393
async updateMetadata(local: IWorkbenchLocalMcpServer, server: IGalleryMcpServer, profileLocation: URI): Promise<IWorkbenchLocalMcpServer> {
394
if (local.scope === LocalMcpServerScope.Workspace) {
395
const result = await this.workspaceMcpManagementService.updateMetadata(local, server, profileLocation);
396
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.Workspace);
397
}
398
399
if (local.scope === LocalMcpServerScope.RemoteUser) {
400
if (!this.remoteMcpManagementService) {
401
throw new Error(`Illegal target: ${local.scope}`);
402
}
403
const result = await this.remoteMcpManagementService.updateMetadata(local, server, profileLocation);
404
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.RemoteUser);
405
}
406
407
const result = await this.mcpManagementService.updateMetadata(local, server, profileLocation);
408
return this.toWorkspaceMcpServer(result, LocalMcpServerScope.User);
409
}
410
411
async uninstall(server: IWorkbenchLocalMcpServer): Promise<void> {
412
if (server.scope === LocalMcpServerScope.Workspace) {
413
return this.workspaceMcpManagementService.uninstall(server);
414
}
415
416
if (server.scope === LocalMcpServerScope.RemoteUser) {
417
if (!this.remoteMcpManagementService) {
418
throw new Error(`Illegal target: ${server.scope}`);
419
}
420
return this.remoteMcpManagementService.uninstall(server);
421
}
422
423
return this.mcpManagementService.uninstall(server, { mcpResource: this.userDataProfileService.currentProfile.mcpResource });
424
}
425
426
private async getRemoteMcpResource(mcpResource?: URI): Promise<URI | undefined> {
427
if (!mcpResource && this.userDataProfileService.currentProfile.isDefault) {
428
return undefined;
429
}
430
mcpResource = mcpResource ?? this.userDataProfileService.currentProfile.mcpResource;
431
let profile = this.userDataProfilesService.profiles.find(p => this.uriIdentityService.extUri.isEqual(p.mcpResource, mcpResource));
432
if (profile) {
433
profile = await this.remoteUserDataProfilesService.getRemoteProfile(profile);
434
} else {
435
profile = (await this.remoteUserDataProfilesService.getRemoteProfiles()).find(p => this.uriIdentityService.extUri.isEqual(p.mcpResource, mcpResource));
436
}
437
return profile?.mcpResource;
438
}
439
}
440
441
class WorkspaceMcpResourceManagementService extends AbstractMcpResourceManagementService {
442
443
constructor(
444
mcpResource: URI,
445
target: McpResourceTarget,
446
@IMcpGalleryService mcpGalleryService: IMcpGalleryService,
447
@IFileService fileService: IFileService,
448
@IUriIdentityService uriIdentityService: IUriIdentityService,
449
@ILogService logService: ILogService,
450
@IMcpResourceScannerService mcpResourceScannerService: IMcpResourceScannerService,
451
) {
452
super(mcpResource, target, mcpGalleryService, fileService, uriIdentityService, logService, mcpResourceScannerService);
453
}
454
455
override async installFromGallery(server: IGalleryMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {
456
this.logService.trace('MCP Management Service: installGallery', server.name, server.galleryUrl);
457
458
this._onInstallMcpServer.fire({ name: server.name, mcpResource: this.mcpResource });
459
460
try {
461
const packageType = options?.packageType ?? server.configuration.packages?.[0]?.registryType ?? RegistryType.REMOTE;
462
463
const { mcpServerConfiguration, notices } = this.getMcpServerConfigurationFromManifest(server.configuration, packageType);
464
465
if (notices.length > 0) {
466
this.logService.warn(`MCP Management Service: Warnings while installing ${server.name}`, notices);
467
}
468
469
const installable: IInstallableMcpServer = {
470
name: server.name,
471
config: {
472
...mcpServerConfiguration.config,
473
gallery: server.galleryUrl ?? true,
474
version: server.version
475
},
476
inputs: mcpServerConfiguration.inputs
477
};
478
479
await this.mcpResourceScannerService.addMcpServers([installable], this.mcpResource, this.target);
480
481
await this.updateLocal();
482
const local = (await this.getInstalled()).find(s => s.name === server.name);
483
if (!local) {
484
throw new Error(`Failed to install MCP server: ${server.name}`);
485
}
486
return local;
487
} catch (e) {
488
this._onDidInstallMcpServers.fire([{ name: server.name, source: server, error: e, mcpResource: this.mcpResource }]);
489
throw e;
490
}
491
}
492
493
override updateMetadata(): Promise<ILocalMcpServer> {
494
throw new Error('Not supported');
495
}
496
497
protected override installFromUri(): Promise<ILocalMcpServer> {
498
throw new Error('Not supported');
499
}
500
501
protected override async getLocalServerInfo(name: string, mcpServerConfig: IMcpServerConfiguration): Promise<ILocalMcpServerInfo | undefined> {
502
if (!mcpServerConfig.gallery) {
503
return undefined;
504
}
505
506
const [mcpServer] = await this.mcpGalleryService.getMcpServersFromGallery([{ name }]);
507
if (!mcpServer) {
508
return undefined;
509
}
510
511
return {
512
name: mcpServer.name,
513
version: mcpServerConfig.version,
514
displayName: mcpServer.displayName,
515
description: mcpServer.description,
516
galleryUrl: mcpServer.galleryUrl,
517
manifest: mcpServer.configuration,
518
publisher: mcpServer.publisher,
519
publisherDisplayName: mcpServer.publisherDisplayName,
520
repositoryUrl: mcpServer.repositoryUrl,
521
icon: mcpServer.icon,
522
};
523
}
524
525
override canInstall(server: IGalleryMcpServer | IInstallableMcpServer): true | IMarkdownString {
526
throw new Error('Not supported');
527
}
528
}
529
530
class WorkspaceMcpManagementService extends AbstractMcpManagementService implements IMcpManagementService {
531
532
private readonly _onInstallMcpServer = this._register(new Emitter<InstallMcpServerEvent>());
533
readonly onInstallMcpServer = this._onInstallMcpServer.event;
534
535
private readonly _onDidInstallMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());
536
readonly onDidInstallMcpServers = this._onDidInstallMcpServers.event;
537
538
private readonly _onDidUpdateMcpServers = this._register(new Emitter<readonly InstallMcpServerResult[]>());
539
readonly onDidUpdateMcpServers = this._onDidUpdateMcpServers.event;
540
541
private readonly _onUninstallMcpServer = this._register(new Emitter<UninstallMcpServerEvent>());
542
readonly onUninstallMcpServer = this._onUninstallMcpServer.event;
543
544
private readonly _onDidUninstallMcpServer = this._register(new Emitter<DidUninstallMcpServerEvent>());
545
readonly onDidUninstallMcpServer = this._onDidUninstallMcpServer.event;
546
547
private allMcpServers: ILocalMcpServer[] = [];
548
549
private workspaceConfiguration?: URI | null;
550
private readonly workspaceMcpManagementServices = new ResourceMap<{ service: WorkspaceMcpResourceManagementService } & IDisposable>();
551
552
constructor(
553
@IAllowedMcpServersService allowedMcpServersService: IAllowedMcpServersService,
554
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
555
@ILogService logService: ILogService,
556
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
557
@IInstantiationService private readonly instantiationService: IInstantiationService,
558
) {
559
super(allowedMcpServersService, logService);
560
this.initialize();
561
}
562
563
private async initialize(): Promise<void> {
564
try {
565
await this.onDidChangeWorkbenchState();
566
await this.onDidChangeWorkspaceFolders({ added: this.workspaceContextService.getWorkspace().folders, removed: [], changed: [] });
567
this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(e => this.onDidChangeWorkspaceFolders(e)));
568
this._register(this.workspaceContextService.onDidChangeWorkbenchState(e => this.onDidChangeWorkbenchState()));
569
} catch (error) {
570
this.logService.error('Failed to initialize workspace folders', error);
571
}
572
}
573
574
private async onDidChangeWorkbenchState(): Promise<void> {
575
if (this.workspaceConfiguration) {
576
await this.removeWorkspaceService(this.workspaceConfiguration);
577
}
578
this.workspaceConfiguration = this.workspaceContextService.getWorkspace().configuration;
579
if (this.workspaceConfiguration) {
580
await this.addWorkspaceService(this.workspaceConfiguration, ConfigurationTarget.WORKSPACE);
581
}
582
}
583
584
private async onDidChangeWorkspaceFolders(e: IWorkspaceFoldersChangeEvent): Promise<void> {
585
try {
586
await Promise.allSettled(e.removed.map(folder => this.removeWorkspaceService(folder.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]))));
587
} catch (error) {
588
this.logService.error(error);
589
}
590
try {
591
await Promise.allSettled(e.added.map(folder => this.addWorkspaceService(folder.toResource(WORKSPACE_STANDALONE_CONFIGURATIONS[MCP_CONFIGURATION_KEY]), ConfigurationTarget.WORKSPACE_FOLDER)));
592
} catch (error) {
593
this.logService.error(error);
594
}
595
}
596
597
private async addWorkspaceService(mcpResource: URI, target: McpResourceTarget): Promise<void> {
598
if (this.workspaceMcpManagementServices.has(mcpResource)) {
599
return;
600
}
601
602
const disposables = new DisposableStore();
603
const service = disposables.add(this.instantiationService.createInstance(WorkspaceMcpResourceManagementService, mcpResource, target));
604
605
try {
606
const installedServers = await service.getInstalled();
607
this.allMcpServers.push(...installedServers);
608
if (installedServers.length > 0) {
609
const installResults: InstallMcpServerResult[] = installedServers.map(server => ({
610
name: server.name,
611
local: server,
612
mcpResource: server.mcpResource
613
}));
614
this._onDidInstallMcpServers.fire(installResults);
615
}
616
} catch (error) {
617
this.logService.warn('Failed to get installed servers from', mcpResource.toString(), error);
618
}
619
620
disposables.add(service.onInstallMcpServer(e => this._onInstallMcpServer.fire(e)));
621
disposables.add(service.onDidInstallMcpServers(e => {
622
for (const { local } of e) {
623
if (local) {
624
this.allMcpServers.push(local);
625
}
626
}
627
this._onDidInstallMcpServers.fire(e);
628
}));
629
disposables.add(service.onDidUpdateMcpServers(e => {
630
for (const { local, mcpResource } of e) {
631
if (local) {
632
const index = this.allMcpServers.findIndex(server => this.uriIdentityService.extUri.isEqual(server.mcpResource, mcpResource) && server.name === local.name);
633
if (index !== -1) {
634
this.allMcpServers.splice(index, 1, local);
635
}
636
}
637
}
638
this._onDidUpdateMcpServers.fire(e);
639
}));
640
disposables.add(service.onUninstallMcpServer(e => this._onUninstallMcpServer.fire(e)));
641
disposables.add(service.onDidUninstallMcpServer(e => {
642
const index = this.allMcpServers.findIndex(server => this.uriIdentityService.extUri.isEqual(server.mcpResource, e.mcpResource) && server.name === e.name);
643
if (index !== -1) {
644
this.allMcpServers.splice(index, 1);
645
this._onDidUninstallMcpServer.fire(e);
646
}
647
}));
648
this.workspaceMcpManagementServices.set(mcpResource, { service, dispose: () => disposables.dispose() });
649
}
650
651
private async removeWorkspaceService(mcpResource: URI): Promise<void> {
652
const serviceItem = this.workspaceMcpManagementServices.get(mcpResource);
653
if (serviceItem) {
654
try {
655
const installedServers = await serviceItem.service.getInstalled();
656
this.allMcpServers = this.allMcpServers.filter(server => !installedServers.some(uninstalled => this.uriIdentityService.extUri.isEqual(uninstalled.mcpResource, server.mcpResource)));
657
for (const server of installedServers) {
658
this._onDidUninstallMcpServer.fire({
659
name: server.name,
660
mcpResource: server.mcpResource
661
});
662
}
663
} catch (error) {
664
this.logService.warn('Failed to get installed servers from', mcpResource.toString(), error);
665
}
666
this.workspaceMcpManagementServices.delete(mcpResource);
667
serviceItem.dispose();
668
}
669
}
670
671
async getInstalled(): Promise<ILocalMcpServer[]> {
672
return this.allMcpServers;
673
}
674
675
async install(server: IInstallableMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {
676
if (!options?.mcpResource) {
677
throw new Error('MCP resource is required');
678
}
679
680
const mcpManagementServiceItem = this.workspaceMcpManagementServices.get(options?.mcpResource);
681
if (!mcpManagementServiceItem) {
682
throw new Error(`No MCP management service found for resource: ${options?.mcpResource.toString()}`);
683
}
684
685
return mcpManagementServiceItem.service.install(server, options);
686
}
687
688
async uninstall(server: ILocalMcpServer, options?: UninstallOptions): Promise<void> {
689
const mcpResource = server.mcpResource;
690
691
const mcpManagementServiceItem = this.workspaceMcpManagementServices.get(mcpResource);
692
if (!mcpManagementServiceItem) {
693
throw new Error(`No MCP management service found for resource: ${mcpResource.toString()}`);
694
}
695
696
return mcpManagementServiceItem.service.uninstall(server, options);
697
}
698
699
installFromGallery(gallery: IGalleryMcpServer, options?: InstallOptions): Promise<ILocalMcpServer> {
700
if (!options?.mcpResource) {
701
throw new Error('MCP resource is required');
702
}
703
704
const mcpManagementServiceItem = this.workspaceMcpManagementServices.get(options?.mcpResource);
705
if (!mcpManagementServiceItem) {
706
throw new Error(`No MCP management service found for resource: ${options?.mcpResource.toString()}`);
707
}
708
709
return mcpManagementServiceItem.service.installFromGallery(gallery, options);
710
}
711
712
updateMetadata(): Promise<ILocalMcpServer> {
713
throw new Error('Not supported');
714
}
715
716
override dispose(): void {
717
this.workspaceMcpManagementServices.forEach(service => service.dispose());
718
this.workspaceMcpManagementServices.clear();
719
super.dispose();
720
}
721
}
722
723