Skip to content

Commit bfa2d76

Browse files
authored
1 parent ebd5fb1 commit bfa2d76

File tree

4 files changed

+120
-59
lines changed

4 files changed

+120
-59
lines changed

src/vs/workbench/contrib/mcp/browser/mcpWorkbenchService.ts

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import { IEditorOptions } from '../../../../platform/editor/common/editor.js';
1818
import { IFileService } from '../../../../platform/files/common/files.js';
1919
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
2020
import { ILabelService } from '../../../../platform/label/common/label.js';
21-
import { DidUninstallMcpServerEvent, IGalleryMcpServer, IMcpGalleryService, InstallMcpServerResult, IQueryOptions, IInstallableMcpServer, IMcpServerManifest, ILocalMcpServer } from '../../../../platform/mcp/common/mcpManagement.js';
21+
import { ILogService } from '../../../../platform/log/common/log.js';
22+
import { IGalleryMcpServer, IMcpGalleryService, IQueryOptions, IInstallableMcpServer, IMcpServerManifest, ILocalMcpServer } from '../../../../platform/mcp/common/mcpManagement.js';
2223
import { IMcpServerConfiguration, IMcpServerVariable, IMcpStdioServerConfiguration, McpServerType } from '../../../../platform/mcp/common/mcpPlatformTypes.js';
2324
import { IProductService } from '../../../../platform/product/common/productService.js';
2425
import { StorageScope } from '../../../../platform/storage/common/storage.js';
@@ -30,7 +31,7 @@ import { IWorkbenchContribution } from '../../../common/contributions.js';
3031
import { MCP_CONFIGURATION_KEY, WORKSPACE_STANDALONE_CONFIGURATIONS } from '../../../services/configuration/common/configuration.js';
3132
import { ACTIVE_GROUP, IEditorService } from '../../../services/editor/common/editorService.js';
3233
import { IWorkbenchEnvironmentService } from '../../../services/environment/common/environmentService.js';
33-
import { IWorkbenchLocalMcpServer, IWorkbenchMcpManagementService, LocalMcpServerScope } from '../../../services/mcp/common/mcpWorkbenchManagementService.js';
34+
import { DidUninstallWorkbenchMcpServerEvent, IWorkbenchLocalMcpServer, IWorkbenchMcpManagementService, IWorkbenchMcpServerInstallResult, LocalMcpServerScope } from '../../../services/mcp/common/mcpWorkbenchManagementService.js';
3435
import { IRemoteAgentService } from '../../../services/remote/common/remoteAgentService.js';
3536
import { mcpConfigurationSection } from '../common/mcpConfiguration.js';
3637
import { HasInstalledMcpServersContext, IMcpConfigPath, IMcpWorkbenchService, IWorkbenchMcpServer, McpCollectionSortOrder, McpServerInstallState, McpServersGalleryEnabledContext } from '../common/mcpTypes.js';
@@ -167,6 +168,7 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ
167168
@IProductService private readonly productService: IProductService,
168169
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
169170
@IInstantiationService private readonly instantiationService: IInstantiationService,
171+
@ILogService private readonly logService: ILogService,
170172
@IURLService urlService: IURLService,
171173
) {
172174
super();
@@ -184,19 +186,28 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ
184186
this._onReset.fire();
185187
}
186188

