push 支持基本的 MCP 项目管理 to 70%

This commit is contained in:
锦恢 2025-04-22 21:58:07 +08:00
parent 31a25f27bc
commit b2b80c1a3f
7 changed files with 264 additions and 75 deletions

View File

@ -40,7 +40,7 @@
|---------|---------|--------|---------|-----------|
| `all` | 完成最基本的各类基础设施 | `完整版本` | 100% | `Done` |
| `render` | chat 模式下支持进行成本分析 | `迭代版本` | 100% | `Done` |
| `ext` | 支持基本的 MCP 项目管理 | `MVP` | 0% | `P0` |
| `ext` | 支持基本的 MCP 项目管理 | `MVP` | 70% | `P0` |
| `service` | 支持自定义支持 openai 接口协议的大模型接入 | `完整版本` | 100% | `Done` |
| `service` | 支持自定义接口协议的大模型接入 | `MVP` | 0% | `P1` |
| `all` | 支持同时调试多个 MCP Server | `MVP` | 0% | `P1` |

View File

@ -9,5 +9,9 @@ npm i
node patch-mcp-sdk.js
Set-Location ..
Set-Location servers
uv sync
Set-Location ..
# 安装根目录依赖
npm i

View File

@ -54,10 +54,16 @@
"views": {
"openmcp-sidebar": [
{
"id": "webview-sidebar.view",
"id": "openmcp.sidebar.connect",
"icon": "./icons/protocol.svg",
"name": "chatbot",
"type": "webview"
"name": "MCP 连接",
"type": "tree"
},
{
"id": "openmcp.sidebar.help",
"icon": "./icons/protocol.svg",
"name": "入门与帮助",
"type": "tree"
}
]
}

View File

@ -1,10 +1,9 @@
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as fspath from 'path';
import * as OpenMCPService from '../resources/service';
import { getLaunchCWD, getWebviewContent } from './webview';
import { panels } from './global';
import { getLaunchCWD, revealOpenMcpWebviewPanel } from './webview';
import { registerSidebar } from './sidebar';
export function activate(context: vscode.ExtensionContext) {
console.log('activate openmcp');
@ -15,81 +14,28 @@ export function activate(context: vscode.ExtensionContext) {
const workspace = workspaceFolder?.uri.fsPath || '';
OpenMCPService.setVscodeWorkspace(workspace);
registerSidebar(context);
// 注册 showOpenMCP 命令
context.subscriptions.push(
vscode.commands.registerCommand('openmcp.showOpenMCP', async (uri: vscode.Uri) => {
if (panels.has(uri.fsPath)) {
const panel = panels.get(uri.fsPath);
panel?.reveal();
return;
}
const panel = vscode.window.createWebviewPanel(
'OpenMCP',
'OpenMCP',
vscode.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true,
enableFindWidget: true
}
);
panels.set(uri.fsPath, panel);
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
});
console.log('current file' + uri.fsPath);
console.log(`relativePath: ${relativePath}`);
// 根据 relativePath 先去 setting 中进行选择
// 设置HTML内容
const html = getWebviewContent(context, panel);
panel.webview.html = html || '';
panel.iconPath = vscode.Uri.file(fspath.join(context.extensionPath, 'resources', 'renderer', 'images', 'openmcp.png'));
// 处理来自webview的消息
panel.webview.onDidReceiveMessage(message => {
const { command, data } = message;
console.log('receive message', message);
// 拦截消息,注入额外信息
switch (command) {
case 'vscode/launch-command':
const commandString = 'mcp run ' + relativePath;
const launchResult = {
code: 200,
msg: {
commandString: commandString,
cwd: cwd
}
}
panel.webview.postMessage({
command: 'vscode/launch-command',
data: launchResult
});
break;
default:
OpenMCPService.messageController(command, data, panel.webview);
break;
}
});
panel.onDidDispose(async () => {
// 删除
panels.delete(uri.fsPath);
// 退出
panel.dispose();
});
})
);
}

View File

@ -1,4 +1,43 @@
import * as vscode from 'vscode';
import * as os from 'os';
import * as fspath from 'path';
import * as fs from 'fs';
export type FsPath = string;
export const panels = new Map<FsPath, vscode.WebviewPanel>();
export const panels = new Map<FsPath, vscode.WebviewPanel>();
export interface IStdioConnectionItem {
type: 'stdio';
name: string;
command: string;
args: string[];
cwd?: string;
env?: { [key: string]: string };
}
export interface ISSEConnectionItem {
type: 'sse';
name: string;
url: string;
oauth?: string;
env?: { [key: string]: string };
}
export interface IConnectionConfig {
items: (IStdioConnectionItem | ISSEConnectionItem)[];
}
export function getConnectionConfig() {
const homeDir = os.homedir();
const configDir = fspath.join(homeDir, '.openmcp');
const connectionConfig = fspath.join(configDir, 'connection.json');
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;
return connection;
}

105
src/sidebar.ts Normal file
View File

