Path: blob/main/src/vs/workbench/contrib/mcp/browser/mcpAddContextContribution.ts
5262 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CancellationToken } from '../../../../base/common/cancellation.js';6import { Codicon } from '../../../../base/common/codicons.js';7import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js';8import { autorun, derived } from '../../../../base/common/observable.js';9import { localize } from '../../../../nls.js';10import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';11import { IWorkbenchContribution } from '../../../common/contributions.js';12import { ChatContextPick, IChatContextPickService } from '../../chat/browser/attachments/chatContextPickService.js';13import { IMcpService, McpCapability } from '../common/mcpTypes.js';14import { McpResourcePickHelper } from './mcpResourceQuickAccess.js';1516export class McpAddContextContribution extends Disposable implements IWorkbenchContribution {17private readonly _addContextMenu = this._register(new MutableDisposable());18constructor(19@IChatContextPickService private readonly _chatContextPickService: IChatContextPickService,20@IInstantiationService private readonly _instantiationService: IInstantiationService,21@IMcpService mcpService: IMcpService22) {23super();2425const hasServersWithResources = derived(reader => {26let enabled = false;27for (const server of mcpService.servers.read(reader)) {28const cap = server.capabilities.read(undefined);29if (cap === undefined) {30enabled = true; // until we know more31} else if (cap & McpCapability.Resources) {32enabled = true;33break;34}35}3637return enabled;38});3940this._register(autorun(reader => {41const enabled = hasServersWithResources.read(reader);42if (enabled && !this._addContextMenu.value) {43this._registerAddContextMenu();44} else {45this._addContextMenu.clear();46}47}));48}4950private _registerAddContextMenu() {51this._addContextMenu.value = this._chatContextPickService.registerChatContextItem({52type: 'pickerPick',53label: localize('mcp.addContext', "MCP Resources..."),54icon: Codicon.mcp,55isEnabled(widget) {56return !!widget.attachmentCapabilities.supportsMCPAttachments;57},58asPicker: () => {59const helper = this._instantiationService.createInstance(McpResourcePickHelper);60return {61placeholder: localize('mcp.addContext.placeholder', "Select MCP Resource..."),62picks: (_query, token) => this._getResourcePicks(token, helper),63goBack: () => {64return helper.navigateBack();65},66dispose: () => {67helper.dispose();68}69};70},71});72}7374private _getResourcePicks(token: CancellationToken, helper: McpResourcePickHelper) {75const picksObservable = helper.getPicks(token);7677return derived(this, reader => {7879const pickItems = picksObservable.read(reader);80const picks: ChatContextPick[] = [];8182for (const [server, resources] of pickItems.picks) {83if (resources.length === 0) {84continue;85}86picks.push(McpResourcePickHelper.sep(server));87for (const resource of resources) {88picks.push({89...McpResourcePickHelper.item(resource),90asAttachment: () => helper.toAttachment(resource, server)91});92}93}94return { picks, busy: pickItems.isBusy };95});96}97}9899100