From 48aa70ff7641da88fe23ec563a07dad92c0e28ce Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 25 Aug 2025 14:27:21 +0200 Subject: [PATCH] Introduce MCP gallery manifest service (#263238) --- .../sharedProcess/sharedProcessMain.ts | 3 + src/vs/code/node/cliProcessMain.ts | 3 + .../platform/mcp/common/mcpGalleryManifest.ts | 52 +++++++++++++ .../mcp/common/mcpGalleryManifestService.ts | 47 ++++++++++++ .../common/mcpGalleryManifestServiceIpc.ts | 54 +++++++++++++ .../platform/mcp/common/mcpGalleryService.ts | 67 ++++++++-------- src/vs/server/node/serverServices.ts | 3 + .../mcp/browser/mcpGalleryManifestService.ts | 31 ++++++++ .../mcpGalleryManifestService.ts | 76 +++++++++++++++++++ src/vs/workbench/workbench.desktop.main.ts | 1 + .../workbench/workbench.web.main.internal.ts | 1 + 11 files changed, 301 insertions(+), 37 deletions(-) create mode 100644 src/vs/platform/mcp/common/mcpGalleryManifest.ts create mode 100644 src/vs/platform/mcp/common/mcpGalleryManifestService.ts create mode 100644 src/vs/platform/mcp/common/mcpGalleryManifestServiceIpc.ts create mode 100644 src/vs/workbench/services/mcp/browser/mcpGalleryManifestService.ts create mode 100644 src/vs/workbench/services/mcp/electron-browser/mcpGalleryManifestService.ts diff --git a/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts index 88244e91cee..42073a631fd 100644 --- a/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-utility/sharedProcess/sharedProcessMain.ts @@ -131,6 +131,8 @@ import { IMcpResourceScannerService, McpResourceScannerService } from '../../../ import { McpGalleryService } from '../../../platform/mcp/common/mcpGalleryService.js'; import { McpManagementChannel } from '../../../platform/mcp/common/mcpManagementIpc.js'; import { AllowedMcpServersService } from '../../../platform/mcp/common/allowedMcpServersService.js'; +import { IMcpGalleryManifestService } from '../../../platform/mcp/common/mcpGalleryManifest.js'; +import { McpGalleryManifestIPCService } from '../../../platform/mcp/common/mcpGalleryManifestServiceIpc.js'; class SharedProcessMain extends Disposable implements IClientConnectionFilter { @@ -343,6 +345,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { // MCP Management services.set(IAllowedMcpServersService, new SyncDescriptor(AllowedMcpServersService, undefined, true)); + services.set(IMcpGalleryManifestService, new McpGalleryManifestIPCService(this.server)); services.set(IMcpGalleryService, new SyncDescriptor(McpGalleryService, undefined, true)); services.set(IMcpResourceScannerService, new SyncDescriptor(McpResourceScannerService, undefined, true)); services.set(INpmPackageManagementService, new SyncDescriptor(NpmPackageService, undefined, true)); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 06424a087ac..9172772a7bf 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -74,6 +74,8 @@ import { INpmPackageManagementService, NpmPackageService } from '../../platform/ import { IMcpResourceScannerService, McpResourceScannerService } from '../../platform/mcp/common/mcpResourceScannerService.js'; import { McpGalleryService } from '../../platform/mcp/common/mcpGalleryService.js'; import { AllowedMcpServersService } from '../../platform/mcp/common/allowedMcpServersService.js'; +import { IMcpGalleryManifestService } from '../../platform/mcp/common/mcpGalleryManifest.js'; +import { McpGalleryManifestService } from '../../platform/mcp/common/mcpGalleryManifestService.js'; class CliMain extends Disposable { @@ -233,6 +235,7 @@ class CliMain extends Disposable { // MCP services.set(IAllowedMcpServersService, new SyncDescriptor(AllowedMcpServersService, undefined, true)); services.set(IMcpResourceScannerService, new SyncDescriptor(McpResourceScannerService, undefined, true)); + services.set(IMcpGalleryManifestService, new SyncDescriptor(McpGalleryManifestService, undefined, true)); services.set(IMcpGalleryService, new SyncDescriptor(McpGalleryService, undefined, true)); services.set(INpmPackageManagementService, new SyncDescriptor(NpmPackageService, undefined, true)); services.set(IMcpManagementService, new SyncDescriptor(McpManagementService, undefined, true)); diff --git a/src/vs/platform/mcp/common/mcpGalleryManifest.ts b/src/vs/platform/mcp/common/mcpGalleryManifest.ts new file mode 100644 index 00000000000..2cc014e36a3 --- /dev/null +++ b/src/vs/platform/mcp/common/mcpGalleryManifest.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from '../../../base/common/event.js'; +import { createDecorator } from '../../instantiation/common/instantiation.js'; + +export const enum McpGalleryResourceType { + McpQueryService = 'McpQueryService', + McpServerManifestUri = 'McpServerManifestUriTemplate', +} + +export type McpGalleryManifestResource = { + readonly id: string; + readonly type: string; +}; + +export interface IMcpGalleryManifest { + readonly resources: readonly McpGalleryManifestResource[]; +} + +export const enum McpGalleryManifestStatus { + Available = 'available', + Unavailable = 'unavailable' +} + +export const IMcpGalleryManifestService = createDecorator('IMcpGalleryManifestService'); + +export interface IMcpGalleryManifestService { + readonly _serviceBrand: undefined; + + readonly mcpGalleryManifestStatus: McpGalleryManifestStatus; + readonly onDidChangeMcpGalleryManifestStatus: Event; + readonly onDidChangeMcpGalleryManifest: Event; + getMcpGalleryManifest(): Promise; +} + +export function getMcpGalleryManifestResourceUri(manifest: IMcpGalleryManifest, type: string): string | undefined { + const [name, version] = type.split('/'); + for (const resource of manifest.resources) { + const [r, v] = resource.type.split('/'); + if (r !== name) { + continue; + } + if (!version || v === version) { + return resource.id; + } + break; + } + return undefined; +} diff --git a/src/vs/platform/mcp/common/mcpGalleryManifestService.ts b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts new file mode 100644 index 00000000000..d9cbc9e4398 --- /dev/null +++ b/src/vs/platform/mcp/common/mcpGalleryManifestService.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from '../../../base/common/event.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; +import { IProductService } from '../../product/common/productService.js'; +import { McpGalleryResourceType, IMcpGalleryManifest, IMcpGalleryManifestService, McpGalleryManifestStatus } from './mcpGalleryManifest.js'; + +export class McpGalleryManifestService extends Disposable implements IMcpGalleryManifestService { + + readonly _serviceBrand: undefined; + readonly onDidChangeMcpGalleryManifest = Event.None; + readonly onDidChangeMcpGalleryManifestStatus = Event.None; + + get mcpGalleryManifestStatus(): McpGalleryManifestStatus { + return !!this.productService.extensionsGallery?.mcpUrl ? McpGalleryManifestStatus.Available : McpGalleryManifestStatus.Unavailable; + } + + constructor( + @IProductService protected readonly productService: IProductService, + ) { + super(); + } + + async getMcpGalleryManifest(): Promise { + return null; + } + + protected createMcpGalleryManifest(mcpUrl: string): IMcpGalleryManifest { + const resources = [ + { + id: mcpUrl, + type: McpGalleryResourceType.McpQueryService + }, + { + id: `${mcpUrl}/{id}`, + type: McpGalleryResourceType.McpServerManifestUri + } + ]; + + return { + resources + }; + } +} diff --git a/src/vs/platform/mcp/common/mcpGalleryManifestServiceIpc.ts b/src/vs/platform/mcp/common/mcpGalleryManifestServiceIpc.ts new file mode 100644 index 00000000000..616aa21ea32 --- /dev/null +++ b/src/vs/platform/mcp/common/mcpGalleryManifestServiceIpc.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Barrier } from '../../../base/common/async.js'; +import { Emitter, Event } from '../../../base/common/event.js'; +import { Disposable } from '../../../base/common/lifecycle.js'; +import { IPCServer } from '../../../base/parts/ipc/common/ipc.js'; +import { IMcpGalleryManifest, IMcpGalleryManifestService, McpGalleryManifestStatus } from './mcpGalleryManifest.js'; + +export class McpGalleryManifestIPCService extends Disposable implements IMcpGalleryManifestService { + + declare readonly _serviceBrand: undefined; + + private _onDidChangeMcpGalleryManifest = this._register(new Emitter()); + readonly onDidChangeMcpGalleryManifest = this._onDidChangeMcpGalleryManifest.event; + + private _onDidChangeMcpGalleryManifestStatus = this._register(new Emitter()); + readonly onDidChangeMcpGalleryManifestStatus = this._onDidChangeMcpGalleryManifestStatus.event; + + private _mcpGalleryManifest: IMcpGalleryManifest | null | undefined; + private readonly barrier = new Barrier(); + + get mcpGalleryManifestStatus(): McpGalleryManifestStatus { + return this._mcpGalleryManifest ? McpGalleryManifestStatus.Available : McpGalleryManifestStatus.Unavailable; + } + + constructor(server: IPCServer) { + super(); + server.registerChannel('mcpGalleryManifest', { + listen: () => Event.None, + call: async (context: any, command: string, args?: any): Promise => { + switch (command) { + case 'setMcpGalleryManifest': return Promise.resolve(this.setMcpGalleryManifest(args[0])); + } + throw new Error('Invalid call'); + } + }); + } + + async getMcpGalleryManifest(): Promise { + await this.barrier.wait(); + return this._mcpGalleryManifest ?? null; + } + + private setMcpGalleryManifest(manifest: IMcpGalleryManifest | null): void { + this._mcpGalleryManifest = manifest; + this._onDidChangeMcpGalleryManifest.fire(manifest); + this._onDidChangeMcpGalleryManifestStatus.fire(this.mcpGalleryManifestStatus); + this.barrier.open(); + } + +} diff --git a/src/vs/platform/mcp/common/mcpGalleryService.ts b/src/vs/platform/mcp/common/mcpGalleryService.ts index b72e3a20175..de061099672 100644 --- a/src/vs/platform/mcp/common/mcpGalleryService.ts +++ b/src/vs/platform/mcp/common/mcpGalleryService.ts @@ -7,17 +7,15 @@ import { CancellationToken } from '../../../base/common/cancellation.js'; import { MarkdownString } from '../../../base/common/htmlContent.js'; import { Disposable } from '../../../base/common/lifecycle.js'; import { Schemas } from '../../../base/common/network.js'; -import { dirname, joinPath } from '../../../base/common/resources.js'; -import { uppercaseFirstLetter } from '../../../base/common/strings.js'; -import { isString } from '../../../base/common/types.js'; +import { format2, uppercaseFirstLetter } from '../../../base/common/strings.js'; import { URI } from '../../../base/common/uri.js'; import { localize } from '../../../nls.js'; -import { IConfigurationService } from '../../configuration/common/configuration.js'; import { IFileService } from '../../files/common/files.js'; import { ILogService } from '../../log/common/log.js'; import { IProductService } from '../../product/common/productService.js'; import { asJson, asText, IRequestService } from '../../request/common/request.js'; -import { IGalleryMcpServer, IMcpGalleryService, IMcpServerManifest, IQueryOptions, mcpGalleryServiceUrlConfig, PackageType } from './mcpManagement.js'; +import { IGalleryMcpServer, IMcpGalleryService, IMcpServerManifest, IQueryOptions, PackageType } from './mcpManagement.js'; +import { IMcpGalleryManifestService, McpGalleryManifestStatus, getMcpGalleryManifestResourceUri, McpGalleryResourceType, IMcpGalleryManifest } from './mcpGalleryManifest.js'; interface IRawGalleryServersResult { readonly servers: readonly IRawGalleryMcpServer[]; @@ -58,21 +56,26 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService _serviceBrand: undefined; constructor( - @IConfigurationService private readonly configurationService: IConfigurationService, @IRequestService private readonly requestService: IRequestService, @IFileService private readonly fileService: IFileService, @IProductService private readonly productService: IProductService, @ILogService private readonly logService: ILogService, + @IMcpGalleryManifestService private readonly mcpGalleryManifestService: IMcpGalleryManifestService, ) { super(); } isEnabled(): boolean { - return this.getMcpGalleryUrl() !== undefined; + return this.mcpGalleryManifestService.mcpGalleryManifestStatus === McpGalleryManifestStatus.Available; } async query(options?: IQueryOptions, token: CancellationToken = CancellationToken.None): Promise { - let { servers } = await this.fetchGallery(token); + const mcpGalleryManifest = await this.mcpGalleryManifestService.getMcpGalleryManifest(); + if (!mcpGalleryManifest) { + return []; + } + + let { servers } = await this.fetchGallery(mcpGalleryManifest, token); if (options?.text) { const searchText = options.text.toLowerCase(); @@ -81,21 +84,21 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService const galleryServers: IGalleryMcpServer[] = []; for (const item of servers) { - galleryServers.push(this.toGalleryMcpServer(item)); + galleryServers.push(this.toGalleryMcpServer(item, mcpGalleryManifest)); } return galleryServers; } async getMcpServers(names: string[]): Promise { - const mcpUrl = this.getMcpGalleryUrl() ?? this.productService.extensionsGallery?.mcpUrl; - if (!mcpUrl) { + const mcpGalleryManifest = await this.mcpGalleryManifestService.getMcpGalleryManifest(); + if (!mcpGalleryManifest) { return []; } - const { servers } = await this.fetchGallery(mcpUrl, CancellationToken.None); + const { servers } = await this.fetchGallery(mcpGalleryManifest, CancellationToken.None); const filteredServers = servers.filter(item => names.includes(item.name)); - return filteredServers.map(item => this.toGalleryMcpServer(item)); + return filteredServers.map(item => this.toGalleryMcpServer(item, mcpGalleryManifest)); } async getManifest(gallery: IGalleryMcpServer, token: CancellationToken): Promise { @@ -167,7 +170,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return result; } - private toGalleryMcpServer(item: IRawGalleryMcpServer): IGalleryMcpServer { + private toGalleryMcpServer(item: IRawGalleryMcpServer, mcpGalleryManifest: IMcpGalleryManifest): IGalleryMcpServer { let publisher = ''; const nameParts = item.name.split('/'); if (nameParts.length > 0) { @@ -178,7 +181,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService } let icon: { light: string; dark: string } | undefined; - const mcpGalleryUrl = this.getMcpGalleryUrl(); + const mcpGalleryUrl = this.getMcpGalleryUrl(mcpGalleryManifest); if (mcpGalleryUrl && this.productService.extensionsGallery?.mcpUrl !== mcpGalleryUrl) { if (item.iconUrl) { icon = { @@ -206,7 +209,7 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService codicon: item.codicon, icon, readmeUrl: item.readmeUrl, - manifestUrl: this.getManifestUrl(item), + manifestUrl: this.getManifestUrl(item, mcpGalleryManifest), packageTypes: item.package_types ?? [], publisher, publisherDisplayName: item.publisher?.displayName, @@ -218,15 +221,12 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService }; } - private async fetchGallery(token: CancellationToken): Promise; - private async fetchGallery(url: string, token: CancellationToken): Promise; - private async fetchGallery(arg1: any, arg2?: any): Promise { - const mcpGalleryUrl = isString(arg1) ? arg1 : this.getMcpGalleryUrl(); + private async fetchGallery(mcpGalleryManifest: IMcpGalleryManifest, token: CancellationToken): Promise { + const mcpGalleryUrl = this.getMcpGalleryUrl(mcpGalleryManifest); if (!mcpGalleryUrl) { - return Promise.resolve({ servers: [] }); + return { servers: [] }; } - const token = isString(arg1) ? arg2 : arg1; const uri = URI.parse(mcpGalleryUrl); if (uri.scheme === Schemas.file) { try { @@ -247,26 +247,19 @@ export class McpGalleryService extends Disposable implements IMcpGalleryService return result || { servers: [] }; } - private getManifestUrl(item: IRawGalleryMcpServer): string | undefined { - const mcpGalleryUrl = this.getMcpGalleryUrl(); - - if (!mcpGalleryUrl) { + private getManifestUrl(item: IRawGalleryMcpServer, mcpGalleryManifest: IMcpGalleryManifest): string | undefined { + if (!item.id) { return undefined; } - - const uri = URI.parse(mcpGalleryUrl); - if (uri.scheme === Schemas.file) { - return joinPath(dirname(uri), item.id ?? item.name).fsPath; + const resourceUriTemplate = getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpServerManifestUri); + if (!resourceUriTemplate) { + return undefined; } - - return `${mcpGalleryUrl}/${item.id}`; + return format2(resourceUriTemplate, { id: item.id }); } - private getMcpGalleryUrl(): string | undefined { - if (this.productService.quality === 'stable') { - return undefined; - } - return this.configurationService.getValue(mcpGalleryServiceUrlConfig); + private getMcpGalleryUrl(mcpGalleryManifest: IMcpGalleryManifest): string | undefined { + return getMcpGalleryManifestResourceUri(mcpGalleryManifest, McpGalleryResourceType.McpQueryService); } } diff --git a/src/vs/server/node/serverServices.ts b/src/vs/server/node/serverServices.ts index da7bc2499f1..4eabaca7997 100644 --- a/src/vs/server/node/serverServices.ts +++ b/src/vs/server/node/serverServices.ts @@ -92,6 +92,8 @@ import { McpGalleryService } from '../../platform/mcp/common/mcpGalleryService.j import { IMcpResourceScannerService, McpResourceScannerService } from '../../platform/mcp/common/mcpResourceScannerService.js'; import { McpManagementChannel } from '../../platform/mcp/common/mcpManagementIpc.js'; import { AllowedMcpServersService } from '../../platform/mcp/common/allowedMcpServersService.js'; +import { IMcpGalleryManifestService } from '../../platform/mcp/common/mcpGalleryManifest.js'; +import { McpGalleryManifestIPCService } from '../../platform/mcp/common/mcpGalleryManifestServiceIpc.js'; const eventPrefix = 'monacoworkbench'; @@ -196,6 +198,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken } services.set(IExtensionGalleryManifestService, new ExtensionGalleryManifestIPCService(socketServer, productService)); + services.set(IMcpGalleryManifestService, new McpGalleryManifestIPCService(socketServer)); services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryServiceWithNoStorageService)); const downloadChannel = socketServer.getChannel('download', router); diff --git a/src/vs/workbench/services/mcp/browser/mcpGalleryManifestService.ts b/src/vs/workbench/services/mcp/browser/mcpGalleryManifestService.ts new file mode 100644 index 00000000000..47e8489ca2b --- /dev/null +++ b/src/vs/workbench/services/mcp/browser/mcpGalleryManifestService.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IMcpGalleryManifestService } from '../../../../platform/mcp/common/mcpGalleryManifest.js'; +import { McpGalleryManifestService as McpGalleryManifestService } from '../../../../platform/mcp/common/mcpGalleryManifestService.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js'; + +class WebMcpGalleryManifestService extends McpGalleryManifestService implements IMcpGalleryManifestService { + + constructor( + @IProductService productService: IProductService, + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + ) { + super(productService); + const remoteConnection = remoteAgentService.getConnection(); + if (remoteConnection) { + const channel = remoteConnection.getChannel('mcpGalleryManifest'); + this.getMcpGalleryManifest().then(manifest => { + channel.call('setMcpGalleryManifest', [manifest]); + this._register(this.onDidChangeMcpGalleryManifest(manifest => channel.call('setMcpGalleryManifest', [manifest]))); + }); + } + } + +} + +registerSingleton(IMcpGalleryManifestService, WebMcpGalleryManifestService, InstantiationType.Delayed); diff --git a/src/vs/workbench/services/mcp/electron-browser/mcpGalleryManifestService.ts b/src/vs/workbench/services/mcp/electron-browser/mcpGalleryManifestService.ts new file mode 100644 index 00000000000..05a836f842d --- /dev/null +++ b/src/vs/workbench/services/mcp/electron-browser/mcpGalleryManifestService.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from '../../../../base/common/event.js'; +import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; +import { IMcpGalleryManifestService, IMcpGalleryManifest, McpGalleryManifestStatus } from '../../../../platform/mcp/common/mcpGalleryManifest.js'; +import { McpGalleryManifestService as McpGalleryManifestService } from '../../../../platform/mcp/common/mcpGalleryManifestService.js'; +import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js'; +import { ISharedProcessService } from '../../../../platform/ipc/electron-browser/services.js'; +import { IProductService } from '../../../../platform/product/common/productService.js'; +import { IRemoteAgentService } from '../../remote/common/remoteAgentService.js'; +import { mcpGalleryServiceUrlConfig } from '../../../../platform/mcp/common/mcpManagement.js'; + +export class WorkbenchMcpGalleryManifestService extends McpGalleryManifestService implements IMcpGalleryManifestService { + + private mcpGalleryManifest: IMcpGalleryManifest | null = null; + + private _onDidChangeMcpGalleryManifest = this._register(new Emitter()); + override readonly onDidChangeMcpGalleryManifest = this._onDidChangeMcpGalleryManifest.event; + + private currentStatus: McpGalleryManifestStatus = McpGalleryManifestStatus.Unavailable; + override get mcpGalleryManifestStatus(): McpGalleryManifestStatus { return this.currentStatus; } + private _onDidChangeMcpGalleryManifestStatus = this._register(new Emitter()); + override readonly onDidChangeMcpGalleryManifestStatus = this._onDidChangeMcpGalleryManifestStatus.event; + + constructor( + @IProductService productService: IProductService, + @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @ISharedProcessService sharedProcessService: ISharedProcessService, + @IConfigurationService private readonly configurationService: IConfigurationService, + ) { + super(productService); + + const channels = [sharedProcessService.getChannel('mcpGalleryManifest')]; + const remoteConnection = remoteAgentService.getConnection(); + if (remoteConnection) { + channels.push(remoteConnection.getChannel('mcpGalleryManifest')); + } + this.getMcpGalleryManifest().then(manifest => { + channels.forEach(channel => channel.call('setMcpGalleryManifest', [manifest])); + }); + } + + private mcpGalleryManifestPromise: Promise | undefined; + override async getMcpGalleryManifest(): Promise { + if (!this.mcpGalleryManifestPromise) { + this.mcpGalleryManifestPromise = this.doGetMcpGalleryManifest(); + } + await this.mcpGalleryManifestPromise; + return this.mcpGalleryManifest; + } + + private async doGetMcpGalleryManifest(): Promise { + if (this.productService.quality === 'stable') { + return; + } + const configuredServiceUrl = this.configurationService.getValue(mcpGalleryServiceUrlConfig); + if (configuredServiceUrl) { + this.update(this.createMcpGalleryManifest(configuredServiceUrl)); + } else { + this.update(await super.getMcpGalleryManifest()); + } + } + + private update(manifest: IMcpGalleryManifest | null): void { + this.mcpGalleryManifest = manifest; + this.currentStatus = manifest ? McpGalleryManifestStatus.Available : McpGalleryManifestStatus.Unavailable; + this._onDidChangeMcpGalleryManifest.fire(manifest); + this._onDidChangeMcpGalleryManifestStatus.fire(this.currentStatus); + } + +} + +registerSingleton(IMcpGalleryManifestService, WorkbenchMcpGalleryManifestService, InstantiationType.Eager); diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index f5d13c8f52b..c1d9dc494ce 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -53,6 +53,7 @@ import './services/keybinding/electron-browser/nativeKeyboardLayout.js'; import './services/path/electron-browser/pathService.js'; import './services/themes/electron-browser/nativeHostColorSchemeService.js'; import './services/extensionManagement/electron-browser/extensionManagementService.js'; +import './services/mcp/electron-browser/mcpGalleryManifestService.js'; import './services/mcp/electron-browser/mcpWorkbenchManagementService.js'; import './services/encryption/electron-browser/encryptionService.js'; import './services/imageResize/electron-browser/imageResizeService.js'; diff --git a/src/vs/workbench/workbench.web.main.internal.ts b/src/vs/workbench/workbench.web.main.internal.ts index 2b4feb52c6d..011e1cbe462 100644 --- a/src/vs/workbench/workbench.web.main.internal.ts +++ b/src/vs/workbench/workbench.web.main.internal.ts @@ -43,6 +43,7 @@ import './services/extensionManagement/browser/extensionsProfileScannerService.j import './services/extensions/browser/extensionsScannerService.js'; import './services/extensionManagement/browser/webExtensionsScannerService.js'; import './services/extensionManagement/common/extensionManagementServerService.js'; +import './services/mcp/browser/mcpGalleryManifestService.js'; import './services/mcp/browser/mcpWorkbenchManagementService.js'; import './services/extensionManagement/browser/extensionGalleryManifestService.js'; import './services/telemetry/browser/telemetryService.js';