增加全局安装的 mcp 服务器控制面板

This commit is contained in:
锦恢 2025-05-02 03:07:29 +08:00
parent 96d029f906
commit dca2a9c820
6 changed files with 170 additions and 38 deletions

View File

@ -5,7 +5,7 @@
- 修复部分因为服务器名称特殊字符而导致的保存实效的错误
- 插件模式下左侧管理面板中的「MCP连接工作区」视图可以进行增删改查了
- 新增「MCP连接全局用于安装全局范围的 mcp server
- 增加引导页面
## [main] 0.0.5
- 支持对已经打开过的文件项目进行管理

View File

@ -6,7 +6,6 @@ import * as fs from 'fs';
export type FsPath = string;
export const panels = new Map<FsPath, vscode.WebviewPanel>();
export interface IStdioConnectionItem {
type: 'stdio';
name: string;
@ -133,6 +132,27 @@ export function getWorkspaceConnectionConfig() {
return connection;
}
export function getInstalledConnectionConfigPath() {
const homeDir = os.homedir();
const configDir = fspath.join(homeDir, '.openmcp');
const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME);
return connectionConfig;
}
/**
* @description
* @returns
*/
export function saveConnectionConfig() {
if (!_connectionConfig) {
return;
}
const connectionConfig = getInstalledConnectionConfigPath();
fs.writeFileSync(connectionConfig, JSON.stringify(_connectionConfig, null, 2), 'utf-8');
}
export function saveWorkspaceConnectionConfig(workspace: string) {
if (!_workspaceConnectionConfig) {

View File

@ -1,7 +1,8 @@
import * as vscode from 'vscode';
import { RegisterCommand, RegisterTreeDataProvider } from '../common';
import { getWorkspaceConnectionConfig, getWorkspacePath, panels, saveWorkspaceConnectionConfig } from '../global';
import { ConnectionViewItem } from './common';
import { getConnectionConfig, getInstalledConnectionConfigPath, saveConnectionConfig } from '../global';
import { acquireInstalledConnection, deleteInstalledConnection } from './installed.service';
@RegisterTreeDataProvider('openmcp.sidebar.installed-connection')
export class McpInstalledConnectProvider implements vscode.TreeDataProvider<ConnectionViewItem> {
@ -17,7 +18,7 @@ export class McpInstalledConnectProvider implements vscode.TreeDataProvider<Conn
getChildren(element?: ConnectionViewItem): Thenable<ConnectionViewItem[]> {
// TODO: 读取 configDir 下的所有文件,作为子节点
const connection = getWorkspaceConnectionConfig();
const connection = getConnectionConfig();
const sidebarItems = connection.items.map((item, index) => {
// 连接的名字
const itemName = `${item.name} (${item.type})`
@ -36,49 +37,31 @@ export class McpInstalledConnectProvider implements vscode.TreeDataProvider<Conn
@RegisterCommand('addConnection')
public async addConnection(context: vscode.ExtensionContext) {
const item = await acquireInstalledConnection();
if (!item) {
return;
}
const connectionConfig = getConnectionConfig();
connectionConfig.items.push(item);
saveConnectionConfig();
// 刷新侧边栏视图
vscode.commands.executeCommand('openmcp.sidebar.installed-connection.refresh');
}
@RegisterCommand('openConfiguration')
public async openConfiguration(context: vscode.ExtensionContext, view: ConnectionViewItem) {
const item = view.item;
const uri = vscode.Uri.file(item.filePath || item.name);
const configPath = getInstalledConnectionConfigPath();
const uri = vscode.Uri.file(configPath);
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();
}
}
await deleteInstalledConnection(connectionItem);
}
}

View File

@ -0,0 +1,127 @@
import { getConnectionConfig, IConnectionItem, panels, saveConnectionConfig } from "../global";
import * as vscode from 'vscode';
export async function deleteInstalledConnection(item: IConnectionItem) {
// 弹出确认对话框
const confirm = await vscode.window.showWarningMessage(
`确定要删除连接 "${item.name}" 吗?`,
{ modal: true },
'确定'
);
if (confirm !== '确定') {
return; // 用户取消删除
}
const installedConnection = getConnectionConfig();
// 从配置中移除该连接项
const index = installedConnection.items.indexOf(item);
if (index !== -1) {
installedConnection.items.splice(index, 1);
// 保存更新后的配置
saveConnectionConfig();
// 刷新侧边栏视图
vscode.commands.executeCommand('openmcp.sidebar.installed-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]}`);
}
}
export async function acquireInstalledConnection(): 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 || ''
}
}
}

View File

@ -1,6 +1,6 @@
import * as vscode from 'vscode';
import { RegisterCommand, RegisterTreeDataProvider } from '../common';
import { getWorkspaceConnectionConfig, getWorkspaceConnectionConfigPath, getWorkspacePath, panels, saveWorkspaceConnectionConfig } from '../global';
import { getWorkspaceConnectionConfig, getWorkspaceConnectionConfigPath, getWorkspacePath, saveWorkspaceConnectionConfig } from '../global';
import { ConnectionViewItem } from './common';
import { revealOpenMcpWebviewPanel } from '../webview/webview.service';
import { acquireUserCustomConnection, deleteUserConnection } from './workspace.service';

View File

@ -2,6 +2,8 @@ import { getWorkspaceConnectionConfig, getWorkspacePath, IConnectionItem, panels
import * as vscode from 'vscode';
export async function acquireUserCustomConnection(): Promise<IConnectionItem | undefined> {
// 让用户选择连接类型
const connectionType = await vscode.window.showQuickPick(['stdio', 'sse'], {