实现连接参数的局部保存
This commit is contained in:
parent
b2b80c1a3f
commit
5bac8f8726
23
package.json
23
package.json
@ -31,6 +31,20 @@
|
|||||||
"light": "./icons/light/protocol.svg",
|
"light": "./icons/light/protocol.svg",
|
||||||
"dark": "./icons/dark/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": {
|
"menus": {
|
||||||
@ -40,6 +54,13 @@
|
|||||||
"group": "navigation",
|
"group": "navigation",
|
||||||
"when": "editorLangId == python || editorLangId == javascript || editorLangId == typescript || editorLangId == java || editorLangId == csharp"
|
"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": {
|
"viewsContainers": {
|
||||||
@ -54,7 +75,7 @@
|
|||||||
"views": {
|
"views": {
|
||||||
"openmcp-sidebar": [
|
"openmcp-sidebar": [
|
||||||
{
|
{
|
||||||
"id": "openmcp.sidebar.connect",
|
"id": "openmcp.sidebar-view.workspace-connection",
|
||||||
"icon": "./icons/protocol.svg",
|
"icon": "./icons/protocol.svg",
|
||||||
"name": "MCP 连接",
|
"name": "MCP 连接",
|
||||||
"type": "tree"
|
"type": "tree"
|
||||||
|
@ -4,6 +4,7 @@ import * as fspath from 'path';
|
|||||||
import * as OpenMCPService from '../resources/service';
|
import * as OpenMCPService from '../resources/service';
|
||||||
import { getLaunchCWD, revealOpenMcpWebviewPanel } from './webview';
|
import { getLaunchCWD, revealOpenMcpWebviewPanel } from './webview';
|
||||||
import { registerSidebar } from './sidebar';
|
import { registerSidebar } from './sidebar';
|
||||||
|
import { getWorkspaceConnectionConfigItemByPath, ISSEConnectionItem, IStdioConnectionItem } from './global';
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
console.log('activate openmcp');
|
console.log('activate openmcp');
|
||||||
@ -16,25 +17,37 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
|
|
||||||
registerSidebar(context);
|
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(
|
context.subscriptions.push(
|
||||||
vscode.commands.registerCommand('openmcp.showOpenMCP', async (uri: vscode.Uri) => {
|
vscode.commands.registerCommand('openmcp.showOpenMCP', async (uri: vscode.Uri) => {
|
||||||
|
|
||||||
const cwd = getLaunchCWD(context, uri);
|
const connectionItem = getWorkspaceConnectionConfigItemByPath(uri.fsPath);
|
||||||
// 获取 uri 相对于 cwd 的路径
|
if (!connectionItem) {
|
||||||
const relativePath = fspath.relative(cwd, uri.fsPath);
|
// 项目不存在连接信息
|
||||||
|
const cwd = getLaunchCWD(context, uri);
|
||||||
|
|
||||||
// TODO: 实现从 connection.json 中读取配置,然后启动对应的 connection
|
// 获取 uri 相对于 cwd 的路径
|
||||||
const command = 'mcp';
|
const relativePath = fspath.relative(cwd, uri.fsPath);
|
||||||
const args = ['run', relativePath];
|
|
||||||
|
|
||||||
revealOpenMcpWebviewPanel(context, uri.fsPath, {
|
// TODO: 实现从 connection.json 中读取配置,然后启动对应的 connection
|
||||||
type: 'stdio',
|
const command = 'mcp';
|
||||||
name: 'OpenMCP',
|
const args = ['run', relativePath];
|
||||||
command,
|
|
||||||
args,
|
revealOpenMcpWebviewPanel(context, uri.fsPath, {
|
||||||
cwd
|
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[];
|
args: string[];
|
||||||
cwd?: string;
|
cwd?: string;
|
||||||
env?: { [key: string]: string };
|
env?: { [key: string]: string };
|
||||||
|
filePath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ISSEConnectionItem {
|
export interface ISSEConnectionItem {
|
||||||
@ -22,16 +23,29 @@ export interface ISSEConnectionItem {
|
|||||||
url: string;
|
url: string;
|
||||||
oauth?: string;
|
oauth?: string;
|
||||||
env?: { [key: string]: string };
|
env?: { [key: string]: string };
|
||||||
|
filePath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IConnectionConfig {
|
export interface IConnectionConfig {
|
||||||
items: (IStdioConnectionItem | ISSEConnectionItem)[];
|
items: (IStdioConnectionItem | ISSEConnectionItem)[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CONNECTION_CONFIG_NAME = 'openmcp_connection.json';
|
||||||
|
|
||||||
|
let _connectionConfig: IConnectionConfig | undefined;
|
||||||
|
let _workspaceConnectionConfig: IConnectionConfig | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取全局的连接信息,全局文件信息都是绝对路径
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
export function getConnectionConfig() {
|
export function getConnectionConfig() {
|
||||||
|
if (_connectionConfig) {
|
||||||
|
return _connectionConfig;
|
||||||
|
}
|
||||||
const homeDir = os.homedir();
|
const homeDir = os.homedir();
|
||||||
const configDir = fspath.join(homeDir, '.openmcp');
|
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)) {
|
if (!fs.existsSync(connectionConfig)) {
|
||||||
fs.mkdirSync(configDir, { recursive: true });
|
fs.mkdirSync(configDir, { recursive: true });
|
||||||
fs.writeFileSync(connectionConfig, JSON.stringify({ items: [] }), 'utf-8');
|
fs.writeFileSync(connectionConfig, JSON.stringify({ items: [] }), 'utf-8');
|
||||||
@ -39,5 +53,153 @@ export function getConnectionConfig() {
|
|||||||
|
|
||||||
const rawConnectionString = fs.readFileSync(connectionConfig, 'utf-8');
|
const rawConnectionString = fs.readFileSync(connectionConfig, 'utf-8');
|
||||||
const connection = JSON.parse(rawConnectionString) as IConnectionConfig;
|
const connection = JSON.parse(rawConnectionString) as IConnectionConfig;
|
||||||
|
_connectionConfig = connection;
|
||||||
return 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 * as vscode from 'vscode';
|
||||||
import { getConnectionConfig, ISSEConnectionItem, IStdioConnectionItem } from './global';
|
import { getConnectionConfig, getWorkspaceConnectionConfig } from './global';
|
||||||
import { revealOpenMcpWebviewPanel } from './webview';
|
|
||||||
|
|
||||||
export function registerSidebar(context: vscode.ExtensionContext) {
|
class McpWorkspaceConnectProvider implements vscode.TreeDataProvider<SidebarItem> {
|
||||||
|
private _onDidChangeTreeData: vscode.EventEmitter<SidebarItem | undefined | null | void> = new vscode.EventEmitter<SidebarItem | undefined | null | void>();
|
||||||
context.subscriptions.push(
|
readonly onDidChangeTreeData: vscode.Event<SidebarItem | undefined | null | void> = this._onDidChangeTreeData.event;
|
||||||
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> {
|
|
||||||
|
|
||||||
constructor(private context: vscode.ExtensionContext) {
|
constructor(private context: vscode.ExtensionContext) {
|
||||||
}
|
}
|
||||||
@ -33,21 +15,42 @@ class McpConnectProvider implements vscode.TreeDataProvider<SidebarItem> {
|
|||||||
|
|
||||||
getChildren(element?: SidebarItem): Thenable<SidebarItem[]> {
|
getChildren(element?: SidebarItem): Thenable<SidebarItem[]> {
|
||||||
// TODO: 读取 configDir 下的所有文件,作为子节点
|
// TODO: 读取 configDir 下的所有文件,作为子节点
|
||||||
const connection = getConnectionConfig();
|
const connection = getWorkspaceConnectionConfig();
|
||||||
const sidebarItems = connection.items.map((item, index) => {
|
const sidebarItems = connection.items.map((item, index) => {
|
||||||
return new SidebarItem(item.name, vscode.TreeItemCollapsibleState.None, {
|
return new SidebarItem(item.name, vscode.TreeItemCollapsibleState.None);
|
||||||
command: 'openmcp.sidebar.revealOpenMcpWebviewPanel',
|
|
||||||
title: 'OpenMCP',
|
|
||||||
arguments: [item]
|
|
||||||
}, 'server');
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 返回子节点
|
// 返回子节点
|
||||||
return Promise.resolve(sidebarItems);
|
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> {
|
class HelpProvider implements vscode.TreeDataProvider<SidebarItem> {
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
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 { ISSEConnectionItem, IStdioConnectionItem, panels } from './global';
|
import { getWorkspaceConnectionConfigItemByPath, ISSEConnectionItem, IStdioConnectionItem, panels, updateWorkspaceConnectionConfig } from './global';
|
||||||
import * as OpenMCPService from '../resources/service';
|
import * as OpenMCPService from '../resources/service';
|
||||||
|
|
||||||
|
|
||||||
@ -93,6 +93,8 @@ export function revealOpenMcpWebviewPanel(
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'connect':
|
||||||
|
updateWorkspaceConnectionConfig(panelKey, data);
|
||||||
default:
|
default:
|
||||||
OpenMCPService.messageController(command, data, panel.webview);
|
OpenMCPService.messageController(command, data, panel.webview);
|
||||||
break;
|
break;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user