Path: blob/main/src/vs/workbench/contrib/mcp/common/mcpConfiguration.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 { MarkdownString } from '../../../../base/common/htmlContent.js';6import { IJSONSchema, IJSONSchemaMap } from '../../../../base/common/jsonSchema.js';7import { Disposable } from '../../../../base/common/lifecycle.js';8import { localize } from '../../../../nls.js';9import { IExtensionManifest, IMcpCollectionContribution } from '../../../../platform/extensions/common/extensions.js';10import { SyncDescriptor } from '../../../../platform/instantiation/common/descriptors.js';11import { Registry } from '../../../../platform/registry/common/platform.js';12import { mcpSchemaId } from '../../../services/configuration/common/configuration.js';13import { inputsSchema } from '../../../services/configurationResolver/common/configurationResolverSchema.js';14import { Extensions, IExtensionFeaturesRegistry, IExtensionFeatureTableRenderer, IRenderedData, IRowData, ITableData } from '../../../services/extensionManagement/common/extensionFeatures.js';15import { IExtensionPointDescriptor } from '../../../services/extensions/common/extensionsRegistry.js';1617const mcpActivationEventPrefix = 'onMcpCollection:';1819/**20* note: `contributedCollectionId` is _not_ the collection ID. The collection21* ID is formed by passing the contributed ID through `extensionPrefixedIdentifier`22*/23export const mcpActivationEvent = (contributedCollectionId: string) =>24mcpActivationEventPrefix + contributedCollectionId;2526export const enum DiscoverySource {27ClaudeDesktop = 'claude-desktop',28Windsurf = 'windsurf',29CursorGlobal = 'cursor-global',30CursorWorkspace = 'cursor-workspace',31}3233export const allDiscoverySources = Object.keys({34[DiscoverySource.ClaudeDesktop]: true,35[DiscoverySource.Windsurf]: true,36[DiscoverySource.CursorGlobal]: true,37[DiscoverySource.CursorWorkspace]: true,38} satisfies Record<DiscoverySource, true>) as DiscoverySource[];3940export const discoverySourceLabel: Record<DiscoverySource, string> = {41[DiscoverySource.ClaudeDesktop]: localize('mcp.discovery.source.claude-desktop', "Claude Desktop"),42[DiscoverySource.Windsurf]: localize('mcp.discovery.source.windsurf', "Windsurf"),43[DiscoverySource.CursorGlobal]: localize('mcp.discovery.source.cursor-global', "Cursor (Global)"),44[DiscoverySource.CursorWorkspace]: localize('mcp.discovery.source.cursor-workspace', "Cursor (Workspace)"),45};46export const discoverySourceSettingsLabel: Record<DiscoverySource, string> = {47[DiscoverySource.ClaudeDesktop]: localize('mcp.discovery.source.claude-desktop.config', "Claude Desktop configuration (`claude_desktop_config.json`)"),48[DiscoverySource.Windsurf]: localize('mcp.discovery.source.windsurf.config', "Windsurf configurations (`~/.codeium/windsurf/mcp_config.json`)"),49[DiscoverySource.CursorGlobal]: localize('mcp.discovery.source.cursor-global.config', "Cursor global configuration (`~/.cursor/mcp.json`)"),50[DiscoverySource.CursorWorkspace]: localize('mcp.discovery.source.cursor-workspace.config', "Cursor workspace configuration (`.cursor/mcp.json`)"),51};5253export const mcpConfigurationSection = 'mcp';54export const mcpDiscoverySection = 'chat.mcp.discovery.enabled';55export const mcpServerSamplingSection = 'chat.mcp.serverSampling';5657export interface IMcpServerSamplingConfiguration {58allowedDuringChat?: boolean;59allowedOutsideChat?: boolean;60allowedModels?: string[];61}6263export const mcpSchemaExampleServers = {64'mcp-server-time': {65command: 'python',66args: ['-m', 'mcp_server_time', '--local-timezone=America/Los_Angeles'],67env: {},68}69};7071const httpSchemaExamples = {72'my-mcp-server': {73url: 'http://localhost:3001/mcp',74headers: {},75}76};7778const mcpDevModeProps = (stdio: boolean): IJSONSchemaMap => ({79dev: {80type: 'object',81markdownDescription: localize('app.mcp.dev', 'Enabled development mode for the server. When present, the server will be started eagerly and output will be included in its output. Properties inside the `dev` object can configure additional behavior.'),82examples: [{ watch: 'src/**/*.ts', debug: { type: 'node' } }],83properties: {84watch: {85description: localize('app.mcp.dev.watch', 'A glob pattern or list of glob patterns relative to the workspace folder to watch. The MCP server will be restarted when these files change.'),86examples: ['src/**/*.ts'],87oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],88},89...(stdio && {90debug: {91markdownDescription: localize('app.mcp.dev.debug', 'If set, debugs the MCP server using the given runtime as it\'s started.'),92oneOf: [93{94type: 'object',95required: ['type'],96properties: {97type: {98type: 'string',99enum: ['node'],100description: localize('app.mcp.dev.debug.type.node', "Debug the MCP server using Node.js.")101}102},103additionalProperties: false104},105{106type: 'object',107required: ['type'],108properties: {109type: {110type: 'string',111enum: ['debugpy'],112description: localize('app.mcp.dev.debug.type.python', "Debug the MCP server using Python and debugpy.")113},114debugpyPath: {115type: 'string',116description: localize('app.mcp.dev.debug.debugpyPath', "Path to the debugpy executable.")117},118},119additionalProperties: false120}121]122}123})124}125}126});127128export const mcpStdioServerSchema: IJSONSchema = {129type: 'object',130additionalProperties: false,131examples: [mcpSchemaExampleServers['mcp-server-time']],132properties: {133type: {134type: 'string',135enum: ['stdio'],136description: localize('app.mcp.json.type', "The type of the server.")137},138command: {139type: 'string',140description: localize('app.mcp.json.command', "The command to run the server.")141},142cwd: {143type: 'string',144description: localize('app.mcp.json.cwd', "The working directory for the server command. Defaults to the workspace folder when run in a workspace."),145examples: ['${workspaceFolder}'],146},147args: {148type: 'array',149description: localize('app.mcp.args.command', "Arguments passed to the server."),150items: {151type: 'string'152},153},154envFile: {155type: 'string',156description: localize('app.mcp.envFile.command', "Path to a file containing environment variables for the server."),157examples: ['${workspaceFolder}/.env'],158},159env: {160description: localize('app.mcp.env.command', "Environment variables passed to the server."),161additionalProperties: {162anyOf: [163{ type: 'null' },164{ type: 'string' },165{ type: 'number' },166]167}168},169...mcpDevModeProps(true),170}171};172173export const mcpServerSchema: IJSONSchema = {174id: mcpSchemaId,175type: 'object',176title: localize('app.mcp.json.title', "Model Context Protocol Servers"),177allowTrailingCommas: true,178allowComments: true,179additionalProperties: false,180properties: {181servers: {182examples: [183mcpSchemaExampleServers,184httpSchemaExamples,185],186additionalProperties: {187oneOf: [188mcpStdioServerSchema, {189type: 'object',190additionalProperties: false,191required: ['url'],192examples: [httpSchemaExamples['my-mcp-server']],193properties: {194type: {195type: 'string',196enum: ['http', 'sse'],197description: localize('app.mcp.json.type', "The type of the server.")198},199url: {200type: 'string',201format: 'uri',202pattern: '^https?:\\/\\/.+',203patternErrorMessage: localize('app.mcp.json.url.pattern', "The URL must start with 'http://' or 'https://'."),204description: localize('app.mcp.json.url', "The URL of the Streamable HTTP or SSE endpoint.")205},206headers: {207type: 'object',208description: localize('app.mcp.json.headers', "Additional headers sent to the server."),209additionalProperties: { type: 'string' },210},211...mcpDevModeProps(false),212}213},214]215}216},217inputs: inputsSchema.definitions!.inputs218}219};220221export const mcpContributionPoint: IExtensionPointDescriptor<IMcpCollectionContribution[]> = {222extensionPoint: 'mcpServerDefinitionProviders',223activationEventsGenerator(contribs, result) {224for (const contrib of contribs) {225if (contrib.id) {226result.push(mcpActivationEvent(contrib.id));227}228}229},230jsonSchema: {231description: localize('vscode.extension.contributes.mcp', 'Contributes Model Context Protocol servers. Users of this should also use `vscode.lm.registerMcpServerDefinitionProvider`.'),232type: 'array',233defaultSnippets: [{ body: [{ id: '', label: '' }] }],234items: {235additionalProperties: false,236type: 'object',237defaultSnippets: [{ body: { id: '', label: '' } }],238properties: {239id: {240description: localize('vscode.extension.contributes.mcp.id', "Unique ID for the collection."),241type: 'string'242},243label: {244description: localize('vscode.extension.contributes.mcp.label', "Display name for the collection."),245type: 'string'246}247}248}249}250};251252class McpServerDefinitionsProviderRenderer extends Disposable implements IExtensionFeatureTableRenderer {253254readonly type = 'table';255256shouldRender(manifest: IExtensionManifest): boolean {257return !!manifest.contributes?.mcpServerDefinitionProviders && Array.isArray(manifest.contributes.mcpServerDefinitionProviders) && manifest.contributes.mcpServerDefinitionProviders.length > 0;258}259260render(manifest: IExtensionManifest): IRenderedData<ITableData> {261const mcpServerDefinitionProviders = manifest.contributes?.mcpServerDefinitionProviders ?? [];262const headers = [localize('id', "ID"), localize('name', "Name")];263const rows: IRowData[][] = mcpServerDefinitionProviders264.map(mcpServerDefinitionProvider => {265return [266new MarkdownString().appendMarkdown(`\`${mcpServerDefinitionProvider.id}\``),267mcpServerDefinitionProvider.label268];269});270271return {272data: {273headers,274rows275},276dispose: () => { }277};278}279}280281Registry.as<IExtensionFeaturesRegistry>(Extensions.ExtensionFeaturesRegistry).registerExtensionFeature({282id: mcpConfigurationSection,283label: localize('mcpServerDefinitionProviders', "MCP Servers"),284access: {285canToggle: false286},287renderer: new SyncDescriptor(McpServerDefinitionsProviderRenderer),288});289290291292