187-
private onDidUninstallMcpServer(e: DidUninstallMcpServerEvent) {
189+
private areSameMcpServers(a: { name: string; scope: LocalMcpServerScope } | undefined, b: { name: string; scope: LocalMcpServerScope } | undefined): boolean {
190+
if (a === b) {
191+
return true;
192+
}
193+
if (!a || !b) {
194+
return false;
195+
}
196+
return a.name === b.name && a.scope === b.scope;
197+
}
198+
199+
private onDidUninstallMcpServer(e: DidUninstallWorkbenchMcpServerEvent) {
188200
if (e.error) {
189201
return;
190202
}
191-
const server = this._local.find(server => server.local?.name === e.name);
192-
if (server) {
193-
this._local = this._local.filter(server => server.local?.name !== e.name);
194-
server.local = undefined;
195-
this._onChange.fire(server);
203+
const uninstalled = this._local.find(server => this.areSameMcpServers(server.local, e));
204+
if (uninstalled) {
205+
this._local = this._local.filter(server => server !== uninstalled);
206+
this._onChange.fire(uninstalled);
196207
}
197208
}
198209

199-
private onDidInstallMcpServers(e: readonly InstallMcpServerResult[]) {
210+
private onDidInstallMcpServers(e: readonly IWorkbenchMcpServerInstallResult[]) {
200211
const servers: IWorkbenchMcpServer[] = [];
201212
for (const result of e) {
202213
if (!result.local) {
@@ -210,25 +221,25 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ
210221
}
211222

212223
private onDidInstallMcpServer(local: IWorkbenchLocalMcpServer, gallery?: IGalleryMcpServer): IWorkbenchMcpServer {
213-
let server = this.installing.find(server => server.name === local.name);
224+
let server = this.installing.find(server => server.local ? this.areSameMcpServers(server.local, local) : server.name === local.name);
214225
this.installing = server ? this.installing.filter(e => e !== server) : this.installing;
215226
if (server) {
216227
server.local = local;
217228
} else {
218229
server = this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), local, gallery, undefined);
219230
}
220-
this._local = this._local.filter(e => e.name !== local.name);
231+
this._local = this._local.filter(server => !this.areSameMcpServers(server.local, local));
221232
this._local.push(server);
222233
this._onChange.fire(server);
223234
return server;
224235
}
225236

226-
private onDidUpdateMcpServers(e: readonly InstallMcpServerResult[]) {
237+
private onDidUpdateMcpServers(e: readonly IWorkbenchMcpServerInstallResult[]) {
227238
for (const result of e) {
228239
if (!result.local) {
229240
continue;
230241
}
231-
const serverIndex = this._local.findIndex(server => server.local?.name === result.name);
242+
const serverIndex = this._local.findIndex(server => this.areSameMcpServers(server.local, result.local));
232243
let server: McpWorkbenchServer;
233244
if (serverIndex !== -1) {
234245
this._local[serverIndex].local = result.local;
@@ -301,14 +312,48 @@ export class McpWorkbenchService extends Disposable implements IMcpWorkbenchServ
301312
async queryLocal(): Promise<IWorkbenchMcpServer[]> {
302313
const installed = await this.mcpManagementService.getInstalled();
303314
this._local = installed.map(i => {
304-
const local = this._local.find(server => server.name === i.name) ?? this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, undefined, undefined);
315+
const local = this.instantiationService.createInstance(McpWorkbenchServer, e => this.getInstallState(e), undefined, undefined, undefined);
305316
local.local = i;
306317
return local;
307318
});
308319
this._onChange.fire(undefined);
309320
return [...this.local];
310321
}
311322

323+
getEnabledLocalMcpServers(): IWorkbenchLocalMcpServer[] {
324+
const result = new Map<string, IWorkbenchLocalMcpServer>();
325+
const userRemote: IWorkbenchLocalMcpServer[] = [];
326+
const workspace: IWorkbenchLocalMcpServer[] = [];
327+
328+
for (const server of this.local) {
329+
if (server.local?.scope === LocalMcpServerScope.User) {
330+
result.set(server.name, server.local);
331+
} else if (server.local?.scope === LocalMcpServerScope.RemoteUser) {
332+
userRemote.push(server.local);
333+
} else if (server.local?.scope === LocalMcpServerScope.Workspace) {
334+
workspace.push(server.local);
335+
}
336+
}
337+
338+
for (const server of userRemote) {
339+
const existing = result.get(server.name);
340+
if (existing) {
341+
this.logService.warn(localize('overwriting', "Overwriting mcp server '{0}' from {1} with {2}.", server.name, server.mcpResource.path, existing.mcpResource.path));
342+
}
343+
result.set(server.name, server);
344+
}
345+
346+
for (const server of workspace) {
347+
const existing = result.get(server.name);
348+
if (existing) {
349+
this.logService.warn(localize('overwriting', "Overwriting mcp server '{0}' from {1} with {2}.", server.name, server.mcpResource.path, existing.mcpResource.path));
350+
}
351+
result.set(server.name, server);
352+
}
353+
354+
return [...result.values()];
355+
}
356+
312357
canInstall(mcpServer: IWorkbenchMcpServer): true | IMarkdownString {
313358
if (!(mcpServer instanceof McpWorkbenchServer)) {
314359
return new MarkdownString().appendText(localize('not an extension', "The provided object is not an mcp server."));

src/vs/workbench/contrib/mcp/common/discovery/installedMcpServersDiscovery.ts

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,18 @@ import { URI } from '../../../../../base/common/uri.js';
99
import { ConfigurationTarget } from '../../../../../platform/configuration/common/configuration.js';
1010
import { StorageScope } from '../../../../../platform/storage/common/storage.js';
1111
import { IMcpRegistry } from '../mcpRegistryTypes.js';
12-
import { McpServerDefinition, McpServerTransportType, IMcpWorkbenchService, IMcpConfigPath, IWorkbenchMcpServer } from '../mcpTypes.js';
12+
import { McpServerDefinition, McpServerTransportType, IMcpWorkbenchService, IMcpConfigPath } from '../mcpTypes.js';
1313
import { IMcpDiscovery } from './mcpDiscovery.js';
1414
import { mcpConfigurationSection } from '../mcpConfiguration.js';
1515
import { posix as pathPosix, win32 as pathWin32, sep as pathSep } from '../../../../../base/common/path.js';
1616
import { ITextModelService } from '../../../../../editor/common/services/resolverService.js';
1717
import { getMcpServerMapping } from '../mcpConfigFileUtils.js';
1818
import { Location } from '../../../../../editor/common/languages.js';
1919
import { ResourceMap } from '../../../../../base/common/map.js';
20-
import { ILocalMcpServer } from '../../../../../platform/mcp/common/mcpManagement.js';
2120
import { observableValue } from '../../../../../base/common/observable.js';
2221
import { IRemoteAgentService } from '../../../../services/remote/common/remoteAgentService.js';
2322
import { isWindows, OperatingSystem } from '../../../../../base/common/platform.js';
23+
import { IWorkbenchLocalMcpServer } from '../../../../services/mcp/common/mcpWorkbenchManagementService.js';
2424

2525
export class InstalledMcpServersDiscovery extends Disposable implements IMcpDiscovery {
2626

@@ -58,30 +58,26 @@ export class InstalledMcpServersDiscovery extends Disposable implements IMcpDisc
5858
private async sync(): Promise<void> {
5959
try {
6060
const remoteEnv = await this.remoteAgentService.getEnvironment();
61-
const collections = new Map<string, [IMcpConfigPath | undefined, McpServerDefinition[], IWorkbenchMcpServer]>();
61+
const collections = new Map<string, [IMcpConfigPath | undefined, McpServerDefinition[]]>();
6262
const mcpConfigPathInfos = new ResourceMap<Promise<IMcpConfigPath & { locations: Map<string, Location> } | undefined>>();
63-
for (const server of this.mcpWorkbenchService.local) {
64-
if (!server.local) {
65-
continue;
66-
}
67-
68-
let mcpConfigPathPromise = mcpConfigPathInfos.get(server.local.mcpResource);
63+
for (const server of this.mcpWorkbenchService.getEnabledLocalMcpServers()) {
64+
let mcpConfigPathPromise = mcpConfigPathInfos.get(server.mcpResource);
6965
if (!mcpConfigPathPromise) {
70-
mcpConfigPathPromise = (async (local: ILocalMcpServer) => {
66+
mcpConfigPathPromise = (async (local: IWorkbenchLocalMcpServer) => {
7167
const mcpConfigPath = this.mcpWorkbenchService.getMcpConfigPath(local);
7268
const locations = mcpConfigPath?.uri ? await this.getServerIdMapping(mcpConfigPath?.uri, mcpConfigPath.section ? [...mcpConfigPath.section, 'servers'] : ['servers']) : new Map();
7369
return mcpConfigPath ? { ...mcpConfigPath, locations } : undefined;
74-
})(server.local);
75-
mcpConfigPathInfos.set(server.local.mcpResource, mcpConfigPathPromise);
70+
})(server);
71+
mcpConfigPathInfos.set(server.mcpResource, mcpConfigPathPromise);
7672
}
7773

78-
const config = server.local.config;
74+
const config = server.config;
7975
const mcpConfigPath = await mcpConfigPathPromise;
8076
const collectionId = `mcp.config.${mcpConfigPath ? mcpConfigPath.id : 'unknown'}`;
8177

8278
let definitions = collections.get(collectionId);
8379
if (!definitions) {
84-
definitions = [mcpConfigPath, [], server];
80+
definitions = [mcpConfigPath, []];
8581
collections.set(collectionId, definitions);
8682
}
8783

@@ -94,8 +90,8 @@ export class InstalledMcpServersDiscovery extends Disposable implements IMcpDisc
9490
};
9591

9692
definitions[1].push({
97-
id: `${collectionId}.${server.local.name}`,
98-
label: server.local.name,
93+
id: `${collectionId}.${server.name}`,
94+
label: server.name,
9995
launch: config.type === 'http' ? {
10096
type: McpServerTransportType.HTTP,
10197
uri: URI.parse(config.url),
@@ -127,7 +123,7 @@ export class InstalledMcpServersDiscovery extends Disposable implements IMcpDisc
127123
devMode: config.dev,
128124
presentation: {
129125
order: mcpConfigPath?.order,
130-
origin: mcpConfigPath?.locations.get(server.local.name)
126+
origin: mcpConfigPath?.locations.get(server.name)
131127
}
132128
});
133129
}

src/vs/workbench/contrib/mcp/common/mcpTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ export interface IMcpWorkbenchService {
621621
readonly onChange: Event<IWorkbenchMcpServer | undefined>;
622622
readonly onReset: Event<void>;
623623
readonly local: readonly IWorkbenchMcpServer[];
624+
getEnabledLocalMcpServers(): IWorkbenchLocalMcpServer[];
624625
queryLocal(): Promise<IWorkbenchMcpServer[]>;
625626
queryGallery(options?: IQueryOptions, token?: CancellationToken): Promise<IWorkbenchMcpServer[]>;
626627
canInstall(mcpServer: IWorkbenchMcpServer): true | IMarkdownString;

0 commit comments

Comments
 (0)