Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/mcp/browser/mcpServersView.ts
5272 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 './media/mcpServersView.css';
7
import * as dom from '../../../../base/browser/dom.js';
8
import { ActionBar } from '../../../../base/browser/ui/actionbar/actionbar.js';
9
import { IListContextMenuEvent } from '../../../../base/browser/ui/list/list.js';
10
import { Emitter, Event } from '../../../../base/common/event.js';
11
import { createMarkdownCommandLink, MarkdownString } from '../../../../base/common/htmlContent.js';
12
import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, isDisposable } from '../../../../base/common/lifecycle.js';
13
import { DelayedPagedModel, IPagedModel, PagedModel, IterativePagedModel } from '../../../../base/common/paging.js';
14
import { localize, localize2 } from '../../../../nls.js';
15
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
16
import { ContextKeyDefinedExpr, ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
17
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
18
import { IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
19
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
20
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
21
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
22
import { WorkbenchPagedList } from '../../../../platform/list/browser/listService.js';
23
import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js';
24
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
25
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
26
import { getLocationBasedViewColors } from '../../../browser/parts/views/viewPane.js';
27
import { IViewletViewOptions } from '../../../browser/parts/views/viewsViewlet.js';
28
import { IViewDescriptorService, IViewsRegistry, ViewContainerLocation, Extensions as ViewExtensions } from '../../../common/views.js';
29
import { HasInstalledMcpServersContext, IMcpWorkbenchService, InstalledMcpServersViewId, IWorkbenchMcpServer, McpServerContainers, McpServerEnablementState, McpServersGalleryStatusContext } from '../common/mcpTypes.js';
30
import { DropDownAction, getContextMenuActions, InstallAction, InstallingLabelAction, ManageMcpServerAction, McpServerStatusAction } from './mcpServerActions.js';
31
import { PublisherWidget, StarredWidget, McpServerIconWidget, McpServerHoverWidget, McpServerScopeBadgeWidget } from './mcpServerWidgets.js';
32
import { ActionRunner, IAction, Separator } from '../../../../base/common/actions.js';
33
import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
34
import { mcpGalleryServiceEnablementConfig, mcpGalleryServiceUrlConfig } from '../../../../platform/mcp/common/mcpManagement.js';
35
import { ThemeIcon } from '../../../../base/common/themables.js';
36
import { alert } from '../../../../base/browser/ui/aria/aria.js';
37
import { Registry } from '../../../../platform/registry/common/platform.js';
38
import { IWorkbenchContribution } from '../../../common/contributions.js';
39
import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';
40
import { DefaultViewsContext, SearchMcpServersContext } from '../../extensions/common/extensions.js';
41
import { VIEW_CONTAINER } from '../../extensions/browser/extensions.contribution.js';
42
import { ChatContextKeys } from '../../chat/common/actions/chatContextKeys.js';
43
import { Button } from '../../../../base/browser/ui/button/button.js';
44
import { defaultButtonStyles } from '../../../../platform/theme/browser/defaultStyles.js';
45
import { AbstractExtensionsListView } from '../../extensions/browser/extensionsViews.js';
46
import { ExtensionListRendererOptions } from '../../extensions/browser/extensionsList.js';
47
import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';
48
import { IWorkbenchLayoutService, Position } from '../../../services/layout/browser/layoutService.js';
49
import { mcpServerIcon } from './mcpServerIcons.js';
50
import { IPagedRenderer } from '../../../../base/browser/ui/list/listPaging.js';
51
import { IMcpGalleryManifestService, McpGalleryManifestStatus } from '../../../../platform/mcp/common/mcpGalleryManifest.js';
52
import { ProductQualityContext } from '../../../../platform/contextkey/common/contextkeys.js';
53
import { SeverityIcon } from '../../../../base/browser/ui/severityIcon/severityIcon.js';
54
import { IMarkdownRendererService } from '../../../../platform/markdown/browser/markdownRenderer.js';
55
56
export interface McpServerListViewOptions {
57
showWelcome?: boolean;
58
}
59
60
interface IQueryResult {
61
model: IPagedModel<IWorkbenchMcpServer>;
62
disposables: DisposableStore;
63
showWelcomeContent?: boolean;
64
readonly onDidChangeModel?: Event<IPagedModel<IWorkbenchMcpServer>>;
65
}
66
67
type Message = {
68
readonly text: string;
69
readonly severity: Severity;
70
};
71
72
export class McpServersListView extends AbstractExtensionsListView<IWorkbenchMcpServer> {
73
74
private list: WorkbenchPagedList<IWorkbenchMcpServer> | null = null;
75
private listContainer: HTMLElement | null = null;
76
private welcomeContainer: HTMLElement | null = null;
77
private bodyTemplate: {
78
messageContainer: HTMLElement;
79
messageSeverityIcon: HTMLElement;
80
messageBox: HTMLElement;
81
mcpServersList: HTMLElement;
82
} | undefined;
83
private readonly contextMenuActionRunner = this._register(new ActionRunner());
84
private input: IQueryResult | undefined;
85
86
constructor(
87
private readonly mpcViewOptions: McpServerListViewOptions,
88
options: IViewletViewOptions,
89
@IKeybindingService keybindingService: IKeybindingService,
90
@IContextMenuService contextMenuService: IContextMenuService,
91
@IInstantiationService instantiationService: IInstantiationService,
92
@IThemeService themeService: IThemeService,
93
@IHoverService hoverService: IHoverService,
94
@IConfigurationService configurationService: IConfigurationService,
95
@IContextKeyService contextKeyService: IContextKeyService,
96
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
97
@IOpenerService openerService: IOpenerService,
98
@IDialogService private readonly dialogService: IDialogService,
99
@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService,
100
@IMcpGalleryManifestService protected readonly mcpGalleryManifestService: IMcpGalleryManifestService,
101
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
102
@IMarkdownRendererService protected readonly markdownRendererService: IMarkdownRendererService,
103
) {
104
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
105
}
106
107
protected override renderBody(container: HTMLElement): void {
108
super.renderBody(container);
109
110
// Create welcome container
111
this.welcomeContainer = dom.append(container, dom.$('.mcp-welcome-container.hide'));
112
this.createWelcomeContent(this.welcomeContainer);
113
114
const messageContainer = dom.append(container, dom.$('.message-container'));
115
const messageSeverityIcon = dom.append(messageContainer, dom.$(''));
116
const messageBox = dom.append(messageContainer, dom.$('.message'));
117
const mcpServersList = dom.$('.mcp-servers-list');
118
119
this.bodyTemplate = {
120
mcpServersList,
121
messageBox,
122
messageContainer,
123
messageSeverityIcon
124
};
125
126
this.listContainer = dom.append(container, mcpServersList);
127
this.list = this._register(this.instantiationService.createInstance(WorkbenchPagedList,
128
`${this.id}-MCP-Servers`,
129
this.listContainer,
130
{
131
getHeight() { return 72; },
132
getTemplateId: () => McpServerRenderer.templateId,
133
},
134
[this.instantiationService.createInstance(McpServerRenderer, {
135
hoverOptions: {
136
position: () => {
137
const viewLocation = this.viewDescriptorService.getViewLocationById(this.id);
138
if (viewLocation === ViewContainerLocation.Sidebar) {
139
return this.layoutService.getSideBarPosition() === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT;
140
}
141
if (viewLocation === ViewContainerLocation.AuxiliaryBar) {
142
return this.layoutService.getSideBarPosition() === Position.LEFT ? HoverPosition.LEFT : HoverPosition.RIGHT;
143
}
144
return HoverPosition.RIGHT;
145
}
146
}
147
})],
148
{
149
multipleSelectionSupport: false,
150
setRowLineHeight: false,
151
horizontalScrolling: false,
152
accessibilityProvider: {
153
getAriaLabel(mcpServer: IWorkbenchMcpServer | null): string {
154
return mcpServer?.label ?? '';
155
},
156
getWidgetAriaLabel(): string {
157
return localize('mcp servers', "MCP Servers");
158
}
159
},
160
overrideStyles: getLocationBasedViewColors(this.viewDescriptorService.getViewLocationById(this.id)).listOverrideStyles,
161
openOnSingleClick: true,
162
}) as WorkbenchPagedList<IWorkbenchMcpServer>);
163
this._register(Event.debounce(Event.filter(this.list.onDidOpen, e => e.element !== null), (_, event) => event, 75, true)(options => {
164
this.mcpWorkbenchService.open(options.element!, options.editorOptions);
165
}));
166
this._register(this.list.onContextMenu(e => this.onContextMenu(e), this));
167
168
if (this.input) {
169
this.renderInput();
170
}
171
}
172
173
private async onContextMenu(e: IListContextMenuEvent<IWorkbenchMcpServer>): Promise<void> {
174
if (e.element) {
175
const disposables = new DisposableStore();
176
const mcpServer = e.element ? this.mcpWorkbenchService.local.find(local => local.id === e.element!.id) || e.element
177
: e.element;
178
const groups: IAction[][] = getContextMenuActions(mcpServer, false, this.instantiationService);
179
const actions: IAction[] = [];
180
for (const menuActions of groups) {
181
for (const menuAction of menuActions) {
182
actions.push(menuAction);
183
if (isDisposable(menuAction)) {
184
disposables.add(menuAction);
185
}
186
}
187
actions.push(new Separator());
188
}
189
actions.pop();
190
this.contextMenuService.showContextMenu({
191
getAnchor: () => e.anchor,
192
getActions: () => actions,
193
actionRunner: this.contextMenuActionRunner,
194
onHide: () => disposables.dispose()
195
});
196
}
197
}
198
199
protected override layoutBody(height: number, width: number): void {
200
super.layoutBody(height, width);
201
this.list?.layout(height, width);
202
}
203
204
async show(query: string): Promise<IPagedModel<IWorkbenchMcpServer>> {
205
if (this.input) {
206
this.input.disposables.dispose();
207
this.input = undefined;
208
}
209
210
if (this.mpcViewOptions.showWelcome) {
211
this.input = { model: new PagedModel([]), disposables: new DisposableStore(), showWelcomeContent: true };
212
} else {
213
this.input = await this.query(query.trim());
214
}
215
216
this.renderInput();
217
218
if (this.input.onDidChangeModel) {
219
this.input.disposables.add(this.input.onDidChangeModel(model => {
220
if (!this.input) {
221
return;
222
}
223
this.input.model = model;
224
this.renderInput();
225
}));
226
}
227
228
return this.input.model;
229
}
230
231
private renderInput() {
232
if (!this.input) {
233
return;
234
}
235
if (this.list) {
236
this.list.model = new DelayedPagedModel(this.input.model);
237
}
238
this.showWelcomeContent(!!this.input.showWelcomeContent);
239
if (!this.input.showWelcomeContent) {
240
this.updateBody();
241
}
242
}
243
244
private showWelcomeContent(show: boolean): void {
245
this.welcomeContainer?.classList.toggle('hide', !show);
246
this.listContainer?.classList.toggle('hide', show);
247
}
248
249
private createWelcomeContent(welcomeContainer: HTMLElement): void {
250
const welcomeContent = dom.append(welcomeContainer, dom.$('.mcp-welcome-content'));
251
252
const iconContainer = dom.append(welcomeContent, dom.$('.mcp-welcome-icon'));
253
const iconElement = dom.append(iconContainer, dom.$('span'));
254
iconElement.className = ThemeIcon.asClassName(mcpServerIcon);
255
256
const title = dom.append(welcomeContent, dom.$('.mcp-welcome-title'));
257
title.textContent = localize('mcp.welcome.title', "MCP Servers");
258
259
const settingsCommandLink = createMarkdownCommandLink({ id: 'workbench.action.openSettings', arguments: [`@id:${mcpGalleryServiceEnablementConfig}`], title: mcpGalleryServiceEnablementConfig, tooltip: localize('mcp.welcome.settings.tooltip', "Open Settings") }).toString();
260
const description = dom.append(welcomeContent, dom.$('.mcp-welcome-description'));
261
const markdownResult = this._register(this.markdownRendererService.render(
262
new MarkdownString(
263
localize('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."),
264
{ isTrusted: { enabledCommands: ['workbench.action.openSettings'] } },
265
)
266
.appendMarkdown('\n\n')
267
.appendMarkdown(localize('mcp.gallery.enableDialog.setting', "This feature is currently in preview. You can disable it anytime using the setting {0}.", settingsCommandLink)),
268
));
269
description.appendChild(markdownResult.element);
270
271
const buttonContainer = dom.append(welcomeContent, dom.$('.mcp-welcome-button-container'));
272
const button = this._register(new Button(buttonContainer, {
273
title: localize('mcp.welcome.enableGalleryButton', "Enable MCP Servers Marketplace"),
274
...defaultButtonStyles
275
}));
276
button.label = localize('mcp.welcome.enableGalleryButton', "Enable MCP Servers Marketplace");
277
278
this._register(button.onDidClick(async () => {
279
280
const { result } = await this.dialogService.prompt({
281
type: 'info',
282
message: localize('mcp.gallery.enableDialog.title', "Enable MCP Servers Marketplace?"),
283
custom: {
284
markdownDetails: [{
285
markdown: 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 })
286
}]
287
},
288
buttons: [
289
{ label: localize('mcp.gallery.enableDialog.enable', "Enable"), run: () => true },
290
{ label: localize('mcp.gallery.enableDialog.cancel', "Cancel"), run: () => false }
291
]
292
});
293
294
if (result) {
295
await this.configurationService.updateValue(mcpGalleryServiceEnablementConfig, true);
296
}
297
}));
298
}
299
300
private updateBody(message?: Message): void {
301
if (this.bodyTemplate) {
302
303
const count = this.input?.model.length ?? 0;
304
this.bodyTemplate.mcpServersList.classList.toggle('hidden', count === 0);
305
this.bodyTemplate.messageContainer.classList.toggle('hidden', !message && count > 0);
306
307
if (this.isBodyVisible()) {
308
if (message) {
309
this.bodyTemplate.messageSeverityIcon.className = SeverityIcon.className(message.severity);
310
this.bodyTemplate.messageBox.textContent = message.text;
311
} else if (count === 0) {
312
this.bodyTemplate.messageSeverityIcon.className = '';
313
this.bodyTemplate.messageBox.textContent = localize('no extensions found', "No MCP Servers found.");
314
}
315
if (this.bodyTemplate.messageBox.textContent) {
316
alert(this.bodyTemplate.messageBox.textContent);
317
}
318
}
319
}
320
}
321
322
private async query(query: string): Promise<IQueryResult> {
323
const disposables = new DisposableStore();
324
if (query) {
325
const servers = await this.mcpWorkbenchService.queryGallery({ text: query.replace('@mcp', '') });
326
const model = disposables.add(new IterativePagedModel(servers));
327
return { model, disposables };
328
}
329
330
const onDidChangeModel = disposables.add(new Emitter<IPagedModel<IWorkbenchMcpServer>>());
331
let servers = await this.mcpWorkbenchService.queryLocal();
332
disposables.add(Event.debounce(this.mcpWorkbenchService.onChange, () => undefined)(() => {
333
const mergedMcpServers = this.mergeChangedMcpServers(servers, [...this.mcpWorkbenchService.local]);
334
if (mergedMcpServers) {
335
servers = mergedMcpServers;
336
onDidChangeModel.fire(new PagedModel(servers));
337
}
338
}));
339
disposables.add(this.mcpWorkbenchService.onReset(() => onDidChangeModel.fire(new PagedModel([...this.mcpWorkbenchService.local]))));
340
return { model: new PagedModel(servers), onDidChangeModel: onDidChangeModel.event, disposables };
341
}
342
343
private mergeChangedMcpServers(mcpServers: IWorkbenchMcpServer[], newMcpServers: IWorkbenchMcpServer[]): IWorkbenchMcpServer[] | undefined {
344
const oldMcpServers = [...mcpServers];
345
const findPreviousMcpServerIndex = (from: number): number => {
346
let index = -1;
347
const previousMcpServerInNew = newMcpServers[from];
348
if (previousMcpServerInNew) {
349
index = oldMcpServers.findIndex(e => e.id === previousMcpServerInNew.id);
350
if (index === -1) {
351
return findPreviousMcpServerIndex(from - 1);
352
}
353
}
354
return index;
355
};
356
357
let hasChanged: boolean = false;
358
for (let index = 0; index < newMcpServers.length; index++) {
359
const newMcpServer = newMcpServers[index];
360
if (mcpServers.every(r => r.id !== newMcpServer.id)) {
361
hasChanged = true;
362
mcpServers.splice(findPreviousMcpServerIndex(index - 1) + 1, 0, newMcpServer);
363
}
364
}
365
366
for (let index = mcpServers.length - 1; index >= 0; index--) {
367
const oldMcpServer = mcpServers[index];
368
if (newMcpServers.every(r => r.id !== oldMcpServer.id) && newMcpServers.some(r => r.name === oldMcpServer.name)) {
369
hasChanged = true;
370
mcpServers.splice(index, 1);
371
}
372
}
373
374
if (!hasChanged) {
375
if (mcpServers.length === newMcpServers.length) {
376
for (let index = 0; index < newMcpServers.length; index++) {
377
if (mcpServers[index]?.id !== newMcpServers[index]?.id) {
378
hasChanged = true;
379
mcpServers = newMcpServers;
380
break;
381
}
382
}
383
}
384
}
385
386
return hasChanged ? mcpServers : undefined;
387
}
388
}
389
390
interface IMcpServerTemplateData {
391
root: HTMLElement;
392
element: HTMLElement;
393
name: HTMLElement;
394
description: HTMLElement;
395
starred: HTMLElement;
396
mcpServer: IWorkbenchMcpServer | null;
397
disposables: IDisposable[];
398
mcpServerDisposables: IDisposable[];
399
actionbar: ActionBar;
400
}
401
402
class McpServerRenderer implements IPagedRenderer<IWorkbenchMcpServer, IMcpServerTemplateData> {
403
404
static readonly templateId = 'mcpServer';
405
readonly templateId = McpServerRenderer.templateId;
406
407
constructor(
408
private readonly options: ExtensionListRendererOptions,
409
@IInstantiationService private readonly instantiationService: IInstantiationService,
410
@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService,
411
@INotificationService private readonly notificationService: INotificationService,
412
) { }
413
414
renderTemplate(root: HTMLElement): IMcpServerTemplateData {
415
const element = dom.append(root, dom.$('.mcp-server-item.extension-list-item'));
416
const iconContainer = dom.append(element, dom.$('.icon-container'));
417
const iconWidget = this.instantiationService.createInstance(McpServerIconWidget, iconContainer);
418
const details = dom.append(element, dom.$('.details'));
419
const headerContainer = dom.append(details, dom.$('.header-container'));
420
const header = dom.append(headerContainer, dom.$('.header'));
421
const name = dom.append(header, dom.$('span.name'));
422
const starred = dom.append(header, dom.$('span.ratings'));
423
const description = dom.append(details, dom.$('.description.ellipsis'));
424
const footer = dom.append(details, dom.$('.footer'));
425
const publisherWidget = this.instantiationService.createInstance(PublisherWidget, dom.append(footer, dom.$('.publisher-container')), true);
426
const actionbar = new ActionBar(footer, {
427
actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {
428
if (action instanceof DropDownAction) {
429
return action.createActionViewItem(options);
430
}
431
return undefined;
432
},
433
focusOnlyEnabledItems: true
434
});
435
436
actionbar.setFocusable(false);
437
const actionBarListener = actionbar.onDidRun(({ error }) => error && this.notificationService.error(error));
438
const mcpServerStatusAction = this.instantiationService.createInstance(McpServerStatusAction);
439
440
const actions = [
441
this.instantiationService.createInstance(InstallAction, true),
442
this.instantiationService.createInstance(InstallingLabelAction),
443
this.instantiationService.createInstance(ManageMcpServerAction, false),
444
mcpServerStatusAction
445
];
446
447
const widgets = [
448
iconWidget,
449
publisherWidget,
450
this.instantiationService.createInstance(StarredWidget, starred, true),
451
this.instantiationService.createInstance(McpServerScopeBadgeWidget, iconContainer),
452
this.instantiationService.createInstance(McpServerHoverWidget, { target: root, position: this.options.hoverOptions.position }, mcpServerStatusAction)
453
];
454
const extensionContainers: McpServerContainers = this.instantiationService.createInstance(McpServerContainers, [...actions, ...widgets]);
455
456
actionbar.push(actions, { icon: true, label: true });
457
const disposable = combinedDisposable(...actions, ...widgets, actionbar, actionBarListener, extensionContainers);
458
459
return {
460
root, element, name, description, starred, disposables: [disposable], actionbar,
461
mcpServerDisposables: [],
462
set mcpServer(mcpServer: IWorkbenchMcpServer) {
463
extensionContainers.mcpServer = mcpServer;
464
}
465
};
466
}
467
468
renderPlaceholder(index: number, data: IMcpServerTemplateData): void {
469
data.element.classList.add('loading');
470
471
data.mcpServerDisposables = dispose(data.mcpServerDisposables);
472
data.name.textContent = '';
473
data.description.textContent = '';
474
data.starred.style.display = 'none';
475
data.mcpServer = null;
476
}
477
478
renderElement(mcpServer: IWorkbenchMcpServer, index: number, data: IMcpServerTemplateData): void {
479
data.element.classList.remove('loading');
480
data.mcpServerDisposables = dispose(data.mcpServerDisposables);
481
data.root.setAttribute('data-mcp-server-id', mcpServer.id);
482
data.name.textContent = mcpServer.label;
483
data.description.textContent = mcpServer.description;
484
485
data.starred.style.display = '';
486
data.mcpServer = mcpServer;
487
488
const updateEnablement = () => data.root.classList.toggle('disabled', !!mcpServer.runtimeStatus?.state && mcpServer.runtimeStatus.state !== McpServerEnablementState.Enabled);
489
updateEnablement();
490
data.mcpServerDisposables.push(this.mcpWorkbenchService.onChange(e => {
491
if (!e || e.id === mcpServer.id) {
492
updateEnablement();
493
}
494
}));
495
}
496
497
disposeElement(mcpServer: IWorkbenchMcpServer, index: number, data: IMcpServerTemplateData): void {
498
data.mcpServerDisposables = dispose(data.mcpServerDisposables);
499
}
500
501
disposeTemplate(data: IMcpServerTemplateData): void {
502
data.mcpServerDisposables = dispose(data.mcpServerDisposables);
503
data.disposables = dispose(data.disposables);
504
}
505
}
506
507
508
export class DefaultBrowseMcpServersView extends McpServersListView {
509
510
protected override renderBody(container: HTMLElement): void {
511
super.renderBody(container);
512
this._register(this.mcpGalleryManifestService.onDidChangeMcpGalleryManifest(() => this.show()));
513
}
514
515
override async show(): Promise<IPagedModel<IWorkbenchMcpServer>> {
516
return super.show('@mcp');
517
}
518
}
519
520
export class McpServersViewsContribution extends Disposable implements IWorkbenchContribution {
521
522
static ID = 'workbench.mcp.servers.views.contribution';
523
524
constructor() {
525
super();
526
527
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([
528
{
529
id: InstalledMcpServersViewId,
530
name: localize2('mcp-installed', "MCP Servers - Installed"),
531
ctorDescriptor: new SyncDescriptor(McpServersListView, [{}]),
532
when: ContextKeyExpr.and(DefaultViewsContext, HasInstalledMcpServersContext, ChatContextKeys.Setup.hidden.negate()),
533
weight: 40,
534
order: 4,
535
canToggleVisibility: true
536
},
537
{
538
id: 'workbench.views.mcp.default.marketplace',
539
name: localize2('mcp', "MCP Servers"),
540
ctorDescriptor: new SyncDescriptor(DefaultBrowseMcpServersView, [{}]),
541
when: 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}`))),
542
weight: 40,
543
order: 4,
544
canToggleVisibility: true
545
},
546
{
547
id: 'workbench.views.mcp.marketplace',
548
name: localize2('mcp', "MCP Servers"),
549
ctorDescriptor: new SyncDescriptor(McpServersListView, [{}]),
550
when: ContextKeyExpr.and(SearchMcpServersContext, ChatContextKeys.Setup.hidden.negate(), McpServersGalleryStatusContext.isEqualTo(McpGalleryManifestStatus.Available), ContextKeyExpr.or(ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceUrlConfig}`), ProductQualityContext.notEqualsTo('stable'), ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceEnablementConfig}`))),
551
},
552
{
553
id: 'workbench.views.mcp.default.welcomeView',
554
name: localize2('mcp', "MCP Servers"),
555
ctorDescriptor: new SyncDescriptor(DefaultBrowseMcpServersView, [{ showWelcome: true }]),
556
when: 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()),
557
weight: 40,
558
order: 4,
559
canToggleVisibility: true
560
},
561
{
562
id: 'workbench.views.mcp.welcomeView',
563
name: localize2('mcp', "MCP Servers"),
564
ctorDescriptor: new SyncDescriptor(McpServersListView, [{ showWelcome: true }]),
565
when: ContextKeyExpr.and(SearchMcpServersContext, ChatContextKeys.Setup.hidden.negate(), McpServersGalleryStatusContext.isEqualTo(McpGalleryManifestStatus.Available), ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceUrlConfig}`).negate(), ProductQualityContext.isEqualTo('stable'), ContextKeyDefinedExpr.create(`config.${mcpGalleryServiceEnablementConfig}`).negate()),
566
}
567
], VIEW_CONTAINER);
568
}
569
}
570
571