更新插件端架构
This commit is contained in:
parent
34936d944d
commit
c1b06313d7
65
package.json
65
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",
|
||||
|
@ -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<string, any>();
|
||||
|
||||
export function launch(context: vscode.ExtensionContext) {
|
||||
|
||||
for (const [command, value] of registerCommands) {
|
||||
context.subscriptions.push(vscode.commands.registerCommand(command, (...args: any[]) => {
|
||||
value.handler(context, ...args);
|
||||
}));
|
||||
}
|
||||
|
||||
for (const [providerId, value] of registerTreeDataProviders) {
|
||||
const provider = new value.providerConstructor(context);
|
||||
|
||||
registerSingles.set(providerId, provider);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerTreeDataProvider(providerId, value.provider)
|
||||
vscode.window.registerTreeDataProvider(providerId, provider)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
for (const [command, value] of registerCommands) {
|
||||
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);
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
@ -10,15 +10,17 @@ export interface CustomDescriptor<T> {
|
||||
}
|
||||
|
||||
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<IRegisterCommandItem['handler']>;
|
||||
|
||||
export interface IRegisterTreeDataProviderItem<T> {
|
||||
provider: TreeDataProvider<T>;
|
||||
providerConstructor: TreeDataProviderConstructor<T>;
|
||||
options?: any;
|
||||
}
|
||||
|
||||
export type TreeDataProviderDescriptor<T> = CustomDescriptor<IRegisterTreeDataProviderItem<T>['provider']>;
|
||||
export type TreeDataProviderConstructor<T> = new (context: ExtensionContext, ...args: any[]) => TreeDataProvider<T>;
|
@ -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<string, IRegisterCommandItem>();
|
||||
export const registerCommands = new Array<[string, IRegisterCommandItem]>();
|
||||
export const registerTreeDataProviders = new Map<string, IRegisterTreeDataProviderItem<any>>();
|
||||
|
||||
export function RegisterCommand(command: string, options?: any) {
|
||||
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<T>(command: string, options?: any) {
|
||||
return function(target: any, propertyKey: string, descriptor: TreeDataProviderDescriptor<T>) {
|
||||
const provider = descriptor.value;
|
||||
export function RegisterTreeDataProvider<T>(providerId: string, options?: any) {
|
||||
return function (target: TreeDataProviderConstructor<T>) {
|
||||
|
||||
// 根据 option 进行的操作
|
||||
// ...
|
||||
target.prototype.__openmcp_namespace = providerId;
|
||||
|
||||
if (provider) {
|
||||
registerTreeDataProviders.set(command, { provider, options });
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
registerTreeDataProviders.set(providerId, {
|
||||
providerConstructor: target,
|
||||
options
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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<SidebarItem> {
|
||||
|
||||
constructor(private context: vscode.ExtensionContext) {
|
@ -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))
|
||||
);
|
||||
}
|
||||
|
84
src/sidebar/installed.controller.ts
Normal file
84
src/sidebar/installed.controller.ts
Normal file
@ -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<ConnectionViewItem> {
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<ConnectionViewItem | undefined | null | void> = new vscode.EventEmitter<ConnectionViewItem | undefined | null | void>();
|
||||
readonly onDidChangeTreeData: vscode.Event<ConnectionViewItem | undefined | null | void> = this._onDidChangeTreeData.event;
|
||||
|
||||
constructor(private context: vscode.ExtensionContext) {}
|
||||
|
||||
// 实现 TreeDataProvider 接口
|
||||
getTreeItem(element: ConnectionViewItem): vscode.TreeItem {
|
||||
return element;
|
||||
}
|
||||
|
||||
getChildren(element?: ConnectionViewItem): Thenable<ConnectionViewItem[]> {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
75
src/sidebar/workspace.controller.ts
Normal file
75
src/sidebar/workspace.controller.ts
Normal file
@ -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<ConnectionViewItem> {
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<ConnectionViewItem | undefined | null | void> = new vscode.EventEmitter<ConnectionViewItem | undefined | null | void>();
|
||||
readonly onDidChangeTreeData: vscode.Event<ConnectionViewItem | undefined | null | void> = this._onDidChangeTreeData.event;
|
||||
|
||||
constructor(private context: vscode.ExtensionContext) { }
|
||||
|
||||
// 实现 TreeDataProvider 接口
|
||||
getTreeItem(element: ConnectionViewItem): vscode.TreeItem {
|
||||
return element;
|
||||
}
|
||||
|
||||
getChildren(element?: ConnectionViewItem): Thenable<ConnectionViewItem[]> {
|
||||
// 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);
|
||||
}
|
||||
}
|
127
src/sidebar/workspace.service.ts
Normal file
127
src/sidebar/workspace.service.ts
Normal file
@ -0,0 +1,127 @@
|
||||
import { getWorkspaceConnectionConfig, getWorkspacePath, IConnectionItem, panels, saveWorkspaceConnectionConfig } from "../global";
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export async function acquireUserCustomConnection(): Promise<IConnectionItem | undefined> {
|
||||
// 让用户选择连接类型
|
||||
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<string> {
|
||||
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]}`);
|
||||
}
|
||||
}
|
@ -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<ConnectionViewItem> {
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<ConnectionViewItem | undefined | null | void> = new vscode.EventEmitter<ConnectionViewItem | undefined | null | void>();
|
||||
readonly onDidChangeTreeData: vscode.Event<ConnectionViewItem | undefined | null | void> = this._onDidChangeTreeData.event;
|
||||
|
||||
constructor(private context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
// 实现 TreeDataProvider 接口
|
||||
getTreeItem(element: ConnectionViewItem): vscode.TreeItem {
|
||||
return element;
|
||||
}
|
||||
|
||||
getChildren(element?: ConnectionViewItem): Thenable<ConnectionViewItem[]> {
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
33
src/webview/webview.controller.ts
Normal file
33
src/webview/webview.controller.ts
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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')) {
|
@ -8,6 +8,7 @@
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"experimentalDecorators": true,
|
||||
// 允许访问 resources 目录
|
||||
"paths": {
|
||||
"@resources/*": ["./resources/*"]
|
||||
|
Loading…
x
Reference in New Issue
Block a user