diff --git a/extension/loc/xlf/aspire-vscode.xlf b/extension/loc/xlf/aspire-vscode.xlf index 4b741264196..8855e802d27 100644 --- a/extension/loc/xlf/aspire-vscode.xlf +++ b/extension/loc/xlf/aspire-vscode.xlf @@ -7,6 +7,9 @@ Add an integration + + Aspire + Aspire @@ -415,11 +418,12 @@ Update integrations - - Verify Aspire CLI installation Use the system default browser (cannot auto-close). + + Verify Aspire CLI installation + View logs @@ -429,6 +433,9 @@ Welcome to Aspire + + Whether to the Aspire MCP server when a workspace is open. + Yes diff --git a/extension/package.json b/extension/package.json index fcec1af019c..773c83e6f66 100644 --- a/extension/package.json +++ b/extension/package.json @@ -37,6 +37,12 @@ "main": "./dist/extension.js", "l10n": "./l10n", "contributes": { + "mcpServerDefinitionProviders": [ + { + "id": "aspire-mcp-server", + "label": "Aspire" + } + ], "viewsContainers": { "activitybar": [ { @@ -461,6 +467,12 @@ "default": true, "description": "%configuration.aspire.enableDebugConfigEnvironmentLogging%", "scope": "window" + }, + "aspire.registerMcpServerInWorkspace": { + "type": "boolean", + "default": false, + "description": "%configuration.aspire.registerMcpServerInWorkspace%", + "scope": "window" } } }, diff --git a/extension/package.nls.json b/extension/package.nls.json index e16757faed7..12e8365175f 100644 --- a/extension/package.nls.json +++ b/extension/package.nls.json @@ -32,6 +32,7 @@ "configuration.aspire.dashboardBrowser.debugFirefox": "Launch Firefox as a debug session (requires Firefox Debugger extension).", "configuration.aspire.closeDashboardOnDebugEnd": "Close the dashboard browser when the debug session ends. Works with debug browser options (Chrome, Edge, Firefox).", "configuration.aspire.enableDebugConfigEnvironmentLogging": "Include environment variables when logging debug session configurations. This can help diagnose environment-related issues but may expose sensitive information in logs.", + "configuration.aspire.registerMcpServerInWorkspace": "Whether to register the Aspire MCP server when a workspace is open.", "command.runAppHost": "Run Aspire apphost", "command.debugAppHost": "Debug Aspire apphost", "aspire-vscode.strings.noCsprojFound": "No apphost found in the current workspace.", @@ -146,5 +147,6 @@ "walkthrough.getStarted.dashboard.title": "Explore the dashboard", "walkthrough.getStarted.dashboard.description": "The Aspire Dashboard shows your resources, endpoints, logs, traces, and metrics — all in one place.\n\n[Open dashboard](command:aspire-vscode.openDashboard)", "walkthrough.getStarted.nextSteps.title": "Next steps", - "walkthrough.getStarted.nextSteps.description": "You're all set! Add integrations for databases, messaging, and cloud services, or deploy your app to production.\n\n[Add an integration](command:aspire-vscode.add)\n\n[Open Aspire docs](https://aspire.dev/docs/)" + "walkthrough.getStarted.nextSteps.description": "You're all set! Add integrations for databases, messaging, and cloud services, or deploy your app to production.\n\n[Add an integration](command:aspire-vscode.add)\n\n[Open Aspire docs](https://aspire.dev/docs/)", + "mcpServerDefinitionProviders.aspire.label": "Aspire" } diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 2bc9d56cf5c..81fa003e360 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -29,6 +29,7 @@ import { AspireEditorCommandProvider } from './editor/AspireEditorCommandProvide import { AspireAppHostTreeProvider } from './views/AspireAppHostTreeProvider'; import { installCliStableCommand, installCliDailyCommand, verifyCliInstalledCommand } from './commands/walkthroughCommands'; import { AspireStatusBarProvider } from './views/AspireStatusBarProvider'; +import { AspireMcpServerDefinitionProvider } from './mcp/AspireMcpServerDefinitionProvider'; let aspireExtensionContext = new AspireExtensionContext(); @@ -119,6 +120,15 @@ export async function activate(context: vscode.ExtensionContext) { aspireExtensionContext.initialize(rpcServer, context, debugConfigProvider, dcpServer, terminalProvider, editorCommandProvider); + // Register Aspire MCP server definition provider so the Aspire MCP server + // appears automatically in VS Code's MCP tools list for Aspire workspaces. + const mcpProvider = new AspireMcpServerDefinitionProvider(); + if (typeof vscode.lm?.registerMcpServerDefinitionProvider === 'function') { + context.subscriptions.push(vscode.lm.registerMcpServerDefinitionProvider('aspire-mcp-server', mcpProvider)); + context.subscriptions.push(mcpProvider); + mcpProvider.refresh(); + } + const getEnableSettingsFileCreationPromptOnStartup = () => vscode.workspace.getConfiguration('aspire').get('enableSettingsFileCreationPromptOnStartup', true); const setEnableSettingsFileCreationPromptOnStartup = async (value: boolean) => await vscode.workspace.getConfiguration('aspire').update('enableSettingsFileCreationPromptOnStartup', value, vscode.ConfigurationTarget.Workspace); const appHostDisposablePromise = checkForExistingAppHostPathInWorkspace( diff --git a/extension/src/mcp/AspireMcpServerDefinitionProvider.ts b/extension/src/mcp/AspireMcpServerDefinitionProvider.ts new file mode 100644 index 00000000000..aee79824805 --- /dev/null +++ b/extension/src/mcp/AspireMcpServerDefinitionProvider.ts @@ -0,0 +1,83 @@ +import * as vscode from 'vscode'; +import { resolveCliPath } from '../utils/cliPath'; +import { extensionLogOutputChannel } from '../utils/logging'; +import { getRegisterMcpServerInWorkspace, registerMcpServerInWorkspaceSetting } from '../utils/settings'; + +/** + * Provides the Aspire MCP server definition to VS Code so it appears + * automatically in the MCP tools list when the Aspire CLI is available + * and the workspace contains an Aspire project. + */ +export class AspireMcpServerDefinitionProvider implements vscode.McpServerDefinitionProvider { + private readonly _onDidChange = new vscode.EventEmitter(); + readonly onDidChangeMcpServerDefinitions = this._onDidChange.event; + + private _cliPath: string | undefined; + private _cliAvailable: boolean = false; + private _shouldProvide: boolean = false; + private _configChangeDisposable: vscode.Disposable | undefined; + private _workspaceFolderChangeDisposable: vscode.Disposable | undefined; + + constructor() { + // Re-evaluate when the setting changes + this._configChangeDisposable = vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(registerMcpServerInWorkspaceSetting)) { + this.refresh(); + } + }); + + // Re-evaluate when workspace folders change + this._workspaceFolderChangeDisposable = vscode.workspace.onDidChangeWorkspaceFolders(() => { + this.refresh(); + }); + } + + async refresh(): Promise { + const [cliResult, shouldProvide] = await Promise.all([ + resolveCliPath(), + checkShouldProvideMcpServer(), + ]); + + const changed = + this._cliAvailable !== cliResult.available || + this._cliPath !== cliResult.cliPath || + this._shouldProvide !== shouldProvide; + + this._cliAvailable = cliResult.available; + this._cliPath = cliResult.cliPath; + this._shouldProvide = shouldProvide; + + if (changed) { + extensionLogOutputChannel.info(`Aspire MCP server definition changed: cliAvailable=${cliResult.available}, shouldProvide=${shouldProvide}`); + this._onDidChange.fire(); + } + } + + provideMcpServerDefinitions(_token: vscode.CancellationToken): vscode.ProviderResult { + if (!this._cliAvailable || !this._shouldProvide || !this._cliPath) { + return []; + } + + return [new vscode.McpStdioServerDefinition('Aspire', this._cliPath, ['agent', 'mcp'])]; + } + + dispose(): void { + this._configChangeDisposable?.dispose(); + this._workspaceFolderChangeDisposable?.dispose(); + this._onDidChange.dispose(); + } +} + +/** + * Determines whether the Aspire MCP server should be provided. + * + * The server is provided only when workspace folders are open and the + * "aspire.registerMcpServerInWorkspace" setting is enabled. + */ +async function checkShouldProvideMcpServer(): Promise { + if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) { + return false; + } + + return getRegisterMcpServerInWorkspace(); +} diff --git a/extension/src/utils/settings.ts b/extension/src/utils/settings.ts new file mode 100644 index 00000000000..6c1c8263617 --- /dev/null +++ b/extension/src/utils/settings.ts @@ -0,0 +1,17 @@ +import * as vscode from 'vscode'; + +const aspireConfigSection = 'aspire'; + +const registerMcpServerInWorkspaceSettingName = 'registerMcpServerInWorkspace'; +export const registerMcpServerInWorkspaceSetting = `${aspireConfigSection}.${registerMcpServerInWorkspaceSettingName}`; + +/** + * Returns the Aspire extension configuration object. + */ +function getAspireConfig(): vscode.WorkspaceConfiguration { + return vscode.workspace.getConfiguration(aspireConfigSection); +} + +export function getRegisterMcpServerInWorkspace(): boolean { + return getAspireConfig().get(registerMcpServerInWorkspaceSettingName, false); +} diff --git a/extension/src/vscode.proposed.mcpServerDefinitions.d.ts b/extension/src/vscode.proposed.mcpServerDefinitions.d.ts new file mode 100644 index 00000000000..33402753140 --- /dev/null +++ b/extension/src/vscode.proposed.mcpServerDefinitions.d.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Type declarations for the vscode proposed API: mcpServerDefinitions +// https://github.com/microsoft/vscode/blob/main/src/vscode-dts/vscode.proposed.mcpServerDefinitions.d.ts + +declare module 'vscode' { + + export class McpStdioServerDefinition { + readonly label: string; + cwd?: Uri; + command: string; + args: string[]; + env: Record; + version?: string; + constructor(label: string, command: string, args?: string[], env?: Record, version?: string); + } + + export class McpHttpServerDefinition { + readonly label: string; + uri: Uri; + headers: Record; + version?: string; + constructor(label: string, uri: Uri, headers?: Record, version?: string); + } + + export type McpServerDefinition = McpStdioServerDefinition | McpHttpServerDefinition; + + export interface McpServerDefinitionProvider { + readonly onDidChangeMcpServerDefinitions?: Event; + provideMcpServerDefinitions(token: CancellationToken): ProviderResult; + resolveMcpServerDefinition?(server: T, token: CancellationToken): ProviderResult; + } + + export namespace lm { + export function registerMcpServerDefinitionProvider(id: string, provider: McpServerDefinitionProvider): Disposable; + } +}