Path: blob/main/src/vs/platform/mcp/common/mcpGalleryManifestService.ts
5284 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 { CancellationToken } from '../../../base/common/cancellation.js';6import { Event } from '../../../base/common/event.js';7import { Disposable } from '../../../base/common/lifecycle.js';8import { ILogService } from '../../log/common/log.js';9import { IProductService } from '../../product/common/productService.js';10import { IRequestService, isSuccess } from '../../request/common/request.js';11import { McpGalleryResourceType, IMcpGalleryManifest, IMcpGalleryManifestService, McpGalleryManifestStatus } from './mcpGalleryManifest.js';1213const SUPPORTED_VERSIONS = [14'v0.1',15'v0',16];1718export class McpGalleryManifestService extends Disposable implements IMcpGalleryManifestService {1920readonly _serviceBrand: undefined;21readonly onDidChangeMcpGalleryManifest = Event.None;22readonly onDidChangeMcpGalleryManifestStatus = Event.None;2324private readonly versionByUrl = new Map<string, Promise<string>>();2526get mcpGalleryManifestStatus(): McpGalleryManifestStatus {27return !!this.productService.mcpGallery?.serviceUrl ? McpGalleryManifestStatus.Available : McpGalleryManifestStatus.Unavailable;28}2930constructor(31@IProductService private readonly productService: IProductService,32@IRequestService private readonly requestService: IRequestService,33@ILogService protected readonly logService: ILogService,34) {35super();36}3738async getMcpGalleryManifest(): Promise<IMcpGalleryManifest | null> {39if (!this.productService.mcpGallery) {40return null;41}42return this.createMcpGalleryManifest(this.productService.mcpGallery.serviceUrl, SUPPORTED_VERSIONS[0]);43}4445protected async createMcpGalleryManifest(url: string, version?: string): Promise<IMcpGalleryManifest> {46url = url.endsWith('/') ? url.slice(0, -1) : url;4748if (!version) {49let versionPromise = this.versionByUrl.get(url);50if (!versionPromise) {51this.versionByUrl.set(url, versionPromise = this.getVersion(url));52}53version = await versionPromise;54}5556const isProductGalleryUrl = this.productService.mcpGallery?.serviceUrl === url;57const serversUrl = `${url}/${version}/servers`;58const resources = [59{60id: serversUrl,61type: McpGalleryResourceType.McpServersQueryService62},63{64id: `${serversUrl}/{name}/versions/{version}`,65type: McpGalleryResourceType.McpServerVersionUri66},67{68id: `${serversUrl}/{name}/versions/latest`,69type: McpGalleryResourceType.McpServerLatestVersionUri70}71];7273if (isProductGalleryUrl) {74resources.push({75id: `${serversUrl}/by-name/{name}`,76type: McpGalleryResourceType.McpServerNamedResourceUri77});78resources.push({79id: this.productService.mcpGallery.itemWebUrl,80type: McpGalleryResourceType.McpServerWebUri81});82resources.push({83id: this.productService.mcpGallery.publisherUrl,84type: McpGalleryResourceType.PublisherUriTemplate85});86resources.push({87id: this.productService.mcpGallery.supportUrl,88type: McpGalleryResourceType.ContactSupportUri89});90resources.push({91id: this.productService.mcpGallery.supportUrl,92type: McpGalleryResourceType.ContactSupportUri93});94resources.push({95id: this.productService.mcpGallery.privacyPolicyUrl,96type: McpGalleryResourceType.PrivacyPolicyUri97});98resources.push({99id: this.productService.mcpGallery.termsOfServiceUrl,100type: McpGalleryResourceType.TermsOfServiceUri101});102resources.push({103id: this.productService.mcpGallery.reportUrl,104type: McpGalleryResourceType.ReportUri105});106}107108if (version === 'v0') {109resources.push({110id: `${serversUrl}/{id}`,111type: McpGalleryResourceType.McpServerIdUri112});113}114115return {116version,117url,118resources119};120}121122private async getVersion(url: string): Promise<string> {123for (const version of SUPPORTED_VERSIONS) {124if (await this.checkVersion(url, version)) {125return version;126}127}128return SUPPORTED_VERSIONS[0];129}130131private async checkVersion(url: string, version: string): Promise<boolean> {132try {133const context = await this.requestService.request({134type: 'GET',135url: `${url}/${version}/servers?limit=1`,136}, CancellationToken.None);137if (isSuccess(context)) {138return true;139}140this.logService.info(`The service at ${url} does not support version ${version}. Service returned status ${context.res.statusCode}.`);141} catch (error) {142this.logService.error(error);143}144return false;145}146}147148149