mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-24 00:05:46 +01:00
mcp: add more data in codelens, clean up options pick (#250341)
Closes https://github.com/microsoft/vscode/issues/250126
This commit is contained in:
@@ -213,7 +213,7 @@ class SlashCommandCompletions extends Disposable {
|
||||
return null;
|
||||
}
|
||||
|
||||
const range = computeCompletionRanges(model, position, /\/\w*/g);
|
||||
const range = computeCompletionRanges(model, position, /\/[\w.]*/g);
|
||||
if (!range) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ import { McpSamplingService } from '../common/mcpSamplingService.js';
|
||||
import { McpService } from '../common/mcpService.js';
|
||||
import { HasInstalledMcpServersContext, IMcpSamplingService, IMcpService, IMcpWorkbenchService, InstalledMcpServersViewId, McpServersGalleryEnabledContext } from '../common/mcpTypes.js';
|
||||
import { McpAddContextContribution } from './mcpAddContextContribution.js';
|
||||
import { AddConfigurationAction, EditStoredInput, InstallFromActivation, ListMcpServerCommand, McpBrowseCommand, McpBrowseResourcesCommand, McpConfigureSamplingModels, MCPServerActionRendering, McpServerOptionsCommand, RemoveStoredInput, ResetMcpCachedTools, ResetMcpTrustCommand, RestartServer, ShowConfiguration, ShowOutput, StartServer, StopServer } from './mcpCommands.js';
|
||||
import { AddConfigurationAction, EditStoredInput, InstallFromActivation, ListMcpServerCommand, McpBrowseCommand, McpBrowseResourcesCommand, McpConfigureSamplingModels, MCPServerActionRendering, McpServerOptionsCommand, McpStartPromptingServerCommand, RemoveStoredInput, ResetMcpCachedTools, ResetMcpTrustCommand, RestartServer, ShowConfiguration, ShowOutput, StartServer, StopServer } from './mcpCommands.js';
|
||||
import { McpDiscovery } from './mcpDiscovery.js';
|
||||
import { McpLanguageFeatures } from './mcpLanguageFeatures.js';
|
||||
import { McpResourceQuickAccess } from './mcpResourceQuickAccess.js';
|
||||
@@ -79,6 +79,7 @@ registerAction2(ShowConfiguration);
|
||||
registerAction2(McpBrowseCommand);
|
||||
registerAction2(McpBrowseResourcesCommand);
|
||||
registerAction2(McpConfigureSamplingModels);
|
||||
registerAction2(McpStartPromptingServerCommand);
|
||||
|
||||
registerWorkbenchContribution2('mcpActionRendering', MCPServerActionRendering, WorkbenchPhase.BlockRestore);
|
||||
registerWorkbenchContribution2('mcpAddContext', McpAddContextContribution, WorkbenchPhase.Eventually);
|
||||
|
||||
@@ -13,6 +13,8 @@ import { autorun, derived } from '../../../../base/common/observable.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
import { isDefined } from '../../../../base/common/types.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { Range } from '../../../../editor/common/core/range.js';
|
||||
import { SuggestController } from '../../../../editor/contrib/suggest/browser/suggestController.js';
|
||||
import { ILocalizedString, localize, localize2 } from '../../../../nls.js';
|
||||
import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js';
|
||||
import { MenuEntryActionViewItem } from '../../../../platform/actions/browser/menuEntryActionViewItem.js';
|
||||
@@ -31,6 +33,7 @@ import { ActiveEditorContext, ResourceContextKey } from '../../../common/context
|
||||
import { IWorkbenchContribution } from '../../../common/contributions.js';
|
||||
import { IEditorService } from '../../../services/editor/common/editorService.js';
|
||||
import { IViewsService } from '../../../services/views/common/viewsService.js';
|
||||
import { ChatViewId, IChatWidgetService } from '../../chat/browser/chat.js';
|
||||
import { ChatContextKeys } from '../../chat/common/chatContextKeys.js';
|
||||
import { ChatMode } from '../../chat/common/constants.js';
|
||||
import { ILanguageModelsService } from '../../chat/common/languageModels.js';
|
||||
@@ -39,7 +42,7 @@ import { TEXT_FILE_EDITOR_ID } from '../../files/common/files.js';
|
||||
import { McpCommandIds } from '../common/mcpCommandIds.js';
|
||||
import { McpContextKeys } from '../common/mcpContextKeys.js';
|
||||
import { IMcpRegistry } from '../common/mcpRegistryTypes.js';
|
||||
import { IMcpSamplingService, IMcpServer, IMcpServerStartOpts, IMcpService, IMcpWorkbenchService, InstalledMcpServersViewId, LazyCollectionState, McpCapability, McpConnectionState, McpServerCacheState, McpServersGalleryEnabledContext } from '../common/mcpTypes.js';
|
||||
import { IMcpSamplingService, IMcpServer, IMcpServerStartOpts, IMcpService, IMcpWorkbenchService, InstalledMcpServersViewId, LazyCollectionState, McpCapability, McpConnectionState, mcpPromptPrefix, McpServerCacheState, McpServersGalleryEnabledContext } from '../common/mcpTypes.js';
|
||||
import { McpAddConfigurationCommand } from './mcpCommandsAddConfiguration.js';
|
||||
import { McpResourceQuickAccess, McpResourceQuickPick } from './mcpResourceQuickAccess.js';
|
||||
import { McpUrlHandler } from './mcpUrlHandler.js';
|
||||
@@ -155,7 +158,6 @@ export class McpServerOptionsCommand extends Action2 {
|
||||
const mcpRegistry = accessor.get(IMcpRegistry);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const instantiationService = accessor.get(IInstantiationService);
|
||||
const samplingService = accessor.get(IMcpSamplingService);
|
||||
const server = mcpService.servers.get().find(s => s.definition.id === id);
|
||||
if (!server) {
|
||||
@@ -169,9 +171,11 @@ export class McpServerOptionsCommand extends Action2 {
|
||||
action: 'start' | 'stop' | 'restart' | 'showOutput' | 'config' | 'configSampling' | 'samplingLog' | 'resources';
|
||||
}
|
||||
|
||||
const items: ActionItem[] = [];
|
||||
const items: (ActionItem | IQuickPickSeparator)[] = [];
|
||||
const serverState = server.connectionState.get();
|
||||
|
||||
items.push({ type: 'separator', label: localize('mcp.actions.status', 'Status') });
|
||||
|
||||
// Only show start when server is stopped or in error state
|
||||
if (McpConnectionState.canBeStarted(serverState.state)) {
|
||||
items.push({
|
||||
@@ -189,15 +193,21 @@ export class McpServerOptionsCommand extends Action2 {
|
||||
});
|
||||
}
|
||||
|
||||
const configTarget = serverDefinition?.presentation?.origin || collection?.presentation?.origin;
|
||||
if (configTarget) {
|
||||
items.push({
|
||||
label: localize('mcp.config', 'Show Configuration'),
|
||||
action: 'config',
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: localize('mcp.showOutput', 'Show Output'),
|
||||
action: 'showOutput'
|
||||
}, {
|
||||
label: localize('mcp.configAccess', 'Configure Model Access'),
|
||||
description: localize('mcp.showOutput.description', 'Set the models the server can use via MCP sampling'),
|
||||
action: 'configSampling'
|
||||
});
|
||||
|
||||
items.push({ type: 'separator', label: localize('mcp.actions.sampling', 'Sampling') });
|
||||
|
||||
if (samplingService.hasLogs(server)) {
|
||||
items.push({
|
||||
label: localize('mcp.samplingLog', 'Show Sampling Requests'),
|
||||
@@ -208,20 +218,13 @@ export class McpServerOptionsCommand extends Action2 {
|
||||
|
||||
const capabilities = server.capabilities.get();
|
||||
if (capabilities === undefined || (capabilities & McpCapability.Resources)) {
|
||||
items.push({ type: 'separator', label: localize('mcp.actions.resources', 'Resources') });
|
||||
items.push({
|
||||
label: localize('mcp.resources', 'Browse Resources'),
|
||||
action: 'resources',
|
||||
});
|
||||
}
|
||||
|
||||
const configTarget = serverDefinition?.presentation?.origin || collection?.presentation?.origin;
|
||||
if (configTarget) {
|
||||
items.push({
|
||||
label: localize('mcp.config', 'Show Configuration'),
|
||||
action: 'config',
|
||||
});
|
||||
}
|
||||
|
||||
const pick = await quickInputService.pick(items, {
|
||||
title: server.definition.label,
|
||||
placeHolder: localize('mcp.selectAction', 'Select Server Action')
|
||||
@@ -255,7 +258,7 @@ export class McpServerOptionsCommand extends Action2 {
|
||||
case 'configSampling':
|
||||
return commandService.executeCommand(McpCommandIds.ConfigureSamplingModels, server);
|
||||
case 'resources':
|
||||
return instantiationService.createInstance(McpResourceQuickPick, server).pick();
|
||||
return commandService.executeCommand(McpCommandIds.BrowseResources, server);
|
||||
case 'samplingLog':
|
||||
editorService.openEditor({
|
||||
resource: undefined,
|
||||
@@ -651,12 +654,15 @@ export class McpBrowseResourcesCommand extends Action2 {
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor): void {
|
||||
accessor.get(IQuickInputService).quickAccess.show(McpResourceQuickAccess.PREFIX);
|
||||
run(accessor: ServicesAccessor, server?: IMcpServer): void {
|
||||
if (server) {
|
||||
accessor.get(IInstantiationService).createInstance(McpResourceQuickPick, server).pick();
|
||||
} else {
|
||||
accessor.get(IQuickInputService).quickAccess.show(McpResourceQuickAccess.PREFIX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class McpConfigureSamplingModels extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
@@ -692,3 +698,38 @@ export class McpConfigureSamplingModels extends Action2 {
|
||||
return picked?.length || 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class McpStartPromptingServerCommand extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: McpCommandIds.StartPromptForServer,
|
||||
title: localize2('mcp.startPromptingServer', "Start Prompting Server"),
|
||||
category,
|
||||
f1: false,
|
||||
});
|
||||
}
|
||||
|
||||
async run(accessor: ServicesAccessor, server: IMcpServer): Promise<void> {
|
||||
const chatWidget = accessor.get(IChatWidgetService);
|
||||
await accessor.get(IViewsService).openView(ChatViewId, true);
|
||||
|
||||
const widget = chatWidget.lastFocusedWidget || chatWidget.getAllWidgets()[0];
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editor = widget.inputEditor;
|
||||
const model = editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const range = (editor.getSelection() || model.getFullModelRange()).collapseToEnd();
|
||||
const text = mcpPromptPrefix(server.definition) + '.';
|
||||
|
||||
model.applyEdits([{ range, text }]);
|
||||
editor.setSelection(Range.fromPositions(range.getEndPosition().delta(0, text.length)));
|
||||
widget.focusInput();
|
||||
SuggestController.get(editor)?.triggerSuggest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +178,8 @@ export class McpLanguageFeatures extends Disposable implements IWorkbenchContrib
|
||||
|
||||
const range = Range.fromPositions(model.getPositionAt(node.children[0].offset));
|
||||
const canDebug = !!server.readDefinitions().get().server?.devMode?.debug;
|
||||
switch (read(server.connectionState).state) {
|
||||
const state = read(server.connectionState).state;
|
||||
switch (state) {
|
||||
case McpConnectionState.Kind.Error:
|
||||
lenses.lenses.push({
|
||||
range,
|
||||
@@ -256,15 +257,8 @@ export class McpLanguageFeatures extends Disposable implements IWorkbenchContrib
|
||||
},
|
||||
});
|
||||
}
|
||||
lenses.lenses.push({
|
||||
range,
|
||||
command: {
|
||||
id: '',
|
||||
title: localize('server.toolCount', '{0} tools', read(server.tools).length),
|
||||
},
|
||||
});
|
||||
break;
|
||||
case McpConnectionState.Kind.Stopped: {
|
||||
case McpConnectionState.Kind.Stopped:
|
||||
lenses.lenses.push({
|
||||
range,
|
||||
command: {
|
||||
@@ -283,17 +277,42 @@ export class McpLanguageFeatures extends Disposable implements IWorkbenchContrib
|
||||
},
|
||||
});
|
||||
}
|
||||
const toolCount = read(server.tools).length;
|
||||
if (toolCount) {
|
||||
lenses.lenses.push({
|
||||
range,
|
||||
command: {
|
||||
id: '',
|
||||
title: localize('server.toolCountCached', '{0} cached tools', toolCount),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (state !== McpConnectionState.Kind.Error) {
|
||||
const toolCount = read(server.tools).length;
|
||||
if (toolCount) {
|
||||
lenses.lenses.push({
|
||||
range,
|
||||
command: {
|
||||
id: '',
|
||||
title: localize('server.toolCount', '{0} tools', toolCount),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const promptCount = read(server.prompts).length;
|
||||
if (promptCount) {
|
||||
lenses.lenses.push({
|
||||
range,
|
||||
command: {
|
||||
id: McpCommandIds.StartPromptForServer,
|
||||
title: localize('server.promptcount', '{0} prompts', promptCount),
|
||||
arguments: [server],
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
lenses.lenses.push({
|
||||
range,
|
||||
command: {
|
||||
id: McpCommandIds.ServerOptions,
|
||||
title: localize('mcp.server.more', 'More...'),
|
||||
arguments: [server.definition.id],
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ export const enum McpCommandIds {
|
||||
ServerOptions = 'workbench.mcp.serverOptions',
|
||||
ShowConfiguration = 'workbench.mcp.showConfiguration',
|
||||
ShowOutput = 'workbench.mcp.showOutput',
|
||||
StartPromptForServer = 'workbench.mcp.startPromptForServer',
|
||||
StartServer = 'workbench.mcp.startServer',
|
||||
StopServer = 'workbench.mcp.stopServer',
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import { mcpActivationEvent } from './mcpConfiguration.js';
|
||||
import { McpDevModeServerAttache } from './mcpDevMode.js';
|
||||
import { IMcpRegistry } from './mcpRegistryTypes.js';
|
||||
import { McpServerRequestHandler } from './mcpServerRequestHandler.js';
|
||||
import { extensionMcpCollectionPrefix, IMcpPrompt, IMcpPromptMessage, IMcpResource, IMcpResourceTemplate, IMcpSamplingService, IMcpServer, IMcpServerConnection, IMcpServerStartOpts, IMcpTool, McpCapability, McpCollectionDefinition, McpCollectionReference, McpConnectionFailedError, McpConnectionState, McpDefinitionReference, McpResourceURI, McpServerCacheState, McpServerDefinition, McpServerTransportType, McpToolName } from './mcpTypes.js';
|
||||
import { extensionMcpCollectionPrefix, IMcpPrompt, IMcpPromptMessage, IMcpResource, IMcpResourceTemplate, IMcpSamplingService, IMcpServer, IMcpServerConnection, IMcpServerStartOpts, IMcpTool, McpCapability, McpCollectionDefinition, McpCollectionReference, McpConnectionFailedError, McpConnectionState, McpDefinitionReference, mcpPromptReplaceSpecialChars, McpResourceURI, McpServerCacheState, McpServerDefinition, McpServerTransportType, McpToolName } from './mcpTypes.js';
|
||||
import { MCP } from './modelContextProtocol.js';
|
||||
import { UriTemplate } from './uriTemplate.js';
|
||||
|
||||
@@ -657,7 +657,7 @@ class McpPrompt implements IMcpPrompt {
|
||||
private readonly _server: McpServer,
|
||||
private readonly _definition: MCP.Prompt,
|
||||
) {
|
||||
this.id = (this._server.definition.label + '.' + _definition.name).replace(/[^a-z0-9_.-]/gi, '_');
|
||||
this.id = mcpPromptReplaceSpecialChars(this._server.definition.label + '.' + _definition.name);
|
||||
this.name = _definition.name;
|
||||
this.description = _definition.description;
|
||||
this.arguments = _definition.arguments || [];
|
||||
|
||||
@@ -335,6 +335,11 @@ export interface IMcpPrompt {
|
||||
resolve(args: Record<string, string | undefined>, token?: CancellationToken): Promise<IMcpPromptMessage[]>;
|
||||
}
|
||||
|
||||
export const mcpPromptReplaceSpecialChars = (s: string) => s.replace(/[^a-z0-9_.-]/gi, '_');
|
||||
|
||||
export const mcpPromptPrefix = (definition: McpDefinitionReference) =>
|
||||
`/mcp.` + mcpPromptReplaceSpecialChars(definition.label);
|
||||
|
||||
export interface IMcpPromptMessage extends MCP.PromptMessage { }
|
||||
|
||||
export interface IMcpTool {
|
||||
|
||||
Reference in New Issue
Block a user