Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts
3296 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 { getDomNodePagePosition } from '../../../../base/browser/dom.js';
7
import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';
8
import { alert } from '../../../../base/browser/ui/aria/aria.js';
9
import { Action, IAction, Separator } from '../../../../base/common/actions.js';
10
import { Emitter } from '../../../../base/common/event.js';
11
import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';
12
import { disposeIfDisposable } from '../../../../base/common/lifecycle.js';
13
import { ThemeIcon } from '../../../../base/common/themables.js';
14
import { URI } from '../../../../base/common/uri.js';
15
import { localize } from '../../../../nls.js';
16
import { Location } from '../../../../editor/common/languages.js';
17
import { ICommandService } from '../../../../platform/commands/common/commands.js';
18
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
19
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
20
import { mcpAccessConfig, McpAccessValue } from '../../../../platform/mcp/common/mcpManagement.js';
21
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
22
import { IAuthenticationService } from '../../../services/authentication/common/authentication.js';
23
import { IAccountQuery, IAuthenticationQueryService } from '../../../services/authentication/common/authenticationQuery.js';
24
import { IEditorService } from '../../../services/editor/common/editorService.js';
25
import { errorIcon, infoIcon, manageExtensionIcon, trustIcon, warningIcon } from '../../extensions/browser/extensionsIcons.js';
26
import { McpCommandIds } from '../common/mcpCommandIds.js';
27
import { IMcpRegistry } from '../common/mcpRegistryTypes.js';
28
import { IMcpSamplingService, IMcpServer, IMcpServerContainer, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCapability, McpConnectionState, McpServerEditorTab, McpServerEnablementState, McpServerInstallState } from '../common/mcpTypes.js';
29
import { startServerByFilter } from '../common/mcpTypesUtils.js';
30
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
31
32
export abstract class McpServerAction extends Action implements IMcpServerContainer {
33
34
static readonly EXTENSION_ACTION_CLASS = 'extension-action';
35
static readonly TEXT_ACTION_CLASS = `${McpServerAction.EXTENSION_ACTION_CLASS} text`;
36
static readonly LABEL_ACTION_CLASS = `${McpServerAction.EXTENSION_ACTION_CLASS} label`;
37
static readonly PROMINENT_LABEL_ACTION_CLASS = `${McpServerAction.LABEL_ACTION_CLASS} prominent`;
38
static readonly ICON_ACTION_CLASS = `${McpServerAction.EXTENSION_ACTION_CLASS} icon`;
39
40
private _mcpServer: IWorkbenchMcpServer | null = null;
41
get mcpServer(): IWorkbenchMcpServer | null { return this._mcpServer; }
42
set mcpServer(mcpServer: IWorkbenchMcpServer | null) { this._mcpServer = mcpServer; this.update(); }
43
44
abstract update(): void;
45
}
46
47
export abstract class DropDownAction extends McpServerAction {
48
49
constructor(
50
id: string,
51
label: string,
52
cssClass: string,
53
enabled: boolean,
54
@IInstantiationService protected instantiationService: IInstantiationService
55
) {
56
super(id, label, cssClass, enabled);
57
}
58
59
private _actionViewItem: DropDownExtensionActionViewItem | null = null;
60
createActionViewItem(options: IActionViewItemOptions): DropDownExtensionActionViewItem {
61
this._actionViewItem = this.instantiationService.createInstance(DropDownExtensionActionViewItem, this, options);
62
return this._actionViewItem;
63
}
64
65
public override run(actionGroups: IAction[][]): Promise<any> {
66
this._actionViewItem?.showMenu(actionGroups);
67
return Promise.resolve();
68
}
69
}
70
71
export class DropDownExtensionActionViewItem extends ActionViewItem {
72
73
constructor(
74
action: IAction,
75
options: IActionViewItemOptions,
76
@IContextMenuService private readonly contextMenuService: IContextMenuService
77
) {
78
super(null, action, { ...options, icon: true, label: true });
79
}
80
81
public showMenu(menuActionGroups: IAction[][]): void {
82
if (this.element) {
83
const actions = this.getActions(menuActionGroups);
84
const elementPosition = getDomNodePagePosition(this.element);
85
const anchor = { x: elementPosition.left, y: elementPosition.top + elementPosition.height + 10 };
86
this.contextMenuService.showContextMenu({
87
getAnchor: () => anchor,
88
getActions: () => actions,
89
actionRunner: this.actionRunner,
90
onHide: () => disposeIfDisposable(actions)
91
});
92
}
93
}
94
95
private getActions(menuActionGroups: IAction[][]): IAction[] {
96
let actions: IAction[] = [];
97
for (const menuActions of menuActionGroups) {
98
actions = [...actions, ...menuActions, new Separator()];
99
}
100
return actions.length ? actions.slice(0, actions.length - 1) : actions;
101
}
102
}
103
104
export class InstallAction extends McpServerAction {
105
106
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent install`;
107
private static readonly HIDE = `${this.CLASS} hide`;
108
109
constructor(
110
private readonly editor: boolean,
111
@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService,
112
@ITelemetryService private readonly telemetryService: ITelemetryService,
113
@IMcpService private readonly mcpService: IMcpService,
114
) {
115
super('extensions.install', localize('install', "Install"), InstallAction.CLASS, false);
116
this.update();
117
}
118
119
update(): void {
120
this.enabled = false;
121
this.class = InstallAction.HIDE;
122
if (!this.mcpServer?.gallery && !this.mcpServer?.installable) {
123
return;
124
}
125
if (this.mcpServer.installState !== McpServerInstallState.Uninstalled) {
126
return;
127
}
128
this.class = InstallAction.CLASS;
129
this.enabled = this.mcpWorkbenchService.canInstall(this.mcpServer) === true;
130
}
131
132
override async run(): Promise<any> {
133
if (!this.mcpServer) {
134
return;
135
}
136
137
if (!this.editor) {
138
this.mcpWorkbenchService.open(this.mcpServer);
139
alert(localize('mcpServerInstallation', "Installing MCP Server {0} started. An editor is now open with more details on this MCP Server", this.mcpServer.label));
140
}
141
142
type McpServerInstallClassification = {
143
owner: 'sandy081';
144
comment: 'Used to understand if the action to install the MCP server is used.';
145
name?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The gallery name of the MCP server being installed' };
146
};
147
type McpServerInstall = {
148
name?: string;
149
};
150
this.telemetryService.publicLog2<McpServerInstall, McpServerInstallClassification>('mcp:action:install', { name: this.mcpServer.gallery?.name });
151
152
const installed = await this.mcpWorkbenchService.install(this.mcpServer);
153
154
await startServerByFilter(this.mcpService, s => {
155
return s.definition.label === installed.name;
156
});
157
}
158
}
159
160
export class InstallingLabelAction extends McpServerAction {
161
162
private static readonly LABEL = localize('installing', "Installing");
163
private static readonly CLASS = `${McpServerAction.LABEL_ACTION_CLASS} install installing`;
164
165
constructor() {
166
super('extension.installing', InstallingLabelAction.LABEL, InstallingLabelAction.CLASS, false);
167
}
168
169
update(): void {
170
this.class = `${InstallingLabelAction.CLASS}${this.mcpServer && this.mcpServer.installState === McpServerInstallState.Installing ? '' : ' hide'}`;
171
}
172
}
173
174
export class UninstallAction extends McpServerAction {
175
176
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent uninstall`;
177
private static readonly HIDE = `${this.CLASS} hide`;
178
179
constructor(
180
@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService,
181
) {
182
super('extensions.uninstall', localize('uninstall', "Uninstall"), UninstallAction.CLASS, false);
183
this.update();
184
}
185
186
update(): void {
187
this.enabled = false;
188
this.class = UninstallAction.HIDE;
189
if (!this.mcpServer) {
190
return;
191
}
192
if (!this.mcpServer.local) {
193
return;
194
}
195
if (this.mcpServer.installState !== McpServerInstallState.Installed) {
196
this.enabled = false;
197
return;
198
}
199
this.class = UninstallAction.CLASS;
200
this.enabled = true;
201
this.label = localize('uninstall', "Uninstall");
202
}
203
204
override async run(): Promise<any> {
205
if (!this.mcpServer) {
206
return;
207
}
208
await this.mcpWorkbenchService.uninstall(this.mcpServer);
209
}
210
}
211
212
export class ManageMcpServerAction extends DropDownAction {
213
214
static readonly ID = 'mcpServer.manage';
215
216
private static readonly Class = `${McpServerAction.ICON_ACTION_CLASS} manage ` + ThemeIcon.asClassName(manageExtensionIcon);
217
private static readonly HideManageExtensionClass = `${this.Class} hide`;
218
219
constructor(
220
private readonly isEditorAction: boolean,
221
@IInstantiationService instantiationService: IInstantiationService,
222
) {
223
224
super(ManageMcpServerAction.ID, '', '', true, instantiationService);
225
this.tooltip = localize('manage', "Manage");
226
this.update();
227
}
228
229
async getActionGroups(): Promise<IAction[][]> {
230
const groups: IAction[][] = [];
231
groups.push([
232
this.instantiationService.createInstance(StartServerAction),
233
]);
234
groups.push([
235
this.instantiationService.createInstance(StopServerAction),
236
this.instantiationService.createInstance(RestartServerAction),
237
]);
238
groups.push([
239
this.instantiationService.createInstance(AuthServerAction),
240
]);
241
groups.push([
242
this.instantiationService.createInstance(ShowServerOutputAction),
243
this.instantiationService.createInstance(ShowServerConfigurationAction),
244
this.instantiationService.createInstance(ShowServerJsonConfigurationAction),
245
]);
246
groups.push([
247
this.instantiationService.createInstance(ConfigureModelAccessAction),
248
this.instantiationService.createInstance(ShowSamplingRequestsAction),
249
]);
250
groups.push([
251
this.instantiationService.createInstance(BrowseResourcesAction),
252
]);
253
if (!this.isEditorAction) {
254
groups.push([
255
this.instantiationService.createInstance(UninstallAction),
256
]);
257
}
258
groups.forEach(group => group.forEach(extensionAction => {
259
if (extensionAction instanceof McpServerAction) {
260
extensionAction.mcpServer = this.mcpServer;
261
}
262
}));
263
264
return groups;
265
}
266
267
override async run(): Promise<any> {
268
return super.run(await this.getActionGroups());
269
}
270
271
update(): void {
272
this.class = ManageMcpServerAction.HideManageExtensionClass;
273
this.enabled = false;
274
if (this.mcpServer) {
275
this.enabled = !!this.mcpServer.local;
276
this.class = this.enabled ? ManageMcpServerAction.Class : ManageMcpServerAction.HideManageExtensionClass;
277
}
278
}
279
}
280
281
export class StartServerAction extends McpServerAction {
282
283
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent start`;
284
private static readonly HIDE = `${this.CLASS} hide`;
285
286
constructor(
287
@IMcpService private readonly mcpService: IMcpService,
288
) {
289
super('extensions.start', localize('start', "Start Server"), StartServerAction.CLASS, false);
290
this.update();
291
}
292
293
update(): void {
294
this.enabled = false;
295
this.class = StartServerAction.HIDE;
296
const server = this.getServer();
297
if (!server) {
298
return;
299
}
300
const serverState = server.connectionState.get();
301
if (!McpConnectionState.canBeStarted(serverState.state)) {
302
return;
303
}
304
this.class = StartServerAction.CLASS;
305
this.enabled = true;
306
this.label = localize('start', "Start Server");
307
}
308
309
override async run(): Promise<any> {
310
const server = this.getServer();
311
if (!server) {
312
return;
313
}
314
await server.start({ promptType: 'all-untrusted' });
315
server.showOutput();
316
}
317
318
private getServer(): IMcpServer | undefined {
319
if (!this.mcpServer) {
320
return;
321
}
322
if (!this.mcpServer.local) {
323
return;
324
}
325
return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);
326
}
327
}
328
329
export class StopServerAction extends McpServerAction {
330
331
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent stop`;
332
private static readonly HIDE = `${this.CLASS} hide`;
333
334
constructor(
335
@IMcpService private readonly mcpService: IMcpService,
336
) {
337
super('extensions.stop', localize('stop', "Stop Server"), StopServerAction.CLASS, false);
338
this.update();
339
}
340
341
update(): void {
342
this.enabled = false;
343
this.class = StopServerAction.HIDE;
344
const server = this.getServer();
345
if (!server) {
346
return;
347
}
348
const serverState = server.connectionState.get();
349
if (McpConnectionState.canBeStarted(serverState.state)) {
350
return;
351
}
352
this.class = StopServerAction.CLASS;
353
this.enabled = true;
354
this.label = localize('stop', "Stop Server");
355
}
356
357
override async run(): Promise<any> {
358
const server = this.getServer();
359
if (!server) {
360
return;
361
}
362
await server.stop();
363
}
364
365
private getServer(): IMcpServer | undefined {
366
if (!this.mcpServer) {
367
return;
368
}
369
if (!this.mcpServer.local) {
370
return;
371
}
372
return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);
373
}
374
}
375
376
export class RestartServerAction extends McpServerAction {
377
378
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent restart`;
379
private static readonly HIDE = `${this.CLASS} hide`;
380
381
constructor(
382
@IMcpService private readonly mcpService: IMcpService,
383
) {
384
super('extensions.restart', localize('restart', "Restart Server"), RestartServerAction.CLASS, false);
385
this.update();
386
}
387
388
update(): void {
389
this.enabled = false;
390
this.class = RestartServerAction.HIDE;
391
const server = this.getServer();
392
if (!server) {
393
return;
394
}
395
const serverState = server.connectionState.get();
396
if (McpConnectionState.canBeStarted(serverState.state)) {
397
return;
398
}
399
this.class = RestartServerAction.CLASS;
400
this.enabled = true;
401
this.label = localize('restart', "Restart Server");
402
}
403
404
override async run(): Promise<any> {
405
const server = this.getServer();
406
if (!server) {
407
return;
408
}
409
await server.stop();
410
await server.start({ promptType: 'all-untrusted' });
411
server.showOutput();
412
}
413
414
private getServer(): IMcpServer | undefined {
415
if (!this.mcpServer) {
416
return;
417
}
418
if (!this.mcpServer.local) {
419
return;
420
}
421
return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);
422
}
423
}
424
425
export class AuthServerAction extends McpServerAction {
426
427
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent account`;
428
private static readonly HIDE = `${this.CLASS} hide`;
429
430
private static readonly SIGN_OUT = localize('mcp.signOut', 'Sign Out');
431
private static readonly DISCONNECT = localize('mcp.disconnect', 'Disconnect Account');
432
433
private _accountQuery: IAccountQuery | undefined;
434
435
constructor(
436
@IMcpService private readonly mcpService: IMcpService,
437
@IAuthenticationQueryService private readonly _authenticationQueryService: IAuthenticationQueryService,
438
@IAuthenticationService private readonly _authenticationService: IAuthenticationService
439
) {
440
super('extensions.restart', localize('restart', "Restart Server"), RestartServerAction.CLASS, false);
441
this.update();
442
}
443
444
update(): void {
445
this.enabled = false;
446
this.class = AuthServerAction.HIDE;
447
const server = this.getServer();
448
if (!server) {
449
return;
450
}
451
const accountQuery = this.getAccountQuery();
452
if (!accountQuery) {
453
return;
454
}
455
this._accountQuery = accountQuery;
456
this.class = AuthServerAction.CLASS;
457
this.enabled = true;
458
let label = accountQuery.entities().getEntityCount().total > 1 ? AuthServerAction.DISCONNECT : AuthServerAction.SIGN_OUT;
459
label += ` (${accountQuery.accountName})`;
460
this.label = label;
461
}
462
463
override async run(): Promise<void> {
464
const server = this.getServer();
465
if (!server) {
466
return;
467
}
468
const accountQuery = this.getAccountQuery();
469
if (!accountQuery) {
470
return;
471
}
472
await server.stop();
473
const { providerId, accountName } = accountQuery;
474
accountQuery.mcpServer(server.definition.id).setAccessAllowed(false, server.definition.label);
475
if (this.label === AuthServerAction.SIGN_OUT) {
476
const accounts = await this._authenticationService.getAccounts(providerId);
477
const account = accounts.find(a => a.label === accountName);
478
if (account) {
479
const sessions = await this._authenticationService.getSessions(providerId, undefined, { account });
480
for (const session of sessions) {
481
await this._authenticationService.removeSession(providerId, session.id);
482
}
483
}
484
}
485
}
486
487
private getServer(): IMcpServer | undefined {
488
if (!this.mcpServer) {
489
return;
490
}
491
if (!this.mcpServer.local) {
492
return;
493
}
494
return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);
495
}
496
497
private getAccountQuery(): IAccountQuery | undefined {
498
const server = this.getServer();
499
if (!server) {
500
return undefined;
501
}
502
if (this._accountQuery) {
503
return this._accountQuery;
504
}
505
const serverId = server.definition.id;
506
const preferences = this._authenticationQueryService.mcpServer(serverId).getAllAccountPreferences();
507
if (!preferences.size) {
508
return undefined;
509
}
510
for (const [providerId, accountName] of preferences) {
511
const accountQuery = this._authenticationQueryService.provider(providerId).account(accountName);
512
if (!accountQuery.mcpServer(serverId).isAccessAllowed()) {
513
continue; // skip accounts that are not allowed
514
}
515
return accountQuery;
516
}
517
return undefined;
518
}
519
520
}
521
522
export class ShowServerOutputAction extends McpServerAction {
523
524
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent output`;
525
private static readonly HIDE = `${this.CLASS} hide`;
526
527
constructor(
528
@IMcpService private readonly mcpService: IMcpService,
529
) {
530
super('extensions.output', localize('output', "Show Output"), ShowServerOutputAction.CLASS, false);
531
this.update();
532
}
533
534
update(): void {
535
this.enabled = false;
536
this.class = ShowServerOutputAction.HIDE;
537
const server = this.getServer();
538
if (!server) {
539
return;
540
}
541
this.class = ShowServerOutputAction.CLASS;
542
this.enabled = true;
543
this.label = localize('output', "Show Output");
544
}
545
546
override async run(): Promise<any> {
547
const server = this.getServer();
548
if (!server) {
549
return;
550
}
551
server.showOutput();
552
}
553
554
private getServer(): IMcpServer | undefined {
555
if (!this.mcpServer) {
556
return;
557
}
558
if (!this.mcpServer.local) {
559
return;
560
}
561
return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);
562
}
563
}
564
565
export class ShowServerConfigurationAction extends McpServerAction {
566
567
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent config`;
568
private static readonly HIDE = `${this.CLASS} hide`;
569
570
constructor(
571
@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService
572
) {
573
super('extensions.config', localize('config', "Show Configuration"), ShowServerConfigurationAction.CLASS, false);
574
this.update();
575
}
576
577
update(): void {
578
this.enabled = false;
579
this.class = ShowServerConfigurationAction.HIDE;
580
if (!this.mcpServer?.local) {
581
return;
582
}
583
this.class = ShowServerConfigurationAction.CLASS;
584
this.enabled = true;
585
}
586
587
override async run(): Promise<any> {
588
if (!this.mcpServer?.local) {
589
return;
590
}
591
this.mcpWorkbenchService.open(this.mcpServer, { tab: McpServerEditorTab.Configuration });
592
}
593
594
}
595
596
export class ShowServerJsonConfigurationAction extends McpServerAction {
597
598
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent config`;
599
private static readonly HIDE = `${this.CLASS} hide`;
600
601
constructor(
602
@IMcpService private readonly mcpService: IMcpService,
603
@IMcpRegistry private readonly mcpRegistry: IMcpRegistry,
604
@IEditorService private readonly editorService: IEditorService,
605
) {
606
super('extensions.jsonConfig', localize('configJson', "Show Configuration (JSON)"), ShowServerJsonConfigurationAction.CLASS, false);
607
this.update();
608
}
609
610
update(): void {
611
this.enabled = false;
612
this.class = ShowServerJsonConfigurationAction.HIDE;
613
const configurationTarget = this.getConfigurationTarget();
614
if (!configurationTarget) {
615
return;
616
}
617
this.class = ShowServerConfigurationAction.CLASS;
618
this.enabled = true;
619
}
620
621
override async run(): Promise<any> {
622
const configurationTarget = this.getConfigurationTarget();
623
if (!configurationTarget) {
624
return;
625
}
626
this.editorService.openEditor({
627
resource: URI.isUri(configurationTarget) ? configurationTarget : configurationTarget!.uri,
628
options: { selection: URI.isUri(configurationTarget) ? undefined : configurationTarget!.range }
629
});
630
}
631
632
private getConfigurationTarget(): Location | URI | undefined {
633
if (!this.mcpServer) {
634
return;
635
}
636
if (!this.mcpServer.local) {
637
return;
638
}
639
const server = this.mcpService.servers.get().find(s => s.definition.label === this.mcpServer?.name);
640
if (!server) {
641
return;
642
}
643
const collection = this.mcpRegistry.collections.get().find(c => c.id === server.collection.id);
644
const serverDefinition = collection?.serverDefinitions.get().find(s => s.id === server.definition.id);
645
return serverDefinition?.presentation?.origin || collection?.presentation?.origin;
646
}
647
}
648
649
export class ConfigureModelAccessAction extends McpServerAction {
650
651
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent config`;
652
private static readonly HIDE = `${this.CLASS} hide`;
653
654
constructor(
655
@IMcpService private readonly mcpService: IMcpService,
656
@ICommandService private readonly commandService: ICommandService,
657
) {
658
super('extensions.config', localize('mcp.configAccess', 'Configure Model Access'), ConfigureModelAccessAction.CLASS, false);
659
this.update();
660
}
661
662
update(): void {
663
this.enabled = false;
664
this.class = ConfigureModelAccessAction.HIDE;
665
const server = this.getServer();
666
if (!server) {
667
return;
668
}
669
this.class = ConfigureModelAccessAction.CLASS;
670
this.enabled = true;
671
this.label = localize('mcp.configAccess', 'Configure Model Access');
672
}
673
674
override async run(): Promise<any> {
675
const server = this.getServer();
676
if (!server) {
677
return;
678
}
679
this.commandService.executeCommand(McpCommandIds.ConfigureSamplingModels, server);
680
}
681
682
private getServer(): IMcpServer | undefined {
683
if (!this.mcpServer) {
684
return;
685
}
686
if (!this.mcpServer.local) {
687
return;
688
}
689
return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);
690
}
691
}
692
693
export class ShowSamplingRequestsAction extends McpServerAction {
694
695
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent config`;
696
private static readonly HIDE = `${this.CLASS} hide`;
697
698
constructor(
699
@IMcpService private readonly mcpService: IMcpService,
700
@IMcpSamplingService private readonly samplingService: IMcpSamplingService,
701
@IEditorService private readonly editorService: IEditorService,
702
) {
703
super('extensions.config', localize('mcp.samplingLog', 'Show Sampling Requests'), ShowSamplingRequestsAction.CLASS, false);
704
this.update();
705
}
706
707
update(): void {
708
this.enabled = false;
709
this.class = ShowSamplingRequestsAction.HIDE;
710
const server = this.getServer();
711
if (!server) {
712
return;
713
}
714
if (!this.samplingService.hasLogs(server)) {
715
return;
716
}
717
this.class = ShowSamplingRequestsAction.CLASS;
718
this.enabled = true;
719
}
720
721
override async run(): Promise<any> {
722
const server = this.getServer();
723
if (!server) {
724
return;
725
}
726
if (!this.samplingService.hasLogs(server)) {
727
return;
728
}
729
this.editorService.openEditor({
730
resource: undefined,
731
contents: this.samplingService.getLogText(server),
732
label: localize('mcp.samplingLog.title', 'MCP Sampling: {0}', server.definition.label),
733
});
734
}
735
736
private getServer(): IMcpServer | undefined {
737
if (!this.mcpServer) {
738
return;
739
}
740
if (!this.mcpServer.local) {
741
return;
742
}
743
return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);
744
}
745
}
746
747
export class BrowseResourcesAction extends McpServerAction {
748
749
static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent config`;
750
private static readonly HIDE = `${this.CLASS} hide`;
751
752
constructor(
753
@IMcpService private readonly mcpService: IMcpService,
754
@ICommandService private readonly commandService: ICommandService,
755
) {
756
super('extensions.config', localize('mcp.resources', 'Browse Resources'), BrowseResourcesAction.CLASS, false);
757
this.update();
758
}
759
760
update(): void {
761
this.enabled = false;
762
this.class = BrowseResourcesAction.HIDE;
763
const server = this.getServer();
764
if (!server) {
765
return;
766
}
767
const capabilities = server.capabilities.get();
768
if (capabilities !== undefined && !(capabilities & McpCapability.Resources)) {
769
return;
770
}
771
this.class = BrowseResourcesAction.CLASS;
772
this.enabled = true;
773
}
774
775
override async run(): Promise<any> {
776
const server = this.getServer();
777
if (!server) {
778
return;
779
}
780
const capabilities = server.capabilities.get();
781
if (capabilities !== undefined && !(capabilities & McpCapability.Resources)) {
782
return;
783
}
784
return this.commandService.executeCommand(McpCommandIds.BrowseResources, server);
785
}
786
787
private getServer(): IMcpServer | undefined {
788
if (!this.mcpServer) {
789
return;
790
}
791
if (!this.mcpServer.local) {
792
return;
793
}
794
return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);
795
}
796
}
797
798
export type McpServerStatus = { readonly message: IMarkdownString; readonly icon?: ThemeIcon };
799
800
export class McpServerStatusAction extends McpServerAction {
801
802
private static readonly CLASS = `${McpServerAction.ICON_ACTION_CLASS} extension-status`;
803
804
private _status: McpServerStatus[] = [];
805
get status(): McpServerStatus[] { return this._status; }
806
807
private readonly _onDidChangeStatus = this._register(new Emitter<void>());
808
readonly onDidChangeStatus = this._onDidChangeStatus.event;
809
810
constructor(
811
@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService,
812
@ICommandService private readonly commandService: ICommandService,
813
@IConfigurationService private readonly configurationService: IConfigurationService,
814
) {
815
super('extensions.status', '', `${McpServerStatusAction.CLASS} hide`, false);
816
this.update();
817
}
818
819
update(): void {
820
this.computeAndUpdateStatus();
821
}
822
823
private computeAndUpdateStatus(): void {
824
this.updateStatus(undefined, true);
825
this.enabled = false;
826
827
if (!this.mcpServer) {
828
return;
829
}
830
831
if ((this.mcpServer.gallery || this.mcpServer.installable) && this.mcpServer.installState === McpServerInstallState.Uninstalled) {
832
const result = this.mcpWorkbenchService.canInstall(this.mcpServer);
833
if (result !== true) {
834
this.updateStatus({ icon: warningIcon, message: result }, true);
835
return;
836
}
837
}
838
839
if (this.mcpServer.local && this.mcpServer.installState === McpServerInstallState.Installed && this.mcpServer.enablementState === McpServerEnablementState.DisabledByAccess) {
840
const settingsCommandLink = URI.parse(`command:workbench.action.openSettings?${encodeURIComponent(JSON.stringify({ query: `@id:${mcpAccessConfig}` }))}`).toString();
841
if (this.configurationService.getValue(mcpAccessConfig) === McpAccessValue.None) {
842
this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('disabled - all not allowed', "This MCP Server is disabled because MCP servers are configured to be disabled in the Editor. Please check your [settings]({0}).", settingsCommandLink)) }, true);
843
} else {
844
this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('disabled - some not allowed', "This MCP Server is disabled because it is configured to be disabled in the Editor. Please check your [settings]({0}).", settingsCommandLink)) }, true);
845
}
846
return;
847
}
848
}
849
850
private updateStatus(status: McpServerStatus | undefined, updateClass: boolean): void {
851
if (status) {
852
if (this._status.some(s => s.message.value === status.message.value && s.icon?.id === status.icon?.id)) {
853
return;
854
}
855
} else {
856
if (this._status.length === 0) {
857
return;
858
}
859
this._status = [];
860
}
861
862
if (status) {
863
this._status.push(status);
864
this._status.sort((a, b) =>
865
b.icon === trustIcon ? -1 :
866
a.icon === trustIcon ? 1 :
867
b.icon === errorIcon ? -1 :
868
a.icon === errorIcon ? 1 :
869
b.icon === warningIcon ? -1 :
870
a.icon === warningIcon ? 1 :
871
b.icon === infoIcon ? -1 :
872
a.icon === infoIcon ? 1 :
873
0
874
);
875
}
876
877
if (updateClass) {
878
if (status?.icon === errorIcon) {
879
this.class = `${McpServerStatusAction.CLASS} extension-status-error ${ThemeIcon.asClassName(errorIcon)}`;
880
}
881
else if (status?.icon === warningIcon) {
882
this.class = `${McpServerStatusAction.CLASS} extension-status-warning ${ThemeIcon.asClassName(warningIcon)}`;
883
}
884
else if (status?.icon === infoIcon) {
885
this.class = `${McpServerStatusAction.CLASS} extension-status-info ${ThemeIcon.asClassName(infoIcon)}`;
886
}
887
else if (status?.icon === trustIcon) {
888
this.class = `${McpServerStatusAction.CLASS} ${ThemeIcon.asClassName(trustIcon)}`;
889
}
890
else {
891
this.class = `${McpServerStatusAction.CLASS} hide`;
892
}
893
}
894
this._onDidChangeStatus.fire();
895
}
896
897
override async run(): Promise<any> {
898
if (this._status[0]?.icon === trustIcon) {
899
return this.commandService.executeCommand('workbench.trust.manage');
900
}
901
}
902
}
903
904