实现连接参数的局部保存
This commit is contained in:
parent
b2b80c1a3f
commit
5bac8f8726
23
package.json
23
package.json
@ -31,6 +31,20 @@
|
||||
"light": "./icons/light/protocol.svg",
|
||||
"dark": "./icons/dark/protocol.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "openmcp.sidebar.workspace-connection.revealWebviewPanel",
|
||||
"title": "展示 OpenMCP",
|
||||
"category": "openmcp",
|
||||
"icon": {
|
||||
"light": "./icons/light/protocol.svg",
|
||||
"dark": "./icons/dark/protocol.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "openmcp.sidebar.workspace-connection.refresh",
|
||||
"title": "刷新",
|
||||
"category": "openmcp"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
@ -40,6 +54,13 @@
|
||||
"group": "navigation",
|
||||
"when": "editorLangId == python || editorLangId == javascript || editorLangId == typescript || editorLangId == java || editorLangId == csharp"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "openmcp.sidebar.workspace-connection.revealWebviewPanel",
|
||||
"group": "navigation",
|
||||
"when": "view == openmcp.sidebar-view.workspace-connection"
|
||||
}
|
||||
]
|
||||
},
|
||||
"viewsContainers": {
|
||||
@ -54,7 +75,7 @@
|
||||
"views": {
|
||||
"openmcp-sidebar": [
|
||||
{
|
||||
"id": "openmcp.sidebar.connect",
|
||||
"id": "openmcp.sidebar-view.workspace-connection",
|
||||
"icon": "./icons/protocol.svg",
|
||||
"name": "MCP 连接",
|
||||
"type": "tree"
|
||||
|
@ -4,6 +4,7 @@ import * as fspath from 'path';
|
||||
import * as OpenMCPService from '../resources/service';
|
||||
import { getLaunchCWD, revealOpenMcpWebviewPanel } from './webview';
|
||||
import { registerSidebar } from './sidebar';
|
||||
import { getWorkspaceConnectionConfigItemByPath, ISSEConnectionItem, IStdioConnectionItem } from './global';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
console.log('activate openmcp');
|
||||
@ -16,25 +17,37 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
registerSidebar(context);
|
||||
|
||||
// 注册 showOpenMCP 命令
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('openmcp.sidebar.workspace-connection.revealWebviewPanel', (item: IStdioConnectionItem | ISSEConnectionItem) => {
|
||||
revealOpenMcpWebviewPanel(context, item.name, item);
|
||||
})
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('openmcp.showOpenMCP', async (uri: vscode.Uri) => {
|
||||
|
||||
const cwd = getLaunchCWD(context, uri);
|
||||
// 获取 uri 相对于 cwd 的路径
|
||||
const relativePath = fspath.relative(cwd, uri.fsPath);
|
||||
|
||||
// TODO: 实现从 connection.json 中读取配置,然后启动对应的 connection
|
||||
const command = 'mcp';
|
||||
const args = ['run', relativePath];
|
||||
|
||||
revealOpenMcpWebviewPanel(context, uri.fsPath, {
|
||||
type: 'stdio',
|
||||
name: 'OpenMCP',
|
||||
command,
|
||||
args,
|
||||
cwd
|
||||
});
|
||||
const connectionItem = getWorkspaceConnectionConfigItemByPath(uri.fsPath);
|
||||
if (!connectionItem) {
|
||||
// 项目不存在连接信息
|
||||
const cwd = getLaunchCWD(context, uri);
|
||||
|
||||
// 获取 uri 相对于 cwd 的路径
|
||||
const relativePath = fspath.relative(cwd, uri.fsPath);
|
||||
|
||||
// TODO: 实现从 connection.json 中读取配置,然后启动对应的 connection
|
||||
const command = 'mcp';
|
||||
const args = ['run', relativePath];
|
||||
|
||||
revealOpenMcpWebviewPanel(context, uri.fsPath, {
|
||||
type: 'stdio',
|
||||
name: 'OpenMCP',
|
||||
command,
|
||||
args,
|
||||
cwd
|
||||
});
|
||||
} else {
|
||||
revealOpenMcpWebviewPanel(context, uri.fsPath, connectionItem);
|
||||
}
|
||||
|
||||
})
|
||||
);
|
||||
|
164
src/global.ts
164
src/global.ts
@ -14,6 +14,7 @@ export interface IStdioConnectionItem {
|
||||
args: string[];
|
||||
cwd?: string;
|
||||
env?: { [key: string]: string };
|
||||
filePath?: string;
|
||||
}
|
||||
|
||||
export interface ISSEConnectionItem {
|
||||
@ -22,16 +23,29 @@ export interface ISSEConnectionItem {
|
||||
url: string;
|
||||
oauth?: string;
|
||||
env?: { [key: string]: string };
|
||||
filePath?: string;
|
||||
}
|
||||
|
||||
export interface IConnectionConfig {
|
||||
items: (IStdioConnectionItem | ISSEConnectionItem)[];
|
||||
}
|
||||
|
||||
export const CONNECTION_CONFIG_NAME = 'openmcp_connection.json';
|
||||
|
||||
let _connectionConfig: IConnectionConfig | undefined;
|
||||
let _workspaceConnectionConfig: IConnectionConfig | undefined;
|
||||
|
||||
/**
|
||||
* @description 获取全局的连接信息,全局文件信息都是绝对路径
|
||||
* @returns
|
||||
*/
|
||||
export function getConnectionConfig() {
|
||||
if (_connectionConfig) {
|
||||
return _connectionConfig;
|
||||
}
|
||||
const homeDir = os.homedir();
|
||||
const configDir = fspath.join(homeDir, '.openmcp');
|
||||
const connectionConfig = fspath.join(configDir, 'connection.json');
|
||||
const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME);
|
||||
if (!fs.existsSync(connectionConfig)) {
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
fs.writeFileSync(connectionConfig, JSON.stringify({ items: [] }), 'utf-8');
|
||||
@ -39,5 +53,153 @@ export function getConnectionConfig() {
|
||||
|
||||
const rawConnectionString = fs.readFileSync(connectionConfig, 'utf-8');
|
||||
const connection = JSON.parse(rawConnectionString) as IConnectionConfig;
|
||||
_connectionConfig = connection;
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @description 获取工作区的连接信息,工作区的连接文件的路径都是相对路径,以 {workspace} 开头
|
||||
* @param workspace
|
||||
*/
|
||||
export function getWorkspaceConnectionConfig() {
|
||||
const workspace = getWorkspacePath();
|
||||
|
||||
if (_workspaceConnectionConfig) {
|
||||
return _workspaceConnectionConfig;
|
||||
}
|
||||
const configDir = fspath.join(workspace, '.vscode');
|
||||
const connectionConfig = fspath.join(configDir, CONNECTION_CONFIG_NAME);
|
||||
|
||||
if (!fs.existsSync(connectionConfig)) {
|
||||
fs.mkdirSync(configDir, { recursive: true });
|
||||
fs.writeFileSync(connectionConfig, JSON.stringify({ items: [] }), 'utf-8');
|
||||
}
|
||||
|
||||
const rawConnectionString = fs.readFileSync(connectionConfig, 'utf-8');
|
||||
const connection = JSON.parse(rawConnectionString) as IConnectionConfig;
|
||||
|
||||
const workspacePath = getWorkspacePath();
|
||||
for (const item of connection.items) {
|
||||
if (item.filePath && item.filePath.startsWith('{workspace}')) {
|
||||
item.filePath = item.filePath.replace('{workspace}', workspacePath).replace('\\', '/');
|
||||
}
|
||||
if (item.type === 'stdio' && item.cwd && item.cwd.startsWith('{workspace}')) {
|
||||
item.cwd = item.cwd.replace('{workspace}', workspacePath).replace('\\', '/');
|
||||
}
|
||||
}
|
||||
|
||||
_workspaceConnectionConfig = connection;
|
||||
return connection;
|
||||
}
|
||||
|
||||
export function saveWorkspaceConnectionConfig(workspace: string) {
|
||||
|
||||
if (!_workspaceConnectionConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
const connectionConfig = JSON.parse(JSON.stringify(_workspaceConnectionConfig)) as IConnectionConfig;
|
||||
|
||||
const configDir = fspath.join(workspace, '.vscode');
|
||||
const connectionConfigPath = fspath.join(configDir, CONNECTION_CONFIG_NAME);
|
||||
|
||||
const workspacePath = getWorkspacePath();
|
||||
for (const item of connectionConfig.items) {
|
||||
if (item.filePath && item.filePath.startsWith(workspacePath)) {
|
||||
item.filePath = item.filePath.replace(workspacePath, '{workspace}').replace('\\', '/');
|
||||
}
|
||||
if (item.type ==='stdio' && item.cwd && item.cwd.startsWith(workspacePath)) {
|
||||
item.cwd = item.cwd.replace(workspacePath, '{workspace}').replace('\\', '/');
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(connectionConfigPath, JSON.stringify(connectionConfig), 'utf-8');
|
||||
}
|
||||
|
||||
interface ClientStdioConnectionItem {
|
||||
command: string;
|
||||
args: string[];
|
||||
clientName: string;
|
||||
clientVersion: string;
|
||||
connectionType: 'STDIO';
|
||||
cwd: string;
|
||||
env: { [key: string]: string };
|
||||
}
|
||||
|
||||
interface ClientSseConnectionItem {
|
||||
url: string;
|
||||
clientName: string;
|
||||
clientVersion: string;
|
||||
connectionType: 'SSE';
|
||||
env: { [key: string]: string };
|
||||
}
|
||||
|
||||
export function updateWorkspaceConnectionConfig(absPath: string, data: ClientStdioConnectionItem | ClientSseConnectionItem) {
|
||||
const connectionItem = getWorkspaceConnectionConfigItemByPath(absPath);
|
||||
const workspaceConnectionConfig = getWorkspaceConnectionConfig();
|
||||
|
||||
// 如果存在,删除老的 connectionItem
|
||||
if (connectionItem) {
|
||||
const index = workspaceConnectionConfig.items.indexOf(connectionItem);
|
||||
if (index !== -1) {
|
||||
workspaceConnectionConfig.items.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.connectionType === 'STDIO') {
|
||||
const connectionItem: IStdioConnectionItem = {
|
||||
type:'stdio',
|
||||
name: data.clientName,
|
||||
command: data.command,
|
||||
args: data.args,
|
||||
cwd: data.cwd.replace('\\', '/'),
|
||||
env: data.env,
|
||||
filePath: absPath.replace('\\', '/')
|
||||
};
|
||||
|
||||
// 插入到第一个
|
||||
workspaceConnectionConfig.items.unshift(connectionItem);
|
||||
const workspacePath = getWorkspacePath();
|
||||
saveWorkspaceConnectionConfig(workspacePath);
|
||||
vscode.commands.executeCommand('openmcp.sidebar.workspace-connection.refresh');
|
||||
|
||||
} else {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function normaliseConnectionFilePath(item: IStdioConnectionItem | ISSEConnectionItem, workspace: string) {
|
||||
if (item.filePath) {
|
||||
if (item.filePath.startsWith('{workspace}')) {
|
||||
return item.filePath.replace('{workspace}', workspace).replace('\\', '/');
|
||||
} else {
|
||||
return item.filePath.replace('\\', '/');
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getWorkspacePath() {
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
return (workspaceFolder?.uri.fsPath || '').replace('\\', '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 根据输入的文件路径,获取该文件的 mcp 连接签名
|
||||
* @param absPath
|
||||
*/
|
||||
export function getWorkspaceConnectionConfigItemByPath(absPath: string) {
|
||||
const workspacePath = getWorkspacePath();
|
||||
const workspaceConnectionConfig = getWorkspaceConnectionConfig();
|
||||
|
||||
const normaliseAbsPath = absPath.replace('\\', '/');
|
||||
for (const item of workspaceConnectionConfig.items) {
|
||||
const filePath = normaliseConnectionFilePath(item, workspacePath);
|
||||
if (filePath === normaliseAbsPath) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
@ -1,27 +1,9 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { getConnectionConfig, ISSEConnectionItem, IStdioConnectionItem } from './global';
|
||||
import { revealOpenMcpWebviewPanel } from './webview';
|
||||
import { getConnectionConfig, getWorkspaceConnectionConfig } from './global';
|
||||
|
||||
export function registerSidebar(context: vscode.ExtensionContext) {
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('openmcp.sidebar.revealOpenMcpWebviewPanel', (item: IStdioConnectionItem | ISSEConnectionItem) => {
|
||||
revealOpenMcpWebviewPanel(context, item.name, item);
|
||||
})
|
||||
)
|
||||
|
||||
// 注册 MCP 连接的 sidebar 视图
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerTreeDataProvider('openmcp.sidebar.connect', new McpConnectProvider(context))
|
||||
);
|
||||
|
||||
// 注册 入门与帮助的 sidebar 视图
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerTreeDataProvider('openmcp.sidebar.help', new HelpProvider(context))
|
||||
);
|
||||
}
|
||||
|
||||
class McpConnectProvider implements vscode.TreeDataProvider<SidebarItem> {
|
||||
class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<SidebarItem> {
|
||||
private _onDidChangeTreeData: vscode.EventEmitter<SidebarItem | undefined | null | void> = new vscode.EventEmitter<SidebarItem | undefined | null | void>();
|
||||
readonly onDidChangeTreeData: vscode.Event<SidebarItem | undefined | null | void> = this._onDidChangeTreeData.event;
|
||||
|
||||
constructor(private context: vscode.ExtensionContext) {
|
||||
}
|
||||
@ -33,21 +15,42 @@ class McpConnectProvider implements vscode.TreeDataProvider<SidebarItem> {
|
||||
|
||||
getChildren(element?: SidebarItem): Thenable<SidebarItem[]> {
|
||||
// TODO: 读取 configDir 下的所有文件,作为子节点
|
||||
const connection = getConnectionConfig();
|
||||
const connection = getWorkspaceConnectionConfig();
|
||||
const sidebarItems = connection.items.map((item, index) => {
|
||||
return new SidebarItem(item.name, vscode.TreeItemCollapsibleState.None, {
|
||||
command: 'openmcp.sidebar.revealOpenMcpWebviewPanel',
|
||||
title: 'OpenMCP',
|
||||
arguments: [item]
|
||||
}, 'server');
|
||||
return new SidebarItem(item.name, vscode.TreeItemCollapsibleState.None);
|
||||
})
|
||||
|
||||
// 返回子节点
|
||||
return Promise.resolve(sidebarItems);
|
||||
}
|
||||
|
||||
// 添加 refresh 方法
|
||||
public refresh(): void {
|
||||
this._onDidChangeTreeData.fire();
|
||||
}
|
||||
}
|
||||
|
||||
// 在 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)
|
||||
);
|
||||
|
||||
// 注册 入门与帮助的 sidebar 视图
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerTreeDataProvider('openmcp.sidebar.help', new HelpProvider(context))
|
||||
);
|
||||
}
|
||||
|
||||
class HelpProvider implements vscode.TreeDataProvider<SidebarItem> {
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as fspath from 'path';
|
||||
import { ISSEConnectionItem, IStdioConnectionItem, panels } from './global';
|
||||
import { getWorkspaceConnectionConfigItemByPath, ISSEConnectionItem, IStdioConnectionItem, panels, updateWorkspaceConnectionConfig } from './global';
|
||||
import * as OpenMCPService from '../resources/service';
|
||||
|
||||
|
||||
@ -92,7 +92,9 @@ export function revealOpenMcpWebviewPanel(
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
|
||||
case 'connect':
|
||||
updateWorkspaceConnectionConfig(panelKey, data);
|
||||
default:
|
||||
OpenMCPService.messageController(command, data, panel.webview);
|
||||
break;
|
||||
|
Loading…
x
Reference in New Issue
Block a user