Path: blob/main/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts
5272 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 './media/mcpServersView.css';6import * as dom from '../../../../base/browser/dom.js';7import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';8import { IListContextMenuEvent } from '../../../../base/browser/ui/list/list.js';9import { Emitter, Event } from '../../../../base/common/event.js';10import { createMarkdownCommandLink, MarkdownString } from '../../../../base/common/htmlContent.js';11import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, isDisposable } from '../../../../base/common/lifecycle.js';12import { DelayedPagedModel, IPagedModel, PagedModel, IterativePagedModel } from '../../../../base/common/paging.js';13import { localize, localize2 } from '../../../../nls.js';14import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';15import { ContextKeyDefinedExpr, ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';16import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';17import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';18import { IHoverService } from '../../../../platform/hover/browser/hover.js';19import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';20import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';21import { WorkbenchPagedList } from '../../../../platform/list/browser/listService.js';22import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';23import { IOpenerService } from '../../../../platform/opener/common/opener.js';24import { IThemeService } from '../../../../platform/theme/common/themeService.js';25import { getLocationBasedViewColors } from '../../../browser/parts/views/viewPane.js';26import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';27import { IViewDescriptorService, IViewsRegistry, ViewContainerLocation, Extensions as ViewExtensions } from '../../../common/views.js';28import { HasInstalledMcpServersContext, IMcpWorkbenchService, InstalledMcpServersViewId, IWorkbenchMcpServer, McpServerContainers, McpServerEnablementState, McpServersGalleryStatusContext } from '../common/mcpTypes.js';29import { DropDownAction, getContextMenuActions, InstallAction, InstallingLabelAction, ManageMcpServerAction, McpServerStatusAction } from './mcpServerActions.js';30import { PublisherWidget, StarredWidget, McpServerIconWidget, McpServerHoverWidget, McpServerScopeBadgeWidget } from './mcpServerWidgets.js';31import { ActionRunner, IAction, Separator } from '../../../../base/common/actions.js';32import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';33import { mcpGalleryServiceEnablementConfig, mcpGalleryServiceUrlConfig } from '../../../../platform/mcp/common/mcpManagement.js';34import { ThemeIcon } from '../../../../base/common/themables.js';35import { alert } from '../../../../base/browser/ui/aria/aria.js';36import { Registry } from '../../../../platform/registry/common/platform.js';37import { IWorkbenchContribution } from '../../../common/contributions.js';38import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';39import { DefaultViewsContext, SearchMcpServersContext } from '../../extensions/common/extensions.js';40import { VIEW_CONTAINER } from '../../extensions/browser/extensions.contribution.js';41import { ChatContextKeys } from '../../chat/common/actions/chatContextKeys.js';42import { Button } from '../../../../base/browser/ui/button/button.js';43import { defaultButtonStyles } from '../../../../platform/theme/browser/defaultStyles.js';44import { AbstractExtensionsListView } from '../../extensions/browser/extensionsViews.js';45import { ExtensionListRendererOptions } from '../../extensions/browser/extensionsList.js';46import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';47import { IWorkbenchLayoutService, Position } from '../../../services/layout/browser/layoutService.js';48import { mcpServerIcon } from './mcpServerIcons.js';49import { IPagedRenderer } from '../../../../base/browser/ui/list/listPaging.js';50import { IMcpGalleryManifestService, McpGalleryManifestStatus } from '../../../../platform/mcp/common/mcpGalleryManifest.js';51import { ProductQualityContext } from '../../../../platform/contextkey/common/contextkeys.js';52import { SeverityIcon } from '../../../../base/browser/ui/severityIcon/severityIcon.js';53import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js';5455export interface McpServerListViewOptions {56showWelcome?: boolean;57}5859interface IQueryResult {60model: IPagedModel<IWorkbenchMcpServer>;61disposables: DisposableStore;62showWelcomeContent?: boolean;63readonly onDidChangeModel?: Event<IPagedModel<IWorkbenchMcpServer>>;64}6566type Message = {67readonly text: string;68readonly severity: Severity;69};7071export class McpServersListView extends AbstractExtensionsListView<IWorkbenchMcpServer> {7273private list: WorkbenchPagedList<IWorkbenchMcpServer> | null = null;74private listContainer: HTMLElement | null = null;75private welcomeContainer: HTMLElement | null = null;76private bodyTemplate: {77messageContainer: HTMLElement;78messageSeverityIcon: HTMLElement;79messageBox: HTMLElement;80mcpServersList: HTMLElement;81} | undefined;82private readonly contextMenuActionRunner = this._register(new ActionRunner());83private input: IQueryResult | undefined;8485constructor(86private readonly mpcViewOptions: McpServerListViewOptions,87options: IViewletViewOptions,88@IKeybindingService keybindingService: IKeybindingService,89@IContextMenuService contextMenuService: IContextMenuService,90@IInstantiationService instantiationService: IInstantiationService,91@IThemeService themeService: IThemeService,92@IHoverService hoverService: IHoverService,93@IConfigurationService configurationService: IConfigurationService,94@IContextKeyService contextKeyService: IContextKeyService,95@IViewDescriptorService viewDescriptorService: IViewDescriptorService,96@IOpenerService openerService: IOpenerService,97@IDialogService private readonly dialogService: IDialogService,98@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService,99@IMcpGalleryManifestService protected readonly mcpGalleryManifestService: IMcpGalleryManifestService,100@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,101@IMarkdownRendererService protected readonly markdownRendererService: IMarkdownRendererService,102) {103super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);104}105106protected override renderBody(container: HTMLElement): void {107super.renderBody(container);108109// Create welcome container110this.welcomeContainer = dom.append(container, dom.$('.mcp-welcome-container.hide'));111this.createWelcomeContent(this.welcomeContainer);112113const messageContainer = dom.append(container, dom.$('.message-container'));114const messageSeverityIcon = dom.append(messageContainer, dom.$(''));115const messageBox = dom.append(messageContainer, dom.$('.message'));116const mcpServersList = dom.$('.mcp-servers-list');117118this.bodyTemplate = {119mcpServersList,120messageBox,121messageContainer,122messageSeverityIcon123};124125this.listContainer = dom.append(container, mcpServersList);126this.list = this._register(this.instantiationService.createInstance(WorkbenchPagedList,127`${this.id}-MCP-Servers`,128this.listContainer,129{130getHeight() { return 72; },131getTemplateId: () => McpServerRenderer.templateId,132},133[this.instantiationService.createInstance(McpServerRenderer, {134hoverOptions: {135position: () => {136const viewLocation = this.viewDescriptorService.getViewLocationById(this.id);137if (viewLocation === ViewContainerLocation.Sidebar) {138return this.layoutService.getSideBarPosition() === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT;139}140if (viewLocation === ViewContainerLocation.AuxiliaryBar) {141return this.layoutService.getSideBarPosition() === Position.LEFT ? HoverPosition.LEFT : HoverPosition.RIGHT;142}143return HoverPosition.RIGHT;144}145}146})],147{148multipleSelectionSupport: false,149setRowLineHeight: false,150horizontalScrolling: false,151accessibilityProvider: {152getAriaLabel(mcpServer: IWorkbenchMcpServer | null): string {153return mcpServer?.label ?? '';154},155getWidgetAriaLabel(): string {156return localize('mcp servers', "MCP Servers");157}158},159overrideStyles: getLocationBasedViewColors(this.viewDescriptorService.getViewLocationById(this.id)).listOverrideStyles,160openOnSingleClick: true,161}) as WorkbenchPagedList<IWorkbenchMcpServer>);162this._register(Event.debounce(Event.filter(this.list.onDidOpen, e => e.element !== null), (_, event) => event, 75, true)(options => {163this.mcpWorkbenchService.open(options.element!, options.editorOptions);164}));165this._register(this.list.onContextMenu(e => this.onContextMenu(e), this));166167if (this.input) {168this.renderInput();169}170}171172private async onContextMenu(e: IListContextMenuEvent<IWorkbenchMcpServer>): Promise<void> {173if (e.element) {174const disposables = new DisposableStore();175const mcpServer = e.element ? this.mcpWorkbenchService.local.find(local => local.id === e.element!.id) || e.element176: e.element;177const groups: IAction[][] = getContextMenuActions(mcpServer, false, this.instantiationService);178const actions: IAction[] = [];179for (const menuActions of groups) {180for (const menuAction of menuActions) {181actions.push(menuAction);182if (isDisposable(menuAction)) {183disposables.add(menuAction);184}185}186actions.push(new Separator());187}188actions.pop();189this.contextMenuService.showContextMenu({190getAnchor: () => e.anchor,191getActions: () => actions,192actionRunner: this.contextMenuActionRunner,193onHide: () => disposables.dispose()194});195}196}197198protected override layoutBody(height: number, width: number): void {199super.layoutBody(height, width);200this.list?.layout(height, width);201}202203async show(query: string): Promise<IPagedModel<IWorkbenchMcpServer>> {204if (this.input) {205this.input.disposables.dispose();206this.input = undefined;207}208209if (this.mpcViewOptions.showWelcome) {210this.input = { model: new PagedModel([]), disposables: new DisposableStore(), showWelcomeContent: true };211} else {212this.input = await this.query(query.trim());213}214215this.renderInput();216217if (this.input.onDidChangeModel) {218this.input.disposables.add(this.input.onDidChangeModel(model => {219if (!this.input) {220return;221}222this.input.model = model;223this.renderInput();224}));225}226227return this.input.model;228}229230private renderInput() {231if (!this.input) {232return;233}234if (this.list) {235this.list.model = new DelayedPagedModel(this.input.model);236}237this.showWelcomeContent(!!this.input.showWelcomeContent);238if (!this.input.showWelcomeContent) {239this.updateBody();240}241}242243private showWelcomeContent(show: boolean): void {244this.welcomeContainer?.classList.toggle('hide', !show);245this.listContainer?.classList.toggle('hide', show);246}247248private createWelcomeContent(welcomeContainer: HTMLElement): void {249const welcomeContent = dom.append(welcomeContainer, dom.$('.mcp-welcome-content'));250251const iconContainer = dom.append(welcomeContent, dom.$('.mcp-welcome-icon'));252const iconElement = dom.append(iconContainer, dom.$('span'));253iconElement.className = ThemeIcon.asClassName(mcpServerIcon);254255const title = dom.append(welcomeContent, dom.$('.mcp-welcome-title'));256title.textContent = localize('mcp.welcome.title', "MCP Servers");257258const settingsCommandLink = createMarkdownCommandLink({ id: 'workbench.action.openSettings', arguments: [`@id:${mcpGalleryServiceEnablementConfig}`], title: mcpGalleryServiceEnablementConfig, tooltip: localize('mcp.welcome.settings.tooltip', "Open Settings") }).toString();259const description = dom.append(welcomeContent, dom.$('.mcp-welcome-description'));260const markdownResult = this._register(this.markdownRendererService.render(261new MarkdownString(262localize('mcp.welcome.descriptionWithLink', "Browse and install [Model Context Protocol (MCP) servers](https://code.visualstudio.com/docs/copilot/customization/mcp-servers) directly from VS Code to extend agent mode with extra tools for connecting to databases, invoking APIs and performing specialized tasks."),263{ isTrusted: { enabledCommands: ['workbench.action.openSettings'] } },264)265.appendMarkdown('\n\n')266.appendMarkdown(localize('mcp.gallery.enableDialog.setting', "This feature is currently in preview. You can disable it anytime using the setting {0}.", settingsCommandLink)),267));268description.appendChild(markdownResult.element);269270const buttonContainer = dom.append(welcomeContent, dom.$('.mcp-welcome-button-container'));271const button = this._register(new Button(buttonContainer, {272title: localize('mcp.welcome.enableGalleryButton', "Enable MCP Servers Marketplace"),273...defaultButtonStyles274}));275button.label = localize('mcp.welcome.enableGalleryButton', "Enable MCP Servers Marketplace");276277this._register(button.onDidClick(async () => {278279const { result } = await this.dialogService.prompt({280type: 'info',281message: localize('mcp.gallery.enableDialog.title', "Enable MCP Servers Marketplace?"),282custom: {283markdownDetails: [{284markdown: new MarkdownString(localize('mcp.gallery.enableDialog.setting', "This feature is currently in preview. You can disable it anytime using the setting {0}.", settingsCommandLink), { isTrusted: true })285}]286},287buttons: [288{ label: localize('mcp.gallery.enableDialog.enable', "Enable"), run: () => true },289{ label: localize('mcp.gallery.enableDialog.cancel', "Cancel"), run: () => false }290]291});292293if (result) {294await this.configurationService.updateValue(mcpGalleryServiceEnablementConfig, true);295}296}));297}298299private updateBody(message?: Message): void {300if (this.bodyTemplate) {301302const count = this.input?.model.length ?? 0;303this.bodyTemplate.mcpServersList.classList.toggle('hidden', count === 0);304this.bodyTemplate.messageContainer.classList.toggle('hidden', !message && count > 0);305306if (this.isBodyVisible()) {307if (message) {308this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(message.severity);309this.bodyTemplate.messageBox.textContent = message.text;310} else if (count === 0) {311this.bodyTemplate.messageSeverityIcon.className = '';312this.bodyTemplate.messageBox.textContent = localize('no extensions found', "No MCP Servers found.");313}314if (this.bodyTemplate.messageBox.textContent) {315alert(this.bodyTemplate.messageBox.textContent);316}317}318}319}320321private async query(query: string): Promise<IQueryResult> {322const disposables = new DisposableStore();323if (query) {324const servers = await this.mcpWorkbenchService.queryGallery({ text: query.replace('@mcp', '') });325const model = disposables.add(new IterativePagedModel(servers));326return { model, disposables };327}328329const onDidChangeModel = disposables.add(new Emitter<IPagedModel<IWorkbenchMcpServer>>());330let servers = await this.mcpWorkbenchService.queryLocal();331disposables.add(Event.debounce(this.mcpWorkbenchService.onChange, () => undefined)(() => {332const mergedMcpServers = this.mergeChangedMcpServers(servers, [...this.mcpWorkbenchService.local]);333if (mergedMcpServers) {334servers = mergedMcpServers;335onDidChangeModel.fire(new PagedModel(servers));336}337}));338disposables.add(this.mcpWorkbenchService.onReset(() => onDidChangeModel.fire(new PagedModel([...this.mcpWorkbenchService.local]))));339return { model: new PagedModel(servers), onDidChangeModel: onDidChangeModel.event, disposables };340}341342private mergeChangedMcpServers(mcpServers: IWorkbenchMcpServer[], newMcpServers: IWorkbenchMcpServer[]): IWorkbenchMcpServer[] | undefined {343const oldMcpServers = [...mcpServers];344const findPreviousMcpServerIndex = (from: number): number => {345let index = -1;346const previousMcpServerInNew = newMcpServers[from];347if (previousMcpServerInNew) {348index = oldMcpServers.findIndex(e => e.id === previousMcpServerInNew.id);349if (index === -1) {350return findPreviousMcpServerIndex(from - 1);351}352}353return index;354};355356let hasChanged: boolean = false;357for (let index = 0; index < newMcpServers.length; index++) {358const newMcpServer = newMcpServers[index];359if (mcpServers.every(r => r.id !== newMcpServer.id)) {360hasChanged = true;361mcpServers.splice(findPreviousMcpServerIndex(index - 1) + 1, 0, newMcpServer);362}363}364365for (let index = mcpServers.length - 1; index >= 0; index--) {366const oldMcpServer = mcpServers[index];367if (newMcpServers.every(r => r.id !== oldMcpServer.id) && newMcpServers.some(r => r.name === oldMcpServer.name)) {368hasChanged = true;369mcpServers.splice(index, 1);370}371}372373if (!hasChanged) {374if (mcpServers.length === newMcpServers.length) {375for (let index = 0; index < newMcpServers.length; index++) {376if (mcpServers[index]?.id !== newMcpServers[index]?.id) {377hasChanged = true;378mcpServers = newMcpServers;379break;380}381}382}383}384385return hasChanged ? mcpServers : undefined;386}387}388389interface IMcpServerTemplateData {390root: HTMLElement;391element: HTMLElement;392name: HTMLElement;393description: HTMLElement;394starred: HTMLElement;395mcpServer: IWorkbenchMcpServer | null;396disposables: IDisposable[];397mcpServerDisposables: IDisposable[];398actionbar: ActionBar;399}400401class McpServerRenderer implements IPagedRenderer<IWorkbenchMcpServer, IMcpServerTemplateData> {402403static readonly templateId = 'mcpServer';404readonly templateId = McpServerRenderer.templateId;405406constructor(407private readonly options: ExtensionListRendererOptions,408@IInstantiationService private readonly instantiationService: IInstantiationService,409@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService,410@INotificationService private readonly notificationService: INotificationService,411) { }412413renderTemplate(root: HTMLElement): IMcpServerTemplateData {414const element = dom.append(root, dom.$('.mcp-server-item.extension-list-item'));415const iconContainer = dom.append(element, dom.$('.icon-container'));416const iconWidget = this.instantiationService.createInstance(McpServerIconWidget, iconContainer);417const details = dom.append(element, dom.$('.details'));418const headerContainer = dom.append(details, dom.$('.header-container'));419const header = dom.append(headerContainer, dom.$('.header'));420const name = dom.append(header, dom.$('span.name'));421const starred = dom.append(header, dom.$('span.ratings'));422const description = dom.append(details, dom.$('.description.ellipsis'));423const footer = dom.append(details, dom.$('.footer'));424const publisherWidget = this.instantiationService.createInstance(PublisherWidget, dom.append(footer, dom.$('.publisher-container')), true);425const actionbar = new ActionBar(footer, {426actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {427if (action instanceof DropDownAction) {428return action.createActionViewItem(options);429}430return undefined;431},432focusOnlyEnabledItems: true433});434435actionbar.setFocusable(false);436const actionBarListener = actionbar.onDidRun(({ error }) => error && this.notificationService.error(error));437const mcpServerStatusAction = this.instantiationService.createInstance(McpServerStatusAction);438439const actions = [440this.instantiationService.createInstance(InstallAction, true),441this.instantiationService.createInstance(InstallingLabelAction),442this.instantiationService.createInstance(ManageMcpServerAction, false),443mcpServerStatusAction444];445446const widgets = [447iconWidget,448publisherWidget,449this.instantiationService.createInstance(StarredWidget, starred, true),450this.instantiationService.createInstance(McpServerScopeBadgeWidget, iconContainer),451this.instantiationService.createInstance(McpServerHoverWidget, { target: root, position: this.options.hoverOptions.position }, mcpServerStatusAction)452];453const extensionContainers: McpServerContainers = this.instantiationService.createInstance(McpServerContainers, [...actions, ...widgets]);454455actionbar.push(actions, { icon: true, label: true });456const disposable = combinedDisposable(...actions, ...widgets, actionbar, actionBarListener, extensionContainers);457458return {459root, element, name, description, starred, disposables: [disposable], actionbar,460mcpServerDisposables: [],461set mcpServer(mcpServer: IWorkbenchMcpServer) {462extensionContainers.mcpServer = mcpServer;463}464};465}466467renderPlaceholder(index: number, data: IMcpServerTemplateData): void {468data.element.classList.add('loading');469470data.mcpServerDisposables = dispose(data.mcpServerDisposables);471data.name.textContent = '';472data.description.textContent = '';473data.starred.style.display = 'none';474data.mcpServer = null;475}476477renderElement(mcpServer: IWorkbenchMcpServer, index: number, data: IMcpServerTemplateData): void {478data.element.classList.remove('loading');479data.mcpServerDisposables = dispose(data.mcpServerDisposables);480data.root.setAttribute('data-mcp-server-id', mcpServer.id);481data.name.textContent = mcpServer.label;482data.description.textContent = mcpServer.description;483484data.starred.style.display = '';485data.mcpServer = mcpServer;486487const updateEnablement = () => data.root.classList.toggle('disabled', !!mcpServer.runtimeStatus?.state && mcpServer.runtimeStatus.state !== McpServerEnablementState.Enabled);488updateEnablement();489data.mcpServerDisposables.push(this.mcpWorkbenchService.onChange(e => {490if (!e || e.id === mcpServer.id) {491updateEnablement();492}493}));494}495496disposeElement(mcpServer: IWorkbenchMcpServer, index: number, data: IMcpServerTemplateData): void {497data.mcpServerDisposables = dispose(data.mcpServerDisposables);498}499500disposeTemplate(data: IMcpServerTemplateData): void {501data.mcpServerDisposables = dispose(data.mcpServerDisposables);502data.disposables = dispose(data.disposables);503}504}505506507export class DefaultBrowseMcpServersView extends McpServersListView {508509protected override renderBody(container: HTMLElement): void {510super.renderBody(container);511this._register(this.mcpGalleryManifestService.onDidChangeMcpGalleryManifest(() => this.show()));512}513514override async show(): Promise<IPagedModel<IWorkbenchMcpServer>> {515return super.show('@mcp');516}517}518519export class McpServersViewsContribution extends Disposable implements IWorkbenchContribution {520521static ID = 'workbench.mcp.servers.views.contribution';522523constructor() {524super();525526Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([527{528id: InstalledMcpServersViewId,529name: localize2('mcp-installed', "MCP Servers - Installed"),530ctorDescriptor: new SyncDescriptor(McpServersListView, [{}]),531when: ContextKeyExpr.and(DefaultViewsContext, HasInstalledMcpServersContext, ChatContextKeys.Setup.hidden.negate()),532weight: 40,533order: 4,534canToggleVisibility: true535},536{537id: 'workbench.views.mcp.default.marketplace',538name: localize2('mcp', "MCP Servers"),539ctorDescriptor: new SyncDescriptor(DefaultBrowseMcpServersView, [{}]),540when: ContextKeyExpr.and(DefaultViewsContext, HasInstalledMcpServersContext.toNegated(), ChatContextKeys.Setup.hidden.negate(), McpServersGalleryStatusContext.isEqualTo(McpGalleryManifestStatus.Available), ContextKeyExpr.or(ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceUrlConfig}`), ProductQualityContext.notEqualsTo('stable'), ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceEnablementConfig}`))),541weight: 40,542order: 4,543canToggleVisibility: true544},545{546id: 'workbench.views.mcp.marketplace',547name: localize2('mcp', "MCP Servers"),548ctorDescriptor: new SyncDescriptor(McpServersListView, [{}]),549when: ContextKeyExpr.and(SearchMcpServersContext, ChatContextKeys.Setup.hidden.negate(), McpServersGalleryStatusContext.isEqualTo(McpGalleryManifestStatus.Available), ContextKeyExpr.or(ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceUrlConfig}`), ProductQualityContext.notEqualsTo('stable'), ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceEnablementConfig}`))),550},551{552id: 'workbench.views.mcp.default.welcomeView',553name: localize2('mcp', "MCP Servers"),554ctorDescriptor: new SyncDescriptor(DefaultBrowseMcpServersView, [{ showWelcome: true }]),555when: ContextKeyExpr.and(DefaultViewsContext, HasInstalledMcpServersContext.toNegated(), ChatContextKeys.Setup.hidden.negate(), McpServersGalleryStatusContext.isEqualTo(McpGalleryManifestStatus.Available), ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceUrlConfig}`).negate(), ProductQualityContext.isEqualTo('stable'), ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceEnablementConfig}`).negate()),556weight: 40,557order: 4,558canToggleVisibility: true559},560{561id: 'workbench.views.mcp.welcomeView',562name: localize2('mcp', "MCP Servers"),563ctorDescriptor: new SyncDescriptor(McpServersListView, [{ showWelcome: true }]),564when: ContextKeyExpr.and(SearchMcpServersContext, ChatContextKeys.Setup.hidden.negate(), McpServersGalleryStatusContext.isEqualTo(McpGalleryManifestStatus.Available), ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceUrlConfig}`).negate(), ProductQualityContext.isEqualTo('stable'), ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceEnablementConfig}`).negate()),565}566], VIEW_CONTAINER);567}568}569570571