push 支持基本的 MCP 项目管理 to 70%
This commit is contained in:
parent
31a25f27bc
commit
b2b80c1a3f
@ -40,7 +40,7 @@
|
|||||||
|---------|---------|--------|---------|-----------|
|
|---------|---------|--------|---------|-----------|
|
||||||
| `all` | 完成最基本的各类基础设施 | `完整版本` | 100% | `Done` |
|
| `all` | 完成最基本的各类基础设施 | `完整版本` | 100% | `Done` |
|
||||||
| `render` | chat 模式下支持进行成本分析 | `迭代版本` | 100% | `Done` |
|
| `render` | chat 模式下支持进行成本分析 | `迭代版本` | 100% | `Done` |
|
||||||
| `ext` | 支持基本的 MCP 项目管理 | `MVP` | 0% | `P0` |
|
| `ext` | 支持基本的 MCP 项目管理 | `MVP` | 70% | `P0` |
|
||||||
| `service` | 支持自定义支持 openai 接口协议的大模型接入 | `完整版本` | 100% | `Done` |
|
| `service` | 支持自定义支持 openai 接口协议的大模型接入 | `完整版本` | 100% | `Done` |
|
||||||
| `service` | 支持自定义接口协议的大模型接入 | `MVP` | 0% | `P1` |
|
| `service` | 支持自定义接口协议的大模型接入 | `MVP` | 0% | `P1` |
|
||||||
| `all` | 支持同时调试多个 MCP Server | `MVP` | 0% | `P1` |
|
| `all` | 支持同时调试多个 MCP Server | `MVP` | 0% | `P1` |
|
||||||
|
@ -9,5 +9,9 @@ npm i
|
|||||||
node patch-mcp-sdk.js
|
node patch-mcp-sdk.js
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
|
|
||||||
|
Set-Location servers
|
||||||
|
uv sync
|
||||||
|
Set-Location ..
|
||||||
|
|
||||||
# 安装根目录依赖
|
# 安装根目录依赖
|
||||||
npm i
|
npm i
|
12
package.json
12
package.json
@ -54,10 +54,16 @@
|
|||||||
"views": {
|
"views": {
|
||||||
"openmcp-sidebar": [
|
"openmcp-sidebar": [
|
||||||
{
|
{
|
||||||
"id": "webview-sidebar.view",
|
"id": "openmcp.sidebar.connect",
|
||||||
"icon": "./icons/protocol.svg",
|
"icon": "./icons/protocol.svg",
|
||||||
"name": "chatbot",
|
"name": "MCP 连接",
|
||||||
"type": "webview"
|
"type": "tree"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "openmcp.sidebar.help",
|
||||||
|
"icon": "./icons/protocol.svg",
|
||||||
|
"name": "入门与帮助",
|
||||||
|
"type": "tree"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as fspath from 'path';
|
import * as fspath from 'path';
|
||||||
|
|
||||||
import * as OpenMCPService from '../resources/service';
|
import * as OpenMCPService from '../resources/service';
|
||||||
import { getLaunchCWD, getWebviewContent } from './webview';
|
import { getLaunchCWD, revealOpenMcpWebviewPanel } from './webview';
|
||||||
import { panels } from './global';
|
import { registerSidebar } from './sidebar';
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
console.log('activate openmcp');
|
console.log('activate openmcp');
|
||||||
@ -15,81 +14,28 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
const workspace = workspaceFolder?.uri.fsPath || '';
|
const workspace = workspaceFolder?.uri.fsPath || '';
|
||||||
OpenMCPService.setVscodeWorkspace(workspace);
|
OpenMCPService.setVscodeWorkspace(workspace);
|
||||||
|
|
||||||
|
registerSidebar(context);
|
||||||
|
|
||||||
// 注册 showOpenMCP 命令
|
// 注册 showOpenMCP 命令
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
vscode.commands.registerCommand('openmcp.showOpenMCP', async (uri: vscode.Uri) => {
|
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);
|
const cwd = getLaunchCWD(context, uri);
|
||||||
// 获取 uri 相对于 cwd 的路径
|
// 获取 uri 相对于 cwd 的路径
|
||||||
const relativePath = fspath.relative(cwd, uri.fsPath);
|
const relativePath = fspath.relative(cwd, uri.fsPath);
|
||||||
|
|
||||||
console.log('current file' + uri.fsPath);
|
// TODO: 实现从 connection.json 中读取配置,然后启动对应的 connection
|
||||||
console.log(`relativePath: ${relativePath}`);
|
const command = 'mcp';
|
||||||
|
const args = ['run', 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
revealOpenMcpWebviewPanel(context, uri.fsPath, {
|
||||||
|
type: 'stdio',
|
||||||
|
name: 'OpenMCP',
|
||||||
|
command,
|
||||||
|
args,
|
||||||
|
cwd
|
||||||
});
|
});
|
||||||
|
|
||||||
panel.onDidDispose(async () => {
|
|
||||||
// 删除
|
|
||||||
panels.delete(uri.fsPath);
|
|
||||||
|
|
||||||
// 退出
|
|
||||||
panel.dispose();
|
|
||||||
});
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,43 @@
|
|||||||
import * as vscode from 'vscode';
|
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 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
105
src/sidebar.ts
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
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 * as OpenMCPService from '../resources/service';
|
||||||
|
|
||||||
|
|
||||||
export function getWebviewContent(context: vscode.ExtensionContext, panel: vscode.WebviewPanel): string | undefined {
|
export function getWebviewContent(context: vscode.ExtensionContext, panel: vscode.WebviewPanel): string | undefined {
|
||||||
const viewRoot = fspath.join(context.extensionPath, 'resources', 'renderer');
|
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);
|
const workspaceFolder = vscode.workspace.getWorkspaceFolder(uri);
|
||||||
return workspaceFolder?.uri.fsPath || '';
|
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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user