Path: blob/main/src/vs/workbench/contrib/mcp/browser/mcpServerActions.ts
3296 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 { getDomNodePagePosition } from '../../../../base/browser/dom.js';6import { ActionViewItem, IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js';7import { alert } from '../../../../base/browser/ui/aria/aria.js';8import { Action, IAction, Separator } from '../../../../base/common/actions.js';9import { Emitter } from '../../../../base/common/event.js';10import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';11import { disposeIfDisposable } from '../../../../base/common/lifecycle.js';12import { ThemeIcon } from '../../../../base/common/themables.js';13import { URI } from '../../../../base/common/uri.js';14import { localize } from '../../../../nls.js';15import { Location } from '../../../../editor/common/languages.js';16import { ICommandService } from '../../../../platform/commands/common/commands.js';17import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';18import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';19import { mcpAccessConfig, McpAccessValue } from '../../../../platform/mcp/common/mcpManagement.js';20import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';21import { IAuthenticationService } from '../../../services/authentication/common/authentication.js';22import { IAccountQuery, IAuthenticationQueryService } from '../../../services/authentication/common/authenticationQuery.js';23import { IEditorService } from '../../../services/editor/common/editorService.js';24import { errorIcon, infoIcon, manageExtensionIcon, trustIcon, warningIcon } from '../../extensions/browser/extensionsIcons.js';25import { McpCommandIds } from '../common/mcpCommandIds.js';26import { IMcpRegistry } from '../common/mcpRegistryTypes.js';27import { IMcpSamplingService, IMcpServer, IMcpServerContainer, IMcpService, IMcpWorkbenchService, IWorkbenchMcpServer, McpCapability, McpConnectionState, McpServerEditorTab, McpServerEnablementState, McpServerInstallState } from '../common/mcpTypes.js';28import { startServerByFilter } from '../common/mcpTypesUtils.js';29import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';3031export abstract class McpServerAction extends Action implements IMcpServerContainer {3233static readonly EXTENSION_ACTION_CLASS = 'extension-action';34static readonly TEXT_ACTION_CLASS = `${McpServerAction.EXTENSION_ACTION_CLASS} text`;35static readonly LABEL_ACTION_CLASS = `${McpServerAction.EXTENSION_ACTION_CLASS} label`;36static readonly PROMINENT_LABEL_ACTION_CLASS = `${McpServerAction.LABEL_ACTION_CLASS} prominent`;37static readonly ICON_ACTION_CLASS = `${McpServerAction.EXTENSION_ACTION_CLASS} icon`;3839private _mcpServer: IWorkbenchMcpServer | null = null;40get mcpServer(): IWorkbenchMcpServer | null { return this._mcpServer; }41set mcpServer(mcpServer: IWorkbenchMcpServer | null) { this._mcpServer = mcpServer; this.update(); }4243abstract update(): void;44}4546export abstract class DropDownAction extends McpServerAction {4748constructor(49id: string,50label: string,51cssClass: string,52enabled: boolean,53@IInstantiationService protected instantiationService: IInstantiationService54) {55super(id, label, cssClass, enabled);56}5758private _actionViewItem: DropDownExtensionActionViewItem | null = null;59createActionViewItem(options: IActionViewItemOptions): DropDownExtensionActionViewItem {60this._actionViewItem = this.instantiationService.createInstance(DropDownExtensionActionViewItem, this, options);61return this._actionViewItem;62}6364public override run(actionGroups: IAction[][]): Promise<any> {65this._actionViewItem?.showMenu(actionGroups);66return Promise.resolve();67}68}6970export class DropDownExtensionActionViewItem extends ActionViewItem {7172constructor(73action: IAction,74options: IActionViewItemOptions,75@IContextMenuService private readonly contextMenuService: IContextMenuService76) {77super(null, action, { ...options, icon: true, label: true });78}7980public showMenu(menuActionGroups: IAction[][]): void {81if (this.element) {82const actions = this.getActions(menuActionGroups);83const elementPosition = getDomNodePagePosition(this.element);84const anchor = { x: elementPosition.left, y: elementPosition.top + elementPosition.height + 10 };85this.contextMenuService.showContextMenu({86getAnchor: () => anchor,87getActions: () => actions,88actionRunner: this.actionRunner,89onHide: () => disposeIfDisposable(actions)90});91}92}9394private getActions(menuActionGroups: IAction[][]): IAction[] {95let actions: IAction[] = [];96for (const menuActions of menuActionGroups) {97actions = [...actions, ...menuActions, new Separator()];98}99return actions.length ? actions.slice(0, actions.length - 1) : actions;100}101}102103export class InstallAction extends McpServerAction {104105static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent install`;106private static readonly HIDE = `${this.CLASS} hide`;107108constructor(109private readonly editor: boolean,110@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService,111@ITelemetryService private readonly telemetryService: ITelemetryService,112@IMcpService private readonly mcpService: IMcpService,113) {114super('extensions.install', localize('install', "Install"), InstallAction.CLASS, false);115this.update();116}117118update(): void {119this.enabled = false;120this.class = InstallAction.HIDE;121if (!this.mcpServer?.gallery && !this.mcpServer?.installable) {122return;123}124if (this.mcpServer.installState !== McpServerInstallState.Uninstalled) {125return;126}127this.class = InstallAction.CLASS;128this.enabled = this.mcpWorkbenchService.canInstall(this.mcpServer) === true;129}130131override async run(): Promise<any> {132if (!this.mcpServer) {133return;134}135136if (!this.editor) {137this.mcpWorkbenchService.open(this.mcpServer);138alert(localize('mcpServerInstallation', "Installing MCP Server {0} started. An editor is now open with more details on this MCP Server", this.mcpServer.label));139}140141type McpServerInstallClassification = {142owner: 'sandy081';143comment: 'Used to understand if the action to install the MCP server is used.';144name?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The gallery name of the MCP server being installed' };145};146type McpServerInstall = {147name?: string;148};149this.telemetryService.publicLog2<McpServerInstall, McpServerInstallClassification>('mcp:action:install', { name: this.mcpServer.gallery?.name });150151const installed = await this.mcpWorkbenchService.install(this.mcpServer);152153await startServerByFilter(this.mcpService, s => {154return s.definition.label === installed.name;155});156}157}158159export class InstallingLabelAction extends McpServerAction {160161private static readonly LABEL = localize('installing', "Installing");162private static readonly CLASS = `${McpServerAction.LABEL_ACTION_CLASS} install installing`;163164constructor() {165super('extension.installing', InstallingLabelAction.LABEL, InstallingLabelAction.CLASS, false);166}167168update(): void {169this.class = `${InstallingLabelAction.CLASS}${this.mcpServer && this.mcpServer.installState === McpServerInstallState.Installing ? '' : ' hide'}`;170}171}172173export class UninstallAction extends McpServerAction {174175static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent uninstall`;176private static readonly HIDE = `${this.CLASS} hide`;177178constructor(179@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService,180) {181super('extensions.uninstall', localize('uninstall', "Uninstall"), UninstallAction.CLASS, false);182this.update();183}184185update(): void {186this.enabled = false;187this.class = UninstallAction.HIDE;188if (!this.mcpServer) {189return;190}191if (!this.mcpServer.local) {192return;193}194if (this.mcpServer.installState !== McpServerInstallState.Installed) {195this.enabled = false;196return;197}198this.class = UninstallAction.CLASS;199this.enabled = true;200this.label = localize('uninstall', "Uninstall");201}202203override async run(): Promise<any> {204if (!this.mcpServer) {205return;206}207await this.mcpWorkbenchService.uninstall(this.mcpServer);208}209}210211export class ManageMcpServerAction extends DropDownAction {212213static readonly ID = 'mcpServer.manage';214215private static readonly Class = `${McpServerAction.ICON_ACTION_CLASS} manage ` + ThemeIcon.asClassName(manageExtensionIcon);216private static readonly HideManageExtensionClass = `${this.Class} hide`;217218constructor(219private readonly isEditorAction: boolean,220@IInstantiationService instantiationService: IInstantiationService,221) {222223super(ManageMcpServerAction.ID, '', '', true, instantiationService);224this.tooltip = localize('manage', "Manage");225this.update();226}227228async getActionGroups(): Promise<IAction[][]> {229const groups: IAction[][] = [];230groups.push([231this.instantiationService.createInstance(StartServerAction),232]);233groups.push([234this.instantiationService.createInstance(StopServerAction),235this.instantiationService.createInstance(RestartServerAction),236]);237groups.push([238this.instantiationService.createInstance(AuthServerAction),239]);240groups.push([241this.instantiationService.createInstance(ShowServerOutputAction),242this.instantiationService.createInstance(ShowServerConfigurationAction),243this.instantiationService.createInstance(ShowServerJsonConfigurationAction),244]);245groups.push([246this.instantiationService.createInstance(ConfigureModelAccessAction),247this.instantiationService.createInstance(ShowSamplingRequestsAction),248]);249groups.push([250this.instantiationService.createInstance(BrowseResourcesAction),251]);252if (!this.isEditorAction) {253groups.push([254this.instantiationService.createInstance(UninstallAction),255]);256}257groups.forEach(group => group.forEach(extensionAction => {258if (extensionAction instanceof McpServerAction) {259extensionAction.mcpServer = this.mcpServer;260}261}));262263return groups;264}265266override async run(): Promise<any> {267return super.run(await this.getActionGroups());268}269270update(): void {271this.class = ManageMcpServerAction.HideManageExtensionClass;272this.enabled = false;273if (this.mcpServer) {274this.enabled = !!this.mcpServer.local;275this.class = this.enabled ? ManageMcpServerAction.Class : ManageMcpServerAction.HideManageExtensionClass;276}277}278}279280export class StartServerAction extends McpServerAction {281282static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent start`;283private static readonly HIDE = `${this.CLASS} hide`;284285constructor(286@IMcpService private readonly mcpService: IMcpService,287) {288super('extensions.start', localize('start', "Start Server"), StartServerAction.CLASS, false);289this.update();290}291292update(): void {293this.enabled = false;294this.class = StartServerAction.HIDE;295const server = this.getServer();296if (!server) {297return;298}299const serverState = server.connectionState.get();300if (!McpConnectionState.canBeStarted(serverState.state)) {301return;302}303this.class = StartServerAction.CLASS;304this.enabled = true;305this.label = localize('start', "Start Server");306}307308override async run(): Promise<any> {309const server = this.getServer();310if (!server) {311return;312}313await server.start({ promptType: 'all-untrusted' });314server.showOutput();315}316317private getServer(): IMcpServer | undefined {318if (!this.mcpServer) {319return;320}321if (!this.mcpServer.local) {322return;323}324return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);325}326}327328export class StopServerAction extends McpServerAction {329330static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent stop`;331private static readonly HIDE = `${this.CLASS} hide`;332333constructor(334@IMcpService private readonly mcpService: IMcpService,335) {336super('extensions.stop', localize('stop', "Stop Server"), StopServerAction.CLASS, false);337this.update();338}339340update(): void {341this.enabled = false;342this.class = StopServerAction.HIDE;343const server = this.getServer();344if (!server) {345return;346}347const serverState = server.connectionState.get();348if (McpConnectionState.canBeStarted(serverState.state)) {349return;350}351this.class = StopServerAction.CLASS;352this.enabled = true;353this.label = localize('stop', "Stop Server");354}355356override async run(): Promise<any> {357const server = this.getServer();358if (!server) {359return;360}361await server.stop();362}363364private getServer(): IMcpServer | undefined {365if (!this.mcpServer) {366return;367}368if (!this.mcpServer.local) {369return;370}371return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);372}373}374375export class RestartServerAction extends McpServerAction {376377static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent restart`;378private static readonly HIDE = `${this.CLASS} hide`;379380constructor(381@IMcpService private readonly mcpService: IMcpService,382) {383super('extensions.restart', localize('restart', "Restart Server"), RestartServerAction.CLASS, false);384this.update();385}386387update(): void {388this.enabled = false;389this.class = RestartServerAction.HIDE;390const server = this.getServer();391if (!server) {392return;393}394const serverState = server.connectionState.get();395if (McpConnectionState.canBeStarted(serverState.state)) {396return;397}398this.class = RestartServerAction.CLASS;399this.enabled = true;400this.label = localize('restart', "Restart Server");401}402403override async run(): Promise<any> {404const server = this.getServer();405if (!server) {406return;407}408await server.stop();409await server.start({ promptType: 'all-untrusted' });410server.showOutput();411}412413private getServer(): IMcpServer | undefined {414if (!this.mcpServer) {415return;416}417if (!this.mcpServer.local) {418return;419}420return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);421}422}423424export class AuthServerAction extends McpServerAction {425426static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent account`;427private static readonly HIDE = `${this.CLASS} hide`;428429private static readonly SIGN_OUT = localize('mcp.signOut', 'Sign Out');430private static readonly DISCONNECT = localize('mcp.disconnect', 'Disconnect Account');431432private _accountQuery: IAccountQuery | undefined;433434constructor(435@IMcpService private readonly mcpService: IMcpService,436@IAuthenticationQueryService private readonly _authenticationQueryService: IAuthenticationQueryService,437@IAuthenticationService private readonly _authenticationService: IAuthenticationService438) {439super('extensions.restart', localize('restart', "Restart Server"), RestartServerAction.CLASS, false);440this.update();441}442443update(): void {444this.enabled = false;445this.class = AuthServerAction.HIDE;446const server = this.getServer();447if (!server) {448return;449}450const accountQuery = this.getAccountQuery();451if (!accountQuery) {452return;453}454this._accountQuery = accountQuery;455this.class = AuthServerAction.CLASS;456this.enabled = true;457let label = accountQuery.entities().getEntityCount().total > 1 ? AuthServerAction.DISCONNECT : AuthServerAction.SIGN_OUT;458label += ` (${accountQuery.accountName})`;459this.label = label;460}461462override async run(): Promise<void> {463const server = this.getServer();464if (!server) {465return;466}467const accountQuery = this.getAccountQuery();468if (!accountQuery) {469return;470}471await server.stop();472const { providerId, accountName } = accountQuery;473accountQuery.mcpServer(server.definition.id).setAccessAllowed(false, server.definition.label);474if (this.label === AuthServerAction.SIGN_OUT) {475const accounts = await this._authenticationService.getAccounts(providerId);476const account = accounts.find(a => a.label === accountName);477if (account) {478const sessions = await this._authenticationService.getSessions(providerId, undefined, { account });479for (const session of sessions) {480await this._authenticationService.removeSession(providerId, session.id);481}482}483}484}485486private getServer(): IMcpServer | undefined {487if (!this.mcpServer) {488return;489}490if (!this.mcpServer.local) {491return;492}493return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);494}495496private getAccountQuery(): IAccountQuery | undefined {497const server = this.getServer();498if (!server) {499return undefined;500}501if (this._accountQuery) {502return this._accountQuery;503}504const serverId = server.definition.id;505const preferences = this._authenticationQueryService.mcpServer(serverId).getAllAccountPreferences();506if (!preferences.size) {507return undefined;508}509for (const [providerId, accountName] of preferences) {510const accountQuery = this._authenticationQueryService.provider(providerId).account(accountName);511if (!accountQuery.mcpServer(serverId).isAccessAllowed()) {512continue; // skip accounts that are not allowed513}514return accountQuery;515}516return undefined;517}518519}520521export class ShowServerOutputAction extends McpServerAction {522523static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent output`;524private static readonly HIDE = `${this.CLASS} hide`;525526constructor(527@IMcpService private readonly mcpService: IMcpService,528) {529super('extensions.output', localize('output', "Show Output"), ShowServerOutputAction.CLASS, false);530this.update();531}532533update(): void {534this.enabled = false;535this.class = ShowServerOutputAction.HIDE;536const server = this.getServer();537if (!server) {538return;539}540this.class = ShowServerOutputAction.CLASS;541this.enabled = true;542this.label = localize('output', "Show Output");543}544545override async run(): Promise<any> {546const server = this.getServer();547if (!server) {548return;549}550server.showOutput();551}552553private getServer(): IMcpServer | undefined {554if (!this.mcpServer) {555return;556}557if (!this.mcpServer.local) {558return;559}560return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);561}562}563564export class ShowServerConfigurationAction extends McpServerAction {565566static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent config`;567private static readonly HIDE = `${this.CLASS} hide`;568569constructor(570@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService571) {572super('extensions.config', localize('config', "Show Configuration"), ShowServerConfigurationAction.CLASS, false);573this.update();574}575576update(): void {577this.enabled = false;578this.class = ShowServerConfigurationAction.HIDE;579if (!this.mcpServer?.local) {580return;581}582this.class = ShowServerConfigurationAction.CLASS;583this.enabled = true;584}585586override async run(): Promise<any> {587if (!this.mcpServer?.local) {588return;589}590this.mcpWorkbenchService.open(this.mcpServer, { tab: McpServerEditorTab.Configuration });591}592593}594595export class ShowServerJsonConfigurationAction extends McpServerAction {596597static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent config`;598private static readonly HIDE = `${this.CLASS} hide`;599600constructor(601@IMcpService private readonly mcpService: IMcpService,602@IMcpRegistry private readonly mcpRegistry: IMcpRegistry,603@IEditorService private readonly editorService: IEditorService,604) {605super('extensions.jsonConfig', localize('configJson', "Show Configuration (JSON)"), ShowServerJsonConfigurationAction.CLASS, false);606this.update();607}608609update(): void {610this.enabled = false;611this.class = ShowServerJsonConfigurationAction.HIDE;612const configurationTarget = this.getConfigurationTarget();613if (!configurationTarget) {614return;615}616this.class = ShowServerConfigurationAction.CLASS;617this.enabled = true;618}619620override async run(): Promise<any> {621const configurationTarget = this.getConfigurationTarget();622if (!configurationTarget) {623return;624}625this.editorService.openEditor({626resource: URI.isUri(configurationTarget) ? configurationTarget : configurationTarget!.uri,627options: { selection: URI.isUri(configurationTarget) ? undefined : configurationTarget!.range }628});629}630631private getConfigurationTarget(): Location | URI | undefined {632if (!this.mcpServer) {633return;634}635if (!this.mcpServer.local) {636return;637}638const server = this.mcpService.servers.get().find(s => s.definition.label === this.mcpServer?.name);639if (!server) {640return;641}642const collection = this.mcpRegistry.collections.get().find(c => c.id === server.collection.id);643const serverDefinition = collection?.serverDefinitions.get().find(s => s.id === server.definition.id);644return serverDefinition?.presentation?.origin || collection?.presentation?.origin;645}646}647648export class ConfigureModelAccessAction extends McpServerAction {649650static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent config`;651private static readonly HIDE = `${this.CLASS} hide`;652653constructor(654@IMcpService private readonly mcpService: IMcpService,655@ICommandService private readonly commandService: ICommandService,656) {657super('extensions.config', localize('mcp.configAccess', 'Configure Model Access'), ConfigureModelAccessAction.CLASS, false);658this.update();659}660661update(): void {662this.enabled = false;663this.class = ConfigureModelAccessAction.HIDE;664const server = this.getServer();665if (!server) {666return;667}668this.class = ConfigureModelAccessAction.CLASS;669this.enabled = true;670this.label = localize('mcp.configAccess', 'Configure Model Access');671}672673override async run(): Promise<any> {674const server = this.getServer();675if (!server) {676return;677}678this.commandService.executeCommand(McpCommandIds.ConfigureSamplingModels, server);679}680681private getServer(): IMcpServer | undefined {682if (!this.mcpServer) {683return;684}685if (!this.mcpServer.local) {686return;687}688return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);689}690}691692export class ShowSamplingRequestsAction extends McpServerAction {693694static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent config`;695private static readonly HIDE = `${this.CLASS} hide`;696697constructor(698@IMcpService private readonly mcpService: IMcpService,699@IMcpSamplingService private readonly samplingService: IMcpSamplingService,700@IEditorService private readonly editorService: IEditorService,701) {702super('extensions.config', localize('mcp.samplingLog', 'Show Sampling Requests'), ShowSamplingRequestsAction.CLASS, false);703this.update();704}705706update(): void {707this.enabled = false;708this.class = ShowSamplingRequestsAction.HIDE;709const server = this.getServer();710if (!server) {711return;712}713if (!this.samplingService.hasLogs(server)) {714return;715}716this.class = ShowSamplingRequestsAction.CLASS;717this.enabled = true;718}719720override async run(): Promise<any> {721const server = this.getServer();722if (!server) {723return;724}725if (!this.samplingService.hasLogs(server)) {726return;727}728this.editorService.openEditor({729resource: undefined,730contents: this.samplingService.getLogText(server),731label: localize('mcp.samplingLog.title', 'MCP Sampling: {0}', server.definition.label),732});733}734735private getServer(): IMcpServer | undefined {736if (!this.mcpServer) {737return;738}739if (!this.mcpServer.local) {740return;741}742return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);743}744}745746export class BrowseResourcesAction extends McpServerAction {747748static readonly CLASS = `${this.LABEL_ACTION_CLASS} prominent config`;749private static readonly HIDE = `${this.CLASS} hide`;750751constructor(752@IMcpService private readonly mcpService: IMcpService,753@ICommandService private readonly commandService: ICommandService,754) {755super('extensions.config', localize('mcp.resources', 'Browse Resources'), BrowseResourcesAction.CLASS, false);756this.update();757}758759update(): void {760this.enabled = false;761this.class = BrowseResourcesAction.HIDE;762const server = this.getServer();763if (!server) {764return;765}766const capabilities = server.capabilities.get();767if (capabilities !== undefined && !(capabilities & McpCapability.Resources)) {768return;769}770this.class = BrowseResourcesAction.CLASS;771this.enabled = true;772}773774override async run(): Promise<any> {775const server = this.getServer();776if (!server) {777return;778}779const capabilities = server.capabilities.get();780if (capabilities !== undefined && !(capabilities & McpCapability.Resources)) {781return;782}783return this.commandService.executeCommand(McpCommandIds.BrowseResources, server);784}785786private getServer(): IMcpServer | undefined {787if (!this.mcpServer) {788return;789}790if (!this.mcpServer.local) {791return;792}793return this.mcpService.servers.get().find(s => s.definition.id === this.mcpServer?.id);794}795}796797export type McpServerStatus = { readonly message: IMarkdownString; readonly icon?: ThemeIcon };798799export class McpServerStatusAction extends McpServerAction {800801private static readonly CLASS = `${McpServerAction.ICON_ACTION_CLASS} extension-status`;802803private _status: McpServerStatus[] = [];804get status(): McpServerStatus[] { return this._status; }805806private readonly _onDidChangeStatus = this._register(new Emitter<void>());807readonly onDidChangeStatus = this._onDidChangeStatus.event;808809constructor(810@IMcpWorkbenchService private readonly mcpWorkbenchService: IMcpWorkbenchService,811@ICommandService private readonly commandService: ICommandService,812@IConfigurationService private readonly configurationService: IConfigurationService,813) {814super('extensions.status', '', `${McpServerStatusAction.CLASS} hide`, false);815this.update();816}817818update(): void {819this.computeAndUpdateStatus();820}821822private computeAndUpdateStatus(): void {823this.updateStatus(undefined, true);824this.enabled = false;825826if (!this.mcpServer) {827return;828}829830if ((this.mcpServer.gallery || this.mcpServer.installable) && this.mcpServer.installState === McpServerInstallState.Uninstalled) {831const result = this.mcpWorkbenchService.canInstall(this.mcpServer);832if (result !== true) {833this.updateStatus({ icon: warningIcon, message: result }, true);834return;835}836}837838if (this.mcpServer.local && this.mcpServer.installState === McpServerInstallState.Installed && this.mcpServer.enablementState === McpServerEnablementState.DisabledByAccess) {839const settingsCommandLink = URI.parse(`command:workbench.action.openSettings?${encodeURIComponent(JSON.stringify({ query: `@id:${mcpAccessConfig}` }))}`).toString();840if (this.configurationService.getValue(mcpAccessConfig) === McpAccessValue.None) {841this.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);842} else {843this.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);844}845return;846}847}848849private updateStatus(status: McpServerStatus | undefined, updateClass: boolean): void {850if (status) {851if (this._status.some(s => s.message.value === status.message.value && s.icon?.id === status.icon?.id)) {852return;853}854} else {855if (this._status.length === 0) {856return;857}858this._status = [];859}860861if (status) {862this._status.push(status);863this._status.sort((a, b) =>864b.icon === trustIcon ? -1 :865a.icon === trustIcon ? 1 :866b.icon === errorIcon ? -1 :867a.icon === errorIcon ? 1 :868b.icon === warningIcon ? -1 :869a.icon === warningIcon ? 1 :870b.icon === infoIcon ? -1 :871a.icon === infoIcon ? 1 :8720873);874}875876if (updateClass) {877if (status?.icon === errorIcon) {878this.class = `${McpServerStatusAction.CLASS} extension-status-error ${ThemeIcon.asClassName(errorIcon)}`;879}880else if (status?.icon === warningIcon) {881this.class = `${McpServerStatusAction.CLASS} extension-status-warning ${ThemeIcon.asClassName(warningIcon)}`;882}883else if (status?.icon === infoIcon) {884this.class = `${McpServerStatusAction.CLASS} extension-status-info ${ThemeIcon.asClassName(infoIcon)}`;885}886else if (status?.icon === trustIcon) {887this.class = `${McpServerStatusAction.CLASS} ${ThemeIcon.asClassName(trustIcon)}`;888}889else {890this.class = `${McpServerStatusAction.CLASS} hide`;891}892}893this._onDidChangeStatus.fire();894}895896override async run(): Promise<any> {897if (this._status[0]?.icon === trustIcon) {898return this.commandService.executeCommand('workbench.trust.manage');899}900}901}902903904