@ -0,0 +1,105 @@
import * as vscode from 'vscode';
import { getConnectionConfig, ISSEConnectionItem, IStdioConnectionItem } from './global';
import { revealOpenMcpWebviewPanel } from './webview';
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> {
constructor(private context: vscode.ExtensionContext) {
}
// 实现 TreeDataProvider 接口
getTreeItem(element: SidebarItem): vscode.TreeItem {
return element;
}
getChildren(element?: SidebarItem): Thenable<SidebarItem[]> {
// TODO: 读取 configDir 下的所有文件,作为子节点
const connection = getConnectionConfig();
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 Promise.resolve(sidebarItems);
}
}
class HelpProvider implements vscode.TreeDataProvider<SidebarItem> {
constructor(private context: vscode.ExtensionContext) {
}
// 实现 TreeDataProvider 接口
getTreeItem(element: SidebarItem): vscode.TreeItem {
return element;
}
getChildren(element?: SidebarItem): Thenable<SidebarItem[]> {
// 返回子节点
return Promise.resolve([
new SidebarItem('入门', vscode.TreeItemCollapsibleState.None, {
command: 'vscode.open',
title: 'Open Guide',
arguments: [vscode.Uri.parse('https://zhuanlan.zhihu.com/p/1894785817186121106')]
}, 'book'),
new SidebarItem('阅读文档', vscode.TreeItemCollapsibleState.None, {
command: 'vscode.open',
title: 'Open Documentation',
arguments: [vscode.Uri.parse('https://document.kirigaya.cn/blogs/openmcp/main.html')]
}, 'file-text'),
new SidebarItem('报告问题', vscode.TreeItemCollapsibleState.None, {
command: 'vscode.open',
title: 'Report Issue',
arguments: [vscode.Uri.parse('https://github.com/LSTM-Kirigaya/openmcp-client/issues')]
}, 'bug'),
new SidebarItem('参与项目', vscode.TreeItemCollapsibleState.None, {
command: 'vscode.open',
title: 'Join Project',
arguments: [vscode.Uri.parse('https://qm.qq.com/cgi-bin/qm/qr?k=C6ZUTZvfqWoI12lWe7L93cWa1hUsuVT0&jump_from=webapi&authKey=McW6B1ogTPjPDrCyGttS890tMZGQ1KB3QLuG4aqVNRaYp4vlTSgf2c6dMcNjMuBD')]
}, 'organization'),
new SidebarItem('评论插件', vscode.TreeItemCollapsibleState.None, {
command: 'vscode.open',
title: 'Review Extension',
arguments: [vscode.Uri.parse('https://marketplace.visualstudio.com/items?itemName=kirigaya.openmcp&ssr=false#review-details')]
}, 'feedback')
]);
}
}
class SidebarItem extends vscode.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly command?: vscode.Command,
public readonly icon?: string
) {
super(label, collapsibleState);
this.command = command;
this.iconPath = new vscode.ThemeIcon(icon || 'circle-outline');
}
}

View File

@ -1,6 +1,9 @@
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as fspath from 'path';
import { ISSEConnectionItem, IStdioConnectionItem, panels } from './global';
import * as OpenMCPService from '../resources/service';
export function getWebviewContent(context: vscode.ExtensionContext, panel: vscode.WebviewPanel): string | undefined {
const viewRoot = fspath.join(context.extensionPath, 'resources', 'renderer');
@ -21,3 +24,89 @@ export function getLaunchCWD(context: vscode.ExtensionContext, uri: vscode.Uri)
const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri);
return workspaceFolder?.uri.fsPath || '';
}
export function revealOpenMcpWebviewPanel(
context: vscode.ExtensionContext,
panelKey: string,
option: (IStdioConnectionItem | ISSEConnectionItem) = {
type: 'stdio',
name: 'OpenMCP',
command: 'mcp',
args: ['run', 'main.py']
}
) {
if (panels.has(panelKey)) {
const panel = panels.get(panelKey);
panel?.reveal();
return panel;
}
const panel = vscode.window.createWebviewPanel(
'OpenMCP',
'OpenMCP',
vscode.ViewColumn.One,
{
enableScripts: true,
retainContextWhenHidden: true,
enableFindWidget: true
}
);
panels.set(panelKey, panel);
// 设置HTML内容
const html = getWebviewContent(context, panel);
panel.webview.html = html || '';
panel.iconPath = vscode.Uri.file(fspath.join(context.extensionPath, 'resources', 'renderer', 'images', 'openmcp.png'));
// 处理来自webview的消息
panel.webview.onDidReceiveMessage(message => {
const { command, data } = message;
console.log('receive message', message);
// 拦截消息,注入额外信息
switch (command) {
case 'vscode/launch-command':
const laucnResultMessage = option.type === 'stdio' ?
{
type: 'stdio',
commandString: option.command + ' ' + option.args.join(' '),
cwd: option.cwd
} :
{
type: 'sse',
url: option.url,
oauth: option.oauth
};
const launchResult = {
code: 200,
msg: laucnResultMessage
};
panel.webview.postMessage({
command: 'vscode/launch-command',
data: launchResult
});
break;
default:
OpenMCPService.messageController(command, data, panel.webview);
break;
}
});
panel.onDidDispose(async () => {
// 删除
panels.delete(panelKey);
// 退出
panel.dispose();
});
return panel;
}