增加全局安装的 mcp 服务器控制面板
This commit is contained in:
parent
96d029f906
commit
dca2a9c820
@ -5,7 +5,7 @@
|
|||||||
- 修复部分因为服务器名称特殊字符而导致的保存实效的错误
|
- 修复部分因为服务器名称特殊字符而导致的保存实效的错误
|
||||||
- 插件模式下,左侧管理面板中的「MCP连接(工作区)」视图可以进行增删改查了
|
- 插件模式下,左侧管理面板中的「MCP连接(工作区)」视图可以进行增删改查了
|
||||||
- 新增「MCP连接(全局)」,用于安装全局范围的 mcp server
|
- 新增「MCP连接(全局)」,用于安装全局范围的 mcp server
|
||||||
|
- 增加引导页面
|
||||||
|
|
||||||
## [main] 0.0.5
|
## [main] 0.0.5
|
||||||
- 支持对已经打开过的文件项目进行管理
|
- 支持对已经打开过的文件项目进行管理
|
||||||
|
@ -6,7 +6,6 @@ import * as fs from 'fs';
|
|||||||
export type FsPath = string;
|
export type FsPath = string;
|
||||||
export const panels = new Map<FsPath, vscode.WebviewPanel>();
|
export const panels = new Map<FsPath, vscode.WebviewPanel>();
|
||||||
|
|
||||||
|
|
||||||
export interface IStdioConnectionItem {
|
export interface IStdioConnectionItem {
|
||||||
type: 'stdio';
|
type: 'stdio';
|
||||||
name: string;
|
name: string;
|
||||||
@ -133,6 +132,27 @@ export function getWorkspaceConnectionConfig() {
|
|||||||
return connection;
|
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) {
|
export function saveWorkspaceConnectionConfig(workspace: string) {
|
||||||
|
|
||||||
if (!_workspaceConnectionConfig) {
|
if (!_workspaceConnectionConfig) {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { RegisterCommand, RegisterTreeDataProvider } from '../common';
|
import { RegisterCommand, RegisterTreeDataProvider } from '../common';
|
||||||
import { getWorkspaceConnectionConfig, getWorkspacePath, panels, saveWorkspaceConnectionConfig } from '../global';
|
|
||||||
import { ConnectionViewItem } from './common';
|
import { ConnectionViewItem } from './common';
|
||||||
|
import { getConnectionConfig, getInstalledConnectionConfigPath, saveConnectionConfig } from '../global';
|
||||||
|
import { acquireInstalledConnection, deleteInstalledConnection } from './installed.service';
|
||||||
|
|
||||||
@RegisterTreeDataProvider('openmcp.sidebar.installed-connection')
|
@RegisterTreeDataProvider('openmcp.sidebar.installed-connection')
|
||||||
export class McpInstalledConnectProvider implements vscode.TreeDataProvider<ConnectionViewItem> {
|
export class McpInstalledConnectProvider implements vscode.TreeDataProvider<ConnectionViewItem> {
|
||||||
@ -17,7 +18,7 @@ export class McpInstalledConnectProvider implements vscode.TreeDataProvider<Conn
|
|||||||
|
|
||||||
getChildren(element?: ConnectionViewItem): Thenable<ConnectionViewItem[]> {
|
getChildren(element?: ConnectionViewItem): Thenable<ConnectionViewItem[]> {
|
||||||
// TODO: 读取 configDir 下的所有文件,作为子节点
|
// TODO: 读取 configDir 下的所有文件,作为子节点
|
||||||
const connection = getWorkspaceConnectionConfig();
|
const connection = getConnectionConfig();
|
||||||
const sidebarItems = connection.items.map((item, index) => {
|
const sidebarItems = connection.items.map((item, index) => {
|
||||||
// 连接的名字
|
// 连接的名字
|
||||||
const itemName = `${item.name} (${item.type})`
|
const itemName = `${item.name} (${item.type})`
|
||||||
@ -36,49 +37,31 @@ export class McpInstalledConnectProvider implements vscode.TreeDataProvider<Conn
|
|||||||
|
|
||||||
@RegisterCommand('addConnection')
|
@RegisterCommand('addConnection')
|
||||||
public async addConnection(context: vscode.ExtensionContext) {
|
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')
|
@RegisterCommand('openConfiguration')
|
||||||
public async openConfiguration(context: vscode.ExtensionContext, view: ConnectionViewItem) {
|
public async openConfiguration(context: vscode.ExtensionContext, view: ConnectionViewItem) {
|
||||||
const item = view.item;
|
const configPath = getInstalledConnectionConfigPath();
|
||||||
const uri = vscode.Uri.file(item.filePath || item.name);
|
const uri = vscode.Uri.file(configPath);
|
||||||
vscode.commands.executeCommand('vscode.open', uri);
|
vscode.commands.executeCommand('vscode.open', uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RegisterCommand('deleteConnection')
|
@RegisterCommand('deleteConnection')
|
||||||
public async deleteConnection(context: vscode.ExtensionContext, view: ConnectionViewItem) {
|
public async deleteConnection(context: vscode.ExtensionContext, view: ConnectionViewItem) {
|
||||||
const workspaceConnectionConfig = getWorkspaceConnectionConfig();
|
|
||||||
const connectionItem = view.item;
|
const connectionItem = view.item;
|
||||||
|
await deleteInstalledConnection(connectionItem);
|
||||||
// 弹出确认对话框
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
127
src/sidebar/installed.service.ts
Normal file
127
src/sidebar/installed.service.ts
Normal 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 || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { RegisterCommand, RegisterTreeDataProvider } from '../common';
|
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 { ConnectionViewItem } from './common';
|
||||||
import { revealOpenMcpWebviewPanel } from '../webview/webview.service';
|
import { revealOpenMcpWebviewPanel } from '../webview/webview.service';
|
||||||
import { acquireUserCustomConnection, deleteUserConnection } from './workspace.service';
|
import { acquireUserCustomConnection, deleteUserConnection } from './workspace.service';
|
||||||
|
@ -2,6 +2,8 @@ import { getWorkspaceConnectionConfig, getWorkspacePath, IConnectionItem, panels
|
|||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function acquireUserCustomConnection(): Promise<IConnectionItem | undefined> {
|
export async function acquireUserCustomConnection(): Promise<IConnectionItem | undefined> {
|
||||||
// 让用户选择连接类型
|
// 让用户选择连接类型
|
||||||
const connectionType = await vscode.window.showQuickPick(['stdio', 'sse'], {
|
const connectionType = await vscode.window.showQuickPick(['stdio', 'sse'], {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user