更新插件端架构
This commit is contained in:
parent
34936d944d
commit
c1b06313d7
65
package.json
65
package.json
@ -64,6 +64,30 @@
|
|||||||
"title": "打开配置",
|
"title": "打开配置",
|
||||||
"category": "openmcp",
|
"category": "openmcp",
|
||||||
"icon": "$(gear)"
|
"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": {
|
"menus": {
|
||||||
@ -78,24 +102,39 @@
|
|||||||
{
|
{
|
||||||
"command": "openmcp.sidebar.workspace-connection.refresh",
|
"command": "openmcp.sidebar.workspace-connection.refresh",
|
||||||
"group": "navigation",
|
"group": "navigation",
|
||||||
"when": "view == openmcp.sidebar-view.workspace-connection"
|
"when": "view == openmcp.sidebar.workspace-connection"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "openmcp.sidebar.workspace-connection.addConnection",
|
"command": "openmcp.sidebar.workspace-connection.addConnection",
|
||||||
"group": "navigation",
|
"group": "navigation",
|
||||||
"when": "view == openmcp.sidebar-view.workspace-connection"
|
"when": "view == openmcp.sidebar.workspace-connection"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "openmcp.sidebar.workspace-connection.openConfiguration",
|
"command": "openmcp.sidebar.workspace-connection.openConfiguration",
|
||||||
"group": "navigation",
|
"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": [
|
"view/item/context": [
|
||||||
{
|
{
|
||||||
"command": "openmcp.sidebar.workspace-connection.revealWebviewPanel",
|
"command": "openmcp.sidebar.workspace-connection.revealWebviewPanel",
|
||||||
"group": "inline@1",
|
"group": "inline@1",
|
||||||
"when": "view == openmcp.sidebar-view.workspace-connection",
|
"when": "view == openmcp.sidebar.workspace-connection || view == openmcp.sidebar.installed-connection",
|
||||||
"args": {
|
"args": {
|
||||||
"view": "${viewItem}"
|
"view": "${viewItem}"
|
||||||
}
|
}
|
||||||
@ -103,7 +142,15 @@
|
|||||||
{
|
{
|
||||||
"command": "openmcp.sidebar.workspace-connection.deleteConnection",
|
"command": "openmcp.sidebar.workspace-connection.deleteConnection",
|
||||||
"group": "inline@2",
|
"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": {
|
"args": {
|
||||||
"view": "${viewItem}"
|
"view": "${viewItem}"
|
||||||
}
|
}
|
||||||
@ -122,11 +169,17 @@
|
|||||||
"views": {
|
"views": {
|
||||||
"openmcp-sidebar": [
|
"openmcp-sidebar": [
|
||||||
{
|
{
|
||||||
"id": "openmcp.sidebar-view.workspace-connection",
|
"id": "openmcp.sidebar.workspace-connection",
|
||||||
"icon": "./icons/protocol.svg",
|
"icon": "./icons/protocol.svg",
|
||||||
"name": "MCP 连接 (工作区)",
|
"name": "MCP 连接 (工作区)",
|
||||||
"type": "tree"
|
"type": "tree"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "openmcp.sidebar.installed-connection",
|
||||||
|
"icon": "./icons/protocol.svg",
|
||||||
|
"name": "安装的 MCP 服务器",
|
||||||
|
"type": "tree"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "openmcp.sidebar.help",
|
"id": "openmcp.sidebar.help",
|
||||||
"icon": "./icons/protocol.svg",
|
"icon": "./icons/protocol.svg",
|
||||||
|
@ -1,22 +1,41 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { registerCommands, registerTreeDataProviders } from '.';
|
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 = [
|
export const InstallModules = [
|
||||||
|
McpWorkspaceConnectProvider,
|
||||||
|
McpInstalledConnectProvider,
|
||||||
|
HelpProvider,
|
||||||
|
WebviewController
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const registerSingles = new Map<string, any>();
|
||||||
|
|
||||||
export function launch(context: vscode.ExtensionContext) {
|
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) {
|
for (const [providerId, value] of registerTreeDataProviders) {
|
||||||
|
const provider = new value.providerConstructor(context);
|
||||||
|
|
||||||
|
registerSingles.set(providerId, provider);
|
||||||
|
|
||||||
context.subscriptions.push(
|
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 {
|
export interface IRegisterCommandItem {
|
||||||
handler: (context: ExtensionContext, ...args: any[]) => void;
|
handler: (context: ExtensionContext, ...args: any[]) => any;
|
||||||
options?: any;
|
options?: any;
|
||||||
|
target: any;
|
||||||
|
propertyKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CommandHandlerDescriptor = CustomDescriptor<IRegisterCommandItem['handler']>;
|
export type CommandHandlerDescriptor = CustomDescriptor<IRegisterCommandItem['handler']>;
|
||||||
|
|
||||||
export interface IRegisterTreeDataProviderItem<T> {
|
export interface IRegisterTreeDataProviderItem<T> {
|
||||||
provider: TreeDataProvider<T>;
|
providerConstructor: TreeDataProviderConstructor<T>;
|
||||||
options?: any;
|
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 const registerTreeDataProviders = new Map<string, IRegisterTreeDataProviderItem<any>>();
|
||||||
|
|
||||||
export function RegisterCommand(command: string, options?: any) {
|
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;
|
const handler = descriptor.value;
|
||||||
|
|
||||||
// 根据 option 进行的操作
|
console.log(propertyKey);
|
||||||
// ...
|
console.log(descriptor);
|
||||||
|
|
||||||
|
|
||||||
if (handler) {
|
if (handler) {
|
||||||
registerCommands.set(command, { handler, options });
|
registerCommands.push([command, { handler, target, propertyKey, options }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return descriptor;
|
return descriptor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RegisterTreeDataProvider<T>(command: string, options?: any) {
|
export function RegisterTreeDataProvider<T>(providerId: string, options?: any) {
|
||||||
return function(target: any, propertyKey: string, descriptor: TreeDataProviderDescriptor<T>) {
|
return function (target: TreeDataProviderConstructor<T>) {
|
||||||
const provider = descriptor.value;
|
|
||||||
|
|
||||||
// 根据 option 进行的操作
|
target.prototype.__openmcp_namespace = providerId;
|
||||||
// ...
|
|
||||||
|
|
||||||
if (provider) {
|
registerTreeDataProviders.set(providerId, {
|
||||||
registerTreeDataProviders.set(command, { provider, options });
|
providerConstructor: target,
|
||||||
}
|
options
|
||||||
|
});
|
||||||
return descriptor;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,10 +1,6 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
import * as OpenMCPService from '../resources/service';
|
import * as OpenMCPService from '../resources/service';
|
||||||
import { getDefaultLanunchSigature, getLaunchCWD, revealOpenMcpWebviewPanel } from './webview';
|
import { launch } from './common/entry';
|
||||||
import { registerSidebar } from './sidebar';
|
|
||||||
import { getWorkspaceConnectionConfigItemByPath } from './global';
|
|
||||||
import type { ConnectionViewItem } from './sidebar/common';
|
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
console.log('activate openmcp');
|
console.log('activate openmcp');
|
||||||
@ -15,49 +11,7 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
const workspace = workspaceFolder?.uri.fsPath || '';
|
const workspace = workspaceFolder?.uri.fsPath || '';
|
||||||
OpenMCPService.setVscodeWorkspace(workspace);
|
OpenMCPService.setVscodeWorkspace(workspace);
|
||||||
|
|
||||||
registerSidebar(context);
|
launch(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,17 +81,27 @@ export function getConnectionConfig() {
|
|||||||
return connection;
|
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} 开头
|
* @description 获取工作区的连接信息,工作区的连接文件的路径都是相对路径,以 {workspace} 开头
|
||||||
* @param workspace
|
* @param workspace
|
||||||
*/
|
*/
|
||||||
export function getWorkspaceConnectionConfig() {
|
export function getWorkspaceConnectionConfig() {
|
||||||
const workspace = getWorkspacePath();
|
|
||||||
|
|
||||||
if (_workspaceConnectionConfig) {
|
if (_workspaceConnectionConfig) {
|
||||||
return _workspaceConnectionConfig;
|
return _workspaceConnectionConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const workspace = getWorkspacePath();
|
||||||
const configDir = fspath.join(workspace, '.vscode');
|
const configDir = fspath.join(workspace, '.vscode');
|
||||||
const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME);
|
const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME);
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { SidebarItem } from './common';
|
import { SidebarItem } from './common';
|
||||||
|
import { RegisterTreeDataProvider } from '../common';
|
||||||
|
|
||||||
|
@RegisterTreeDataProvider('openmcp.sidebar.help')
|
||||||
export class HelpProvider implements vscode.TreeDataProvider<SidebarItem> {
|
export class HelpProvider implements vscode.TreeDataProvider<SidebarItem> {
|
||||||
|
|
||||||
constructor(private context: vscode.ExtensionContext) {
|
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 vscode from 'vscode';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as fspath from 'path';
|
import * as fspath from 'path';
|
||||||
import { getWorkspaceConnectionConfig, getWorkspacePath, IConnectionItem, ILaunchSigature, panels, saveWorkspaceConnectionConfig, updateWorkspaceConnectionConfig } from './global';
|
import { IConnectionItem, ILaunchSigature, panels, updateWorkspaceConnectionConfig } from '../global';
|
||||||
import * as OpenMCPService from '../resources/service';
|
import * as OpenMCPService from '../../resources/service';
|
||||||
|
|
||||||
|
|
||||||
export function getWebviewContent(context: vscode.ExtensionContext, panel: vscode.WebviewPanel): string | undefined {
|
export function getWebviewContent(context: vscode.ExtensionContext, panel: vscode.WebviewPanel): string | undefined {
|
||||||
const viewRoot = fspath.join(context.extensionPath, 'resources', 'renderer');
|
const viewRoot = fspath.join(context.extensionPath, 'resources', 'renderer');
|
||||||
@ -114,7 +113,7 @@ export function revealOpenMcpWebviewPanel(
|
|||||||
return panel;
|
return panel;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultLanunchSigature(path: string, cwd: string) {
|
export function getDefaultLanunchSignature(path: string, cwd: string) {
|
||||||
const relativePath = fspath.relative(cwd, path);
|
const relativePath = fspath.relative(cwd, path);
|
||||||
|
|
||||||
if (relativePath.endsWith('.py')) {
|
if (relativePath.endsWith('.py')) {
|
@ -8,6 +8,7 @@
|
|||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
// 允许访问 resources 目录
|
// 允许访问 resources 目录
|
||||||
"paths": {
|
"paths": {
|
||||||
"@resources/*": ["./resources/*"]
|
"@resources/*": ["./resources/*"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user