@@ -18,10 +18,13 @@ import { ICommandService } from '../../../../../platform/commands/common/command
18
18
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
19
19
import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js';
20
20
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
21
- import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../../platform/quickinput/common/quickInput.js';
21
+ import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
22
+ import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSeparator } from '../../../../../platform/quickinput/common/quickInput.js';
22
23
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
24
+ import { IEditorService } from '../../../../services/editor/common/editorService.js';
23
25
import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js';
24
26
import { AddConfigurationAction } from '../../../mcp/browser/mcpCommands.js';
27
+ import { IMcpRegistry } from '../../../mcp/common/mcpRegistryTypes.js';
25
28
import { IMcpService, IMcpServer, McpConnectionState } from '../../../mcp/common/mcpTypes.js';
26
29
import { ChatContextKeys } from '../../common/chatContextKeys.js';
27
30
import { IChatToolInvocation } from '../../common/chatService.js';
@@ -109,11 +112,13 @@ export class AttachToolsAction extends Action2 {
109
112
110
113
const quickPickService = accessor.get(IQuickInputService);
111
114
const mcpService = accessor.get(IMcpService);
115
+ const mcpRegistry = accessor.get(IMcpRegistry);
112
116
const toolsService = accessor.get(ILanguageModelToolsService);
113
117
const chatWidgetService = accessor.get(IChatWidgetService);
114
118
const telemetryService = accessor.get(ITelemetryService);
115
119
const commandService = accessor.get(ICommandService);
116
120
const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService);
121
+ const editorService = accessor.get(IEditorService);
117
122
118
123
let widget = chatWidgetService.lastFocusedWidget;
119
124
if (!widget) {
@@ -143,6 +148,7 @@ export class AttachToolsAction extends Action2 {
143
148
type ToolPick = IQuickPickItem & { picked: boolean; tool: IToolData; parent: BucketPick };
144
149
type AddPick = IQuickPickItem & { pickable: false; run: () => void };
145
150
type MyPick = ToolPick | BucketPick | AddPick;
151
+ type ActionableButton = IQuickInputButton & { action: () => void };
146
152
147
153
const addMcpPick: AddPick = { type: 'item', label: localize('addServer', "Add MCP Server..."), iconClass: ThemeIcon.asClassName(Codicon.add), pickable: false, run: () => commandService.executeCommand(AddConfigurationAction.ID) };
148
154
const addExpPick: AddPick = { type: 'item', label: localize('addExtension', "Install Extension..."), iconClass: ThemeIcon.asClassName(Codicon.add), pickable: false, run: () => extensionWorkbenchService.openSearch('@tag:language-model-tools') };
@@ -184,16 +190,44 @@ export class AttachToolsAction extends Action2 {
184
190
continue;
185
191
}
186
192
const key = tool.source.type + mcpServer.definition.id;
187
- bucket = toolBuckets.get(key) ?? {
188
- type: 'item',
189
- label: localize('mcplabel', "MCP Server: {0}", mcpServer?.definition.label),
190
- status: localize('mcpstatus', "From {0} ({1})", mcpServer.collection.label, McpConnectionState.toString(mcpServer.connectionState.get())),
191
- ordinal: BucketOrdinal.Mcp,
192
- source: tool.source,
193
- picked: false,
194
- children: []
195
- };
196
- toolBuckets.set(key, bucket);
193
+ const existingBucket = toolBuckets.get(key);
194
+ if (existingBucket) {
195
+ bucket = existingBucket;
196
+ } else {
197
+
198
+ const collection = mcpRegistry.collections.get().find(c => c.id === mcpServer.collection.id);
199
+ const buttons: ActionableButton[] = [];
200
+ if (collection?.presentation?.origin) {
201
+ buttons.push({
202
+ iconClass: ThemeIcon.asClassName(Codicon.settingsGear),
203
+ tooltip: localize('configMcpCol', "Configure {0}", collection.label),
204
+ alwaysVisible: true,
205
+ action: () => editorService.openEditor({
206
+ resource: collection!.presentation!.origin,
207
+ }),
208
+ });
209
+ }
210
+ if (mcpServer.connectionState.get().state === McpConnectionState.Kind.Error) {
211
+ buttons.push({
212
+ iconClass: ThemeIcon.asClassName(Codicon.warning),
213
+ tooltip: localize('mcpShowOutput', "Show Output"),
214
+ alwaysVisible: true,
215
+ action: () => mcpServer.showOutput(),
216
+ });
217
+ }
218
+
219
+ bucket = {
220
+ type: 'item',
221
+ label: localize('mcplabel', "MCP Server: {0}", mcpServer?.definition.label),
222
+ status: localize('mcpstatus', "from {0}", mcpServer.collection.label),
223
+ ordinal: BucketOrdinal.Mcp,
224
+ source: tool.source,
225
+ picked: false,
226
+ children: [],
227
+ buttons,
228
+ };
229
+ toolBuckets.set(key, bucket);
230
+ }
197
231
} else if (tool.source.type === 'extension') {
198
232
const key = tool.source.type + ExtensionIdentifier.toKey(tool.source.extensionId);
199
233
@@ -238,6 +272,9 @@ export class AttachToolsAction extends Action2 {
238
272
function isAddPick(obj: any): obj is AddPick {
239
273
return Boolean((obj as AddPick).run);
240
274
}
275
+ function isActionableButton(obj: IQuickInputButton): obj is ActionableButton {
276
+ return typeof (obj as ActionableButton).action === 'function';
277
+ }
241
278
242
279
const store = new DisposableStore();
243
280
@@ -306,6 +343,13 @@ export class AttachToolsAction extends Action2 {
306
343
picker.items = picks;
307
344
picker.show();
308
345
346
+ store.add(picker.onDidTriggerItemButton(e => {
347
+ if (isActionableButton(e.button)) {
348
+ e.button.action();
349
+ store.dispose();
350
+ }
351
+ }));
352
+
309
353
store.add(picker.onDidChangeSelection(selectedPicks => {
310
354
if (ignoreEvent) {
311
355
return;
0 commit comments