Skip to content

Commit 8c4f33b

Browse files
committed
mcp: apply tool picker UX improvements
Applies the suggestions from #245721 - Adjusts 'From' to 'from' - Remove status wording - Add item buttons for configuration. I also show a button to view the MCP output when it's in a failed state, since I think showing that is important.
1 parent dcfa9b9 commit 8c4f33b

File tree

1 file changed

+55
-11
lines changed

1 file changed

+55
-11
lines changed

src/vs/workbench/contrib/chat/browser/actions/chatToolActions.ts

+55-11
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@ import { ICommandService } from '../../../../../platform/commands/common/command
1818
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
1919
import { ExtensionIdentifier } from '../../../../../platform/extensions/common/extensions.js';
2020
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';
2223
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
24+
import { IEditorService } from '../../../../services/editor/common/editorService.js';
2325
import { IExtensionsWorkbenchService } from '../../../extensions/common/extensions.js';
2426
import { AddConfigurationAction } from '../../../mcp/browser/mcpCommands.js';
27+
import { IMcpRegistry } from '../../../mcp/common/mcpRegistryTypes.js';
2528
import { IMcpService, IMcpServer, McpConnectionState } from '../../../mcp/common/mcpTypes.js';
2629
import { ChatContextKeys } from '../../common/chatContextKeys.js';
2730
import { IChatToolInvocation } from '../../common/chatService.js';
@@ -109,11 +112,13 @@ export class AttachToolsAction extends Action2 {
109112

110113
const quickPickService = accessor.get(IQuickInputService);
111114
const mcpService = accessor.get(IMcpService);
115+
const mcpRegistry = accessor.get(IMcpRegistry);
112116
const toolsService = accessor.get(ILanguageModelToolsService);
113117
const chatWidgetService = accessor.get(IChatWidgetService);
114118
const telemetryService = accessor.get(ITelemetryService);
115119
const commandService = accessor.get(ICommandService);
116120
const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService);
121+
const editorService = accessor.get(IEditorService);
117122

118123
let widget = chatWidgetService.lastFocusedWidget;
119124
if (!widget) {
@@ -143,6 +148,7 @@ export class AttachToolsAction extends Action2 {
143148
type ToolPick = IQuickPickItem & { picked: boolean; tool: IToolData; parent: BucketPick };
144149
type AddPick = IQuickPickItem & { pickable: false; run: () => void };
145150
type MyPick = ToolPick | BucketPick | AddPick;
151+
type ActionableButton = IQuickInputButton & { action: () => void };
146152

147153
const addMcpPick: AddPick = { type: 'item', label: localize('addServer', "Add MCP Server..."), iconClass: ThemeIcon.asClassName(Codicon.add), pickable: false, run: () => commandService.executeCommand(AddConfigurationAction.ID) };
148154
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 {
184190
continue;
185191
}
186192
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+
}
197231
} else if (tool.source.type === 'extension') {
198232
const key = tool.source.type + ExtensionIdentifier.toKey(tool.source.extensionId);
199233

@@ -238,6 +272,9 @@ export class AttachToolsAction extends Action2 {
238272
function isAddPick(obj: any): obj is AddPick {
239273
return Boolean((obj as AddPick).run);
240274
}
275+
function isActionableButton(obj: IQuickInputButton): obj is ActionableButton {
276+
return typeof (obj as ActionableButton).action === 'function';
277+
}
241278

242279
const store = new DisposableStore();
243280

@@ -306,6 +343,13 @@ export class AttachToolsAction extends Action2 {
306343
picker.items = picks;
307344
picker.show();
308345

346+
store.add(picker.onDidTriggerItemButton(e => {
347+
if (isActionableButton(e.button)) {
348+
e.button.action();
349+
store.dispose();
350+
}
351+
}));
352+
309353
store.add(picker.onDidChangeSelection(selectedPicks => {
310354
if (ignoreEvent) {
311355
return;

0 commit comments

Comments
 (0)