From c1b06313d7819ecbf6a82cb53e56ee1fbd9d3049 Mon Sep 17 00:00:00 2001 From: Kirigaya <1193466151@qq.com> Date: Thu, 1 May 2025 03:17:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=8F=92=E4=BB=B6=E7=AB=AF?= =?UTF-8?q?=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 69 ++++++++-- src/common/entry.ts | 37 +++-- src/common/index.dto.ts | 8 +- src/common/index.ts | 32 +++-- src/extension.ts | 50 +------ src/global.ts | 14 +- src/sidebar/global@2.ts | 0 src/sidebar/{help@3.ts => help.controller.ts} | 2 + src/sidebar/index.ts | 29 ---- src/sidebar/installed.controller.ts | 84 ++++++++++++ src/sidebar/workspace.controller.ts | 75 +++++++++++ src/sidebar/workspace.service.ts | 127 ++++++++++++++++++ src/sidebar/workspace@1.ts | 69 ---------- src/webview/webview.controller.ts | 33 +++++ .../webview.service.ts} | 7 +- tsconfig.json | 1 + 16 files changed, 448 insertions(+), 189 deletions(-) delete mode 100644 src/sidebar/global@2.ts rename src/sidebar/{help@3.ts => help.controller.ts} (95%) delete mode 100644 src/sidebar/index.ts create mode 100644 src/sidebar/installed.controller.ts create mode 100644 src/sidebar/workspace.controller.ts create mode 100644 src/sidebar/workspace.service.ts delete mode 100644 src/sidebar/workspace@1.ts create mode 100644 src/webview/webview.controller.ts rename src/{webview.ts => webview/webview.service.ts} (92%) diff --git a/package.json b/package.json index ccac5eb..746cdfe 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,30 @@ "title": "打开配置", "category": "openmcp", "icon": "$(gear)" + }, + { + "command": "openmcp.sidebar.installed-connection.deleteConnection", + "title": "删除连接", + "category": "openmcp", + "icon": "$(trash)" + }, + { + "command": "openmcp.sidebar.installed-connection.refresh", + "title": "刷新", + "category": "openmcp", + "icon": "$(refresh)" + }, + { + "command": "openmcp.sidebar.installed-connection.addConnection", + "title": "添加连接", + "category": "openmcp", + "icon": "$(add)" + }, + { + "command": "openmcp.sidebar.installed-connection.openConfiguration", + "title": "打开配置", + "category": "openmcp", + "icon": "$(gear)" } ], "menus": { @@ -78,24 +102,39 @@ { "command": "openmcp.sidebar.workspace-connection.refresh", "group": "navigation", - "when": "view == openmcp.sidebar-view.workspace-connection" + "when": "view == openmcp.sidebar.workspace-connection" }, { "command": "openmcp.sidebar.workspace-connection.addConnection", "group": "navigation", - "when": "view == openmcp.sidebar-view.workspace-connection" + "when": "view == openmcp.sidebar.workspace-connection" }, { "command": "openmcp.sidebar.workspace-connection.openConfiguration", "group": "navigation", - "when": "view == openmcp.sidebar-view.workspace-connection" + "when": "view == openmcp.sidebar.workspace-connection" + }, + { + "command": "openmcp.sidebar.installed-connection.refresh", + "group": "navigation", + "when": "view == openmcp.sidebar.installed-connection" + }, + { + "command": "openmcp.sidebar.installed-connection.addConnection", + "group": "navigation", + "when": "view == openmcp.sidebar.installed-connection" + }, + { + "command": "openmcp.sidebar.installed-connection.openConfiguration", + "group": "navigation", + "when": "view == openmcp.sidebar.installed-connection" } ], "view/item/context": [ { "command": "openmcp.sidebar.workspace-connection.revealWebviewPanel", "group": "inline@1", - "when": "view == openmcp.sidebar-view.workspace-connection", + "when": "view == openmcp.sidebar.workspace-connection || view == openmcp.sidebar.installed-connection", "args": { "view": "${viewItem}" } @@ -103,7 +142,15 @@ { "command": "openmcp.sidebar.workspace-connection.deleteConnection", "group": "inline@2", - "when": "view == openmcp.sidebar-view.workspace-connection", + "when": "view == openmcp.sidebar.workspace-connection", + "args": { + "view": "${viewItem}" + } + }, + { + "command": "openmcp.sidebar.installed-connection.deleteConnection", + "group": "inline@2", + "when": "view == openmcp.sidebar.installed-connection", "args": { "view": "${viewItem}" } @@ -122,11 +169,17 @@ "views": { "openmcp-sidebar": [ { - "id": "openmcp.sidebar-view.workspace-connection", + "id": "openmcp.sidebar.workspace-connection", "icon": "./icons/protocol.svg", "name": "MCP 连接 (工作区)", "type": "tree" }, + { + "id": "openmcp.sidebar.installed-connection", + "icon": "./icons/protocol.svg", + "name": "安装的 MCP 服务器", + "type": "tree" + }, { "id": "openmcp.sidebar.help", "icon": "./icons/protocol.svg", @@ -146,7 +199,7 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.10.2", - "@seald-io/nedb": "^4.1.1", + "@seald-io/nedb": "^4.1.1", "axios": "^1.7.7", "bson": "^6.8.0", "openai": "^4.93.0", @@ -165,4 +218,4 @@ "webpack": "^5.99.5", "webpack-cli": "^5.1.4" } -} +} \ No newline at end of file diff --git a/src/common/entry.ts b/src/common/entry.ts index 8f3a487..4758fbb 100644 --- a/src/common/entry.ts +++ b/src/common/entry.ts @@ -1,22 +1,41 @@ import * as vscode from 'vscode'; import { registerCommands, registerTreeDataProviders } from '.'; +import { HelpProvider } from '../sidebar/help.controller'; +import { McpWorkspaceConnectProvider } from '../sidebar/workspace.controller'; +import { McpInstalledConnectProvider } from '../sidebar/installed.controller'; +import { WebviewController } from '../webview/webview.controller'; export const InstallModules = [ - + McpWorkspaceConnectProvider, + McpInstalledConnectProvider, + HelpProvider, + WebviewController ]; +const registerSingles = new Map(); + export function launch(context: vscode.ExtensionContext) { + + for (const [providerId, value] of registerTreeDataProviders) { + const provider = new value.providerConstructor(context); + + registerSingles.set(providerId, provider); + + context.subscriptions.push( + vscode.window.registerTreeDataProvider(providerId, provider) + ); + } + for (const [command, value] of registerCommands) { - context.subscriptions.push(vscode.commands.registerCommand(command, (...args: any[]) => { - value.handler(context, ...args); + const namespace = value.target.__openmcp_namespace; + const targetCommand = namespace ? `${namespace}.${command}` : command; + + const target = registerSingles.has(namespace) ? registerSingles.get(namespace) : value.target; + + context.subscriptions.push(vscode.commands.registerCommand(targetCommand, (...args: any[]) => { + target[value.propertyKey](context,...args); })); } - for (const [providerId, value] of registerTreeDataProviders) { - context.subscriptions.push( - vscode.window.registerTreeDataProvider(providerId, value.provider) - ); - } - } \ No newline at end of file diff --git a/src/common/index.dto.ts b/src/common/index.dto.ts index 16cebe8..c9cee59 100644 --- a/src/common/index.dto.ts +++ b/src/common/index.dto.ts @@ -10,15 +10,17 @@ export interface CustomDescriptor { } export interface IRegisterCommandItem { - handler: (context: ExtensionContext, ...args: any[]) => void; + handler: (context: ExtensionContext, ...args: any[]) => any; options?: any; + target: any; + propertyKey: string; } export type CommandHandlerDescriptor = CustomDescriptor; export interface IRegisterTreeDataProviderItem { - provider: TreeDataProvider; + providerConstructor: TreeDataProviderConstructor; options?: any; } -export type TreeDataProviderDescriptor = CustomDescriptor['provider']>; \ No newline at end of file +export type TreeDataProviderConstructor = new (context: ExtensionContext, ...args: any[]) => TreeDataProvider; \ No newline at end of file diff --git a/src/common/index.ts b/src/common/index.ts index e929af4..7410f88 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -1,34 +1,32 @@ -import { CommandHandlerDescriptor, IRegisterCommandItem, IRegisterTreeDataProviderItem, TreeDataProviderDescriptor } from "./index.dto"; +import { CommandHandlerDescriptor, IRegisterCommandItem, IRegisterTreeDataProviderItem, TreeDataProviderConstructor } from "./index.dto"; -export const registerCommands = new Map(); +export const registerCommands = new Array<[string, IRegisterCommandItem]>(); export const registerTreeDataProviders = new Map>(); export function RegisterCommand(command: string, options?: any) { - return function(target: any, propertyKey: string, descriptor: CommandHandlerDescriptor) { + return function (target: any, propertyKey: string, descriptor: CommandHandlerDescriptor) { const handler = descriptor.value; - - // 根据 option 进行的操作 - // ... + console.log(propertyKey); + console.log(descriptor); + + if (handler) { - registerCommands.set(command, { handler, options }); + registerCommands.push([command, { handler, target, propertyKey, options }]); } return descriptor; } } -export function RegisterTreeDataProvider(command: string, options?: any) { - return function(target: any, propertyKey: string, descriptor: TreeDataProviderDescriptor) { - const provider = descriptor.value; +export function RegisterTreeDataProvider(providerId: string, options?: any) { + return function (target: TreeDataProviderConstructor) { - // 根据 option 进行的操作 - // ... - - if (provider) { - registerTreeDataProviders.set(command, { provider, options }); - } + target.prototype.__openmcp_namespace = providerId; - return descriptor; + registerTreeDataProviders.set(providerId, { + providerConstructor: target, + options + }); } } \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 2d540f4..7c20070 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,10 +1,6 @@ import * as vscode from 'vscode'; - import * as OpenMCPService from '../resources/service'; -import { getDefaultLanunchSigature, getLaunchCWD, revealOpenMcpWebviewPanel } from './webview'; -import { registerSidebar } from './sidebar'; -import { getWorkspaceConnectionConfigItemByPath } from './global'; -import type { ConnectionViewItem } from './sidebar/common'; +import { launch } from './common/entry'; export function activate(context: vscode.ExtensionContext) { console.log('activate openmcp'); @@ -15,49 +11,7 @@ export function activate(context: vscode.ExtensionContext) { const workspace = workspaceFolder?.uri.fsPath || ''; OpenMCPService.setVscodeWorkspace(workspace); - registerSidebar(context); - - context.subscriptions.push( - vscode.commands.registerCommand('openmcp.sidebar.workspace-connection.revealWebviewPanel', (view: ConnectionViewItem) => { - const item = view.item; - revealOpenMcpWebviewPanel(context, item.filePath || item.name, item); - } - )); - - context.subscriptions.push( - vscode.commands.registerCommand('openmcp.sidebar.workspace-connection.deleteConnection', (view: ConnectionViewItem) => { - deleteConnection(context, view); - } - )); - - context.subscriptions.push( - vscode.commands.registerCommand('openmcp.showOpenMCP', async (uri: vscode.Uri) => { - - const connectionItem = getWorkspaceConnectionConfigItemByPath(uri.fsPath); - if (!connectionItem) { - // 项目不存在连接信息 - const cwd = getLaunchCWD(context, uri); - - const sigature = getDefaultLanunchSigature(uri.fsPath, cwd); - - if (!sigature) { - vscode.window.showErrorMessage('OpenMCP: 无法获取启动参数'); - return; - } - - revealOpenMcpWebviewPanel(context, uri.fsPath, { - type: 'stdio', - name: 'OpenMCP', - command: sigature.command, - args: sigature.args, - cwd - }); - } else { - revealOpenMcpWebviewPanel(context, uri.fsPath, connectionItem); - } - - }) - ); + launch(context); } diff --git a/src/global.ts b/src/global.ts index 3fa57ab..b699027 100644 --- a/src/global.ts +++ b/src/global.ts @@ -81,17 +81,27 @@ export function getConnectionConfig() { return connection; } +/** + * @description 获取工作区的连接信息,默认是 {workspace}/.vscode/openmcp_connection.json + * @returns + */ +export function getWorkspaceConnectionConfigPath() { + const workspace = getWorkspacePath(); + const configDir = fspath.join(workspace, '.vscode'); + const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME); + return connectionConfig; +} /** * @description 获取工作区的连接信息,工作区的连接文件的路径都是相对路径,以 {workspace} 开头 * @param workspace */ export function getWorkspaceConnectionConfig() { - const workspace = getWorkspacePath(); - if (_workspaceConnectionConfig) { return _workspaceConnectionConfig; } + + const workspace = getWorkspacePath(); const configDir = fspath.join(workspace, '.vscode'); const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME); diff --git a/src/sidebar/global@2.ts b/src/sidebar/global@2.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/sidebar/help@3.ts b/src/sidebar/help.controller.ts similarity index 95% rename from src/sidebar/help@3.ts rename to src/sidebar/help.controller.ts index dcdaf0f..24d6f37 100644 --- a/src/sidebar/help@3.ts +++ b/src/sidebar/help.controller.ts @@ -1,6 +1,8 @@ import * as vscode from 'vscode'; import { SidebarItem } from './common'; +import { RegisterTreeDataProvider } from '../common'; +@RegisterTreeDataProvider('openmcp.sidebar.help') export class HelpProvider implements vscode.TreeDataProvider { constructor(private context: vscode.ExtensionContext) { diff --git a/src/sidebar/index.ts b/src/sidebar/index.ts deleted file mode 100644 index 0e7c595..0000000 --- a/src/sidebar/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as vscode from 'vscode'; -import { McpWorkspaceConnectProvider } from './workspace@1'; -import { HelpProvider } from './help@3'; - -// 在 registerSidebar 函数中注册 refresh 命令 -export function registerSidebar(context: vscode.ExtensionContext) { - const workspaceConnectionProvider = new McpWorkspaceConnectProvider(context); - - // 注册 refresh 命令 - context.subscriptions.push( - vscode.commands.registerCommand('openmcp.sidebar.workspace-connection.refresh', () => { - workspaceConnectionProvider.refresh(); - }) - ); - - // 注册 MCP 连接的 sidebar 视图 - context.subscriptions.push( - vscode.window.registerTreeDataProvider('openmcp.sidebar-view.workspace-connection', workspaceConnectionProvider) - ); - - // 注册 MCP 连接的 sidebar 视图 - - - // 注册 入门与帮助的 sidebar 视图 - context.subscriptions.push( - vscode.window.registerTreeDataProvider('openmcp.sidebar.help', new HelpProvider(context)) - ); -} - diff --git a/src/sidebar/installed.controller.ts b/src/sidebar/installed.controller.ts new file mode 100644 index 0000000..4400ad7 --- /dev/null +++ b/src/sidebar/installed.controller.ts @@ -0,0 +1,84 @@ +import * as vscode from 'vscode'; +import { RegisterCommand, RegisterTreeDataProvider } from '../common'; +import { getWorkspaceConnectionConfig, getWorkspacePath, panels, saveWorkspaceConnectionConfig } from '../global'; +import { ConnectionViewItem } from './common'; + +@RegisterTreeDataProvider('openmcp.sidebar.installed-connection') +export class McpInstalledConnectProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + + constructor(private context: vscode.ExtensionContext) {} + + // 实现 TreeDataProvider 接口 + getTreeItem(element: ConnectionViewItem): vscode.TreeItem { + return element; + } + + getChildren(element?: ConnectionViewItem): Thenable { + // TODO: 读取 configDir 下的所有文件,作为子节点 + const connection = getWorkspaceConnectionConfig(); + const sidebarItems = connection.items.map((item, index) => { + // 连接的名字 + const itemName = `${item.name} (${item.type})` + return new ConnectionViewItem(itemName, vscode.TreeItemCollapsibleState.None, item, 'server'); + }) + + // 返回子节点 + return Promise.resolve(sidebarItems); + } + + + @RegisterCommand('refresh') + public refresh(context: vscode.ExtensionContext): void { + this._onDidChangeTreeData.fire(); + } + + @RegisterCommand('addConnection') + public async addConnection(context: vscode.ExtensionContext) { + + } + + @RegisterCommand('openConfiguration') + public async openConfiguration(context: vscode.ExtensionContext, view: ConnectionViewItem) { + const item = view.item; + const uri = vscode.Uri.file(item.filePath || item.name); + vscode.commands.executeCommand('vscode.open', uri); + } + + @RegisterCommand('deleteConnection') + public async deleteConnection(context: vscode.ExtensionContext, view: ConnectionViewItem) { + const workspaceConnectionConfig = getWorkspaceConnectionConfig(); + const connectionItem = view.item; + + // 弹出确认对话框 + const confirm = await vscode.window.showWarningMessage( + `确定要删除连接 "${connectionItem.name}" 吗?`, + { modal: true }, + '确定' + ); + + if (confirm !== '确定') { + return; // 用户取消删除 + } + + // 从配置中移除该连接项 + const index = workspaceConnectionConfig.items.indexOf(connectionItem); + if (index !== -1) { + workspaceConnectionConfig.items.splice(index, 1); + + // 保存更新后的配置 + const workspacePath = getWorkspacePath(); + saveWorkspaceConnectionConfig(workspacePath); + + // 刷新侧边栏视图 + vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh'); + + // 如果该连接有对应的webview面板,则关闭它 + if (panels.has(connectionItem.filePath || connectionItem.name)) { + const panel = panels.get(connectionItem.filePath || connectionItem.name); + panel?.dispose(); + } + } + } +} diff --git a/src/sidebar/workspace.controller.ts b/src/sidebar/workspace.controller.ts new file mode 100644 index 0000000..978c61f --- /dev/null +++ b/src/sidebar/workspace.controller.ts @@ -0,0 +1,75 @@ +import * as vscode from 'vscode'; +import { RegisterCommand, RegisterTreeDataProvider } from '../common'; +import { getWorkspaceConnectionConfig, getWorkspaceConnectionConfigPath, getWorkspacePath, panels, saveWorkspaceConnectionConfig } from '../global'; +import { ConnectionViewItem } from './common'; +import { revealOpenMcpWebviewPanel } from '../webview/webview.service'; +import { acquireUserCustomConnection, deleteUserConnection } from './workspace.service'; + +@RegisterTreeDataProvider('openmcp.sidebar.workspace-connection') +export class McpWorkspaceConnectProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + + constructor(private context: vscode.ExtensionContext) { } + + // 实现 TreeDataProvider 接口 + getTreeItem(element: ConnectionViewItem): vscode.TreeItem { + return element; + } + + getChildren(element?: ConnectionViewItem): Thenable { + // TODO: 读取 configDir 下的所有文件,作为子节点 + const connection = getWorkspaceConnectionConfig(); + const sidebarItems = connection.items.map((item, index) => { + // 连接的名字 + const itemName = `${item.name} (${item.type})` + return new ConnectionViewItem(itemName, vscode.TreeItemCollapsibleState.None, item, 'server'); + }) + + // 返回子节点 + return Promise.resolve(sidebarItems); + } + + @RegisterCommand('revealWebviewPanel') + public revealWebviewPanel(context: vscode.ExtensionContext, view: ConnectionViewItem) { + const item = view.item; + revealOpenMcpWebviewPanel(context, item.filePath || item.name, item); + } + + @RegisterCommand('refresh') + public refresh(context: vscode.ExtensionContext): void { + console.log(this); + + this._onDidChangeTreeData.fire(); + } + + @RegisterCommand('addConnection') + public async addConnection(context: vscode.ExtensionContext) { + + const item = await acquireUserCustomConnection(); + + if (!item) { + return; + } + + const workspaceConnectionConfig = getWorkspaceConnectionConfig(); + workspaceConnectionConfig.items.push(item); + saveWorkspaceConnectionConfig(getWorkspacePath()); + + // 刷新侧边栏视图 + vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh'); + } + + @RegisterCommand('openConfiguration') + public async openConfiguration(context: vscode.ExtensionContext, view: ConnectionViewItem) { + const configPath = getWorkspaceConnectionConfigPath(); + const uri = vscode.Uri.file(configPath); + vscode.commands.executeCommand('vscode.open', uri); + } + + @RegisterCommand('deleteConnection') + public async deleteConnection(context: vscode.ExtensionContext, view: ConnectionViewItem) { + const connectionItem = view.item; + await deleteUserConnection(connectionItem); + } +} diff --git a/src/sidebar/workspace.service.ts b/src/sidebar/workspace.service.ts new file mode 100644 index 0000000..af15a9b --- /dev/null +++ b/src/sidebar/workspace.service.ts @@ -0,0 +1,127 @@ +import { getWorkspaceConnectionConfig, getWorkspacePath, IConnectionItem, panels, saveWorkspaceConnectionConfig } from "../global"; + +import * as vscode from 'vscode'; + +export async function acquireUserCustomConnection(): Promise { + // 让用户选择连接类型 + const connectionType = await vscode.window.showQuickPick(['stdio', 'sse'], { + placeHolder: '请选择连接类型' + }); + + if (!connectionType) { + return; // 用户取消选择 + } + + if (connectionType === 'stdio') { + // 获取 command + const commandString = await vscode.window.showInputBox({ + prompt: '请输入连接的 command', + placeHolder: '例如: mcp run main.py' + }); + + if (!commandString) { + return; // 用户取消输入 + } + + // 获取 cwd + const cwd = await vscode.window.showInputBox({ + prompt: '请输入工作目录 (cwd),可选', + placeHolder: '例如: /path/to/project' + }); + + // 校验 command + cwd 是否有效 + try { + const commandPath = await validateAndGetCommandPath(commandString, cwd); + console.log('Command Path:', commandPath); + } catch (error) { + vscode.window.showErrorMessage(`无效的 command: ${error}`); + return; + } + + const commands = commandString.split(' '); + const command = commands[0]; + const args = commands.slice(1); + + // 保存连接配置 + return { + type: 'stdio', + name: `stdio-${Date.now()}`, + command: command, + args, + cwd: cwd || '' + }; + + } else if (connectionType === 'sse') { + // 获取 url + const url = await vscode.window.showInputBox({ + prompt: '请输入连接的 URL', + placeHolder: '例如: https://127.0.0.1:8080' + }); + + if (!url) { + return; // 用户取消输入 + } + + // 获取 oauth + const oauth = await vscode.window.showInputBox({ + prompt: '请输入 OAuth 令牌,可选', + placeHolder: '例如: your-oauth-token' + }); + + // 保存连接配置 + return { + type: 'sse', + name: `sse-${Date.now()}`, + version: '1.0', // 假设默认版本为 1.0,可根据实际情况修改 + url: url, + oauth: oauth || '' + } + } +} + +export async function deleteUserConnection(item: IConnectionItem) { + // 弹出确认对话框 + const confirm = await vscode.window.showWarningMessage( + `确定要删除连接 "${item.name}" 吗?`, + { modal: true }, + '确定' + ); + + if (confirm !== '确定') { + return; // 用户取消删除 + } + + const workspaceConnectionConfig = getWorkspaceConnectionConfig(); + + // 从配置中移除该连接项 + const index = workspaceConnectionConfig.items.indexOf(item); + if (index !== -1) { + workspaceConnectionConfig.items.splice(index, 1); + + // 保存更新后的配置 + const workspacePath = getWorkspacePath(); + saveWorkspaceConnectionConfig(workspacePath); + + // 刷新侧边栏视图 + vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh'); + panels.delete(item.name); + // 如果该连接有对应的webview面板,则关闭它 + if (panels.has(item.filePath || item.name)) { + const panel = panels.get(item.filePath || item.name); + panel?.dispose(); + } + } +} + +export async function validateAndGetCommandPath(command: string, cwd?: string): Promise { + const { exec } = require('child_process'); + const { promisify } = require('util'); + const execAsync = promisify(exec); + + try { + const { stdout } = await execAsync(`which ${command.split(' ')[0]}`, { cwd }); + return stdout.trim(); + } catch (error) { + throw new Error(`无法找到命令: ${command.split(' ')[0]}`); + } +} \ No newline at end of file diff --git a/src/sidebar/workspace@1.ts b/src/sidebar/workspace@1.ts deleted file mode 100644 index 65fd3e7..0000000 --- a/src/sidebar/workspace@1.ts +++ /dev/null @@ -1,69 +0,0 @@ -import * as vscode from 'vscode'; -import { ConnectionViewItem } from './common'; -import { getWorkspaceConnectionConfig, getWorkspacePath, panels, saveWorkspaceConnectionConfig } from '../global'; - -export class McpWorkspaceConnectProvider implements vscode.TreeDataProvider { - private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); - readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; - - constructor(private context: vscode.ExtensionContext) { - } - - // 实现 TreeDataProvider 接口 - getTreeItem(element: ConnectionViewItem): vscode.TreeItem { - return element; - } - - getChildren(element?: ConnectionViewItem): Thenable { - // TODO: 读取 configDir 下的所有文件,作为子节点 - const connection = getWorkspaceConnectionConfig(); - const sidebarItems = connection.items.map((item, index) => { - // 连接的名字 - const itemName = `${item.name} (${item.type})` - return new ConnectionViewItem(itemName, vscode.TreeItemCollapsibleState.None, item, 'server'); - }) - - // 返回子节点 - return Promise.resolve(sidebarItems); - } - - // 添加 refresh 方法 - public refresh(): void { - this._onDidChangeTreeData.fire(); - } -} - -export async function deleteConnection(context: vscode.ExtensionContext, view: ConnectionViewItem) { - const workspaceConnectionConfig = getWorkspaceConnectionConfig(); - const connectionItem = view.item; - - // 弹出确认对话框 - const confirm = await vscode.window.showWarningMessage( - `确定要删除连接 "${connectionItem.name}" 吗?`, - { modal: true }, - '确定' - ); - - if (confirm !== '确定') { - return; // 用户取消删除 - } - - // 从配置中移除该连接项 - const index = workspaceConnectionConfig.items.indexOf(connectionItem); - if (index !== -1) { - workspaceConnectionConfig.items.splice(index, 1); - - // 保存更新后的配置 - const workspacePath = getWorkspacePath(); - saveWorkspaceConnectionConfig(workspacePath); - - // 刷新侧边栏视图 - vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh'); - - // 如果该连接有对应的webview面板,则关闭它 - if (panels.has(connectionItem.filePath || connectionItem.name)) { - const panel = panels.get(connectionItem.filePath || connectionItem.name); - panel?.dispose(); - } - } -} \ No newline at end of file diff --git a/src/webview/webview.controller.ts b/src/webview/webview.controller.ts new file mode 100644 index 0000000..7efb0fd --- /dev/null +++ b/src/webview/webview.controller.ts @@ -0,0 +1,33 @@ +import * as vscode from 'vscode'; +import { RegisterCommand } from "../common"; +import { getDefaultLanunchSignature, getLaunchCWD, revealOpenMcpWebviewPanel } from './webview.service'; +import { getWorkspaceConnectionConfigItemByPath } from '../global'; + +export class WebviewController { + @RegisterCommand('openmcp.showOpenMCP') + async showOpenMCP(context: vscode.ExtensionContext, uri: vscode.Uri) { + const connectionItem = getWorkspaceConnectionConfigItemByPath(uri.fsPath); + if (!connectionItem) { + // 项目不存在连接信息 + const cwd = getLaunchCWD(context, uri); + + const sigature = getDefaultLanunchSignature(uri.fsPath, cwd); + + if (!sigature) { + vscode.window.showErrorMessage('OpenMCP: 无法获取启动参数'); + return; + } + + revealOpenMcpWebviewPanel(context, uri.fsPath, { + type: 'stdio', + name: 'OpenMCP', + command: sigature.command, + args: sigature.args, + cwd + }); + } else { + revealOpenMcpWebviewPanel(context, uri.fsPath, connectionItem); + } + + } +} \ No newline at end of file diff --git a/src/webview.ts b/src/webview/webview.service.ts similarity index 92% rename from src/webview.ts rename to src/webview/webview.service.ts index e5806e7..81d7c3a 100644 --- a/src/webview.ts +++ b/src/webview/webview.service.ts @@ -1,9 +1,8 @@ import * as vscode from 'vscode'; import * as fs from 'fs'; import * as fspath from 'path'; -import { getWorkspaceConnectionConfig, getWorkspacePath, IConnectionItem, ILaunchSigature, panels, saveWorkspaceConnectionConfig, updateWorkspaceConnectionConfig } from './global'; -import * as OpenMCPService from '../resources/service'; - +import { IConnectionItem, ILaunchSigature, panels, updateWorkspaceConnectionConfig } from '../global'; +import * as OpenMCPService from '../../resources/service'; export function getWebviewContent(context: vscode.ExtensionContext, panel: vscode.WebviewPanel): string | undefined { const viewRoot = fspath.join(context.extensionPath, 'resources', 'renderer'); @@ -114,7 +113,7 @@ export function revealOpenMcpWebviewPanel( return panel; } -export function getDefaultLanunchSigature(path: string, cwd: string) { +export function getDefaultLanunchSignature(path: string, cwd: string) { const relativePath = fspath.relative(cwd, path); if (relativePath.endsWith('.py')) { diff --git a/tsconfig.json b/tsconfig.json index d132474..e12ba6b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, + "experimentalDecorators": true, // 允许访问 resources 目录 "paths": { "@resources/*": ["./resources/*"]