修复 vscode/trae 下 tab 恢复的问题

This commit is contained in:
锦恢 2025-04-13 16:12:58 +08:00
parent e306388f14
commit a0968902f4
19 changed files with 260 additions and 220 deletions

View File

@ -1,5 +1,12 @@
# Change Log
## [main] 0.0.2
- 优化页面布局
- 解决更新标签页后打开无法显示的 bug
- 解决不如输入组件按下回车直接黑屏的 bug
- 更加完整方便的开发脚本
## [main] 0.0.1
- 完成 openmcp 的基础 inspector 功能

View File

@ -13,13 +13,23 @@
## OpenMCP
一款用于 MCP 服务端调试的一体化 vscode 插件。
一款用于 MCP 服务端调试的一体化 vscode/trae 插件。
![](./icons/sreenshot.png)
集成 Inspector + MCP 客户端基础功能,开发测试一体化。
- 包含原版 Inpsector 的所有功能
- 包含一个简易的用于进行测试的大模型对话 & 执行窗口
- 支持多种大模型
![](./icons/openmcp.welcome.png)
进行资源协议、工具、Prompt 的 MCP 服务器测试。
![](./icons/openmcp.resource.png)
测试完成的工具可以放入 「交互测试」 模块之间进行大模型交互测试。
![](./icons/openmcp.chatbot.png)
支持多种大模型
![](./icons/openmcp.support.llm.png)
## TODO
@ -50,23 +60,22 @@ B <--mcp--> m(MCP Server)
配置项目
```bash
source configure.sh
## linux
./configure.sh
## windows
./configure.ps1
```
启动 dev server
```bash
cd renderer
npm run serve
```
启动 service
```bash
cd service
npm run serve
## linux
./dev.sh
## windows
./dev.ps1
```
> 端口占用: 8080 (renderer) + 8081 (service)
### Extention Dev

View File

@ -1,23 +1,23 @@
# Create resources directory if it doesn't exist
New-Item -ItemType Directory -Force -Path .\resources | Out-Null
# 创建并清理资源目录
New-Item -ItemType Directory -Path ./resources -Force
Remove-Item -Recurse -Force ./resources/*
New-Item -ItemType Directory -Path ./resources -Force
# Start both build tasks in parallel
$jobs = @(
Start-Job -ScriptBlock {
Set-Location $using:PWD\renderer
npm run build
Move-Item -Force -Path .\dist -Destination ..\resources\renderer
}
Start-Job -ScriptBlock {
Set-Location $using:PWD\service
npm run build
Move-Item -Force -Path .\dist -Destination ..\resources\service
}
)
# 并行构建 renderer 和 service
$rendererJob = Start-Job -ScriptBlock {
cd ./renderer
npm run build
mv ./dist ../resources/renderer
}
# Wait for all jobs to complete
Wait-Job -Job $jobs | Out-Null
Receive-Job -Job $jobs
Remove-Job -Job $jobs
$serviceJob = Start-Job -ScriptBlock {
cd ./service
npm run build
mv ./dist ../resources/service
}
Write-Host "finish building services in ./resources"
# 等待任务完成
$rendererJob | Wait-Job | Receive-Job
$serviceJob | Wait-Job | Receive-Job
Write-Output "finish building services in ./resources"

View File

@ -1,6 +1,9 @@
#!/bin/bash
mkdir -p ./resources
rm -rf ./resources/
mkdir -p ./resources
(cd ./renderer && npm run build && mv ./dist ../resources/renderer) &
(cd ./service && npm run build && mv ./dist ../resources/service) &
wait

13
configure.ps1 Normal file
View File

@ -0,0 +1,13 @@
# 安装 renderer 依赖
Set-Location renderer
npm i
Set-Location ..
# 安装 service 依赖并打补丁
Set-Location service
npm i
node patch-mcp-sdk.js
Set-Location ..
# 安装根目录依赖
npm i

16
dev.ps1 Normal file
View File

@ -0,0 +1,16 @@
# 定义颜色变量
$PINK = "$([char]27)[33m"
$GREEN = "$([char]27)[32m"
$NC = "$([char]27)[0m"
Start-Job -ScriptBlock {
cd renderer
npm run serve | ForEach-Object { "$using:PINK[renderer]$using:NC $_" }
}
Start-Job -ScriptBlock {
cd service
npm run serve | ForEach-Object { "$using:GREEN[service]$using:NC $_" }
}
Get-Job | Wait-Job | Receive-Job -Wait

12
dev.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
# 定义颜色变量(使用 -e 选项的 echo 时)
PINK=$'\033[33m'
GREEN=$'\033[32m'
NC=$'\033[0m'
(cd renderer && npm run serve | while read -r line; do echo -e "${PINK}[renderer]${NC} $line"; done) &
(cd service && npm run serve | while read -r line; do echo -e "${GREEN}[service]${NC} $line"; done) &
wait

BIN
icons/openmcp.chatbot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

BIN
icons/openmcp.resource.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 KiB

BIN
icons/openmcp.welcome.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

View File

@ -7,6 +7,7 @@
<script setup lang="ts">
import { onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { Connection } from './components/sidebar/sidebar';
import Sidebar from '@/components/sidebar/index.vue';
@ -26,40 +27,28 @@ bridge.addCommandListener('hello', data => {
greenLog(`version: ${data.version}`);
}, { once: true });
// connect
bridge.addCommandListener('connect', async data => {
const { code, msg } = data;
connectionResult.success = (code === 200);
connectionResult.logString = msg;
const res = await getServerVersion() as { name: string, version: string };
connectionResult.serverInfo.name = res.name || '';
connectionResult.serverInfo.version = res.version || '';
}, { once: true });
function initDebug() {
connectionArgs.commandString = 'mcp run ../servers/main.py';
connectionMethods.current = 'STDIO';
setTimeout(() => {
setTimeout(async () => {
//
loadSetting();
//
await doConnect();
// tab
loadPanels();
//
doConnect();
// 200 ws ws
//
}, 200);
}
function initProduce() {
const route = useRoute();
const router = useRouter();
async function initProduce() {
// TODO: get from vscode
connectionArgs.commandString = 'mcp run ../servers/main.py';
connectionMethods.current = 'STDIO';
@ -67,10 +56,15 @@ function initProduce() {
//
loadSetting();
// tab
loadPanels();
//
await launchConnect();
launchConnect();
// tab
await loadPanels();
if (route.name !== 'debug') {
router.replace('/debug');
}
}
onMounted(() => {

View File

@ -40,6 +40,7 @@
import { defineComponent } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { addNewTab, tabs, closeTab } from './panel';
import { panelLoaded } from '@/hook/panel';
defineComponent({ name: 'main-panel' });

View File

@ -19,49 +19,50 @@ interface SaveTab {
export const panelLoaded = ref(false);
export function loadPanels() {
const bridge = useMessageBridge();
bridge.addCommandListener('panel/load', data => {
if (data.code !== 200) {
pinkLog('tabs 加载失败');
console.log(data.msg);
return new Promise((resolve, reject) => {
const bridge = useMessageBridge();
} else {
const persistTab = data.msg as SaveTab;
bridge.addCommandListener('panel/load', data => {
if (data.code !== 200) {
pinkLog('tabs 加载失败');
console.log(data.msg);
pinkLog('tabs 加载成功');
} else {
const persistTab = data.msg as SaveTab;
if (persistTab.tabs.length === 0) {
// 空的,直接返回不需要管
return;
}
pinkLog('tabs 加载成功');
tabs.activeIndex = 0;
tabs.content = [];
if (persistTab.tabs.length === 0) {
// 空的,直接返回不需要管
return;
}
for (const tab of persistTab.tabs || []) {
tabs.content.push({
name: tab.name,
icon: tab.icon,
type: tab.type,
componentIndex: tab.componentIndex,
component: markRaw(debugModes[tab.componentIndex]),
storage: tab.storage
});
}
tabs.activeIndex = 0;
tabs.content = [];
for (const tab of persistTab.tabs || []) {
tabs.content.push({
name: tab.name,
icon: tab.icon,
type: tab.type,
componentIndex: tab.componentIndex,
component: markRaw(debugModes[tab.componentIndex]),
storage: tab.storage
});
}
tabs.activeIndex = persistTab.currentIndex;
nextTick(() => {
tabs.activeIndex = persistTab.currentIndex;
});
}
}
panelLoaded.value = true;
}, { once: true });
panelLoaded.value = true;
resolve(void 0);
}, { once: true });
bridge.postMessage({
command: 'panel/load'
});
bridge.postMessage({
command: 'panel/load'
});
});
}
let debounceHandler: NodeJS.Timeout;
@ -74,10 +75,10 @@ export function safeSavePanels() {
}
export function savePanels(saveHandler?: () => void) {
// 没有完成 panel 加载就不保存
if (!panelLoaded.value) {
return;
}
// // 没有完成 panel 加载就不保存
// if (!panelLoaded.value) {
// return;
// }
const bridge = useMessageBridge();

View File

@ -57,44 +57,58 @@ export interface MCPOptions {
export function doConnect() {
let connectOption: MCPOptions;
if (connectionMethods.current === 'STDIO') {
if (connectionArgs.commandString.length === 0) {
return;
}
const commandComponents = connectionArgs.commandString.split(/\s+/g);
const command = commandComponents[0];
commandComponents.shift();
connectOption = {
connectionType: 'STDIO',
command: command,
args: commandComponents,
clientName: 'openmcp.connect.stdio.' + command,
clientVersion: '0.0.1'
}
} else {
const url = connectionArgs.urlString;
if (url.length === 0) {
return;
}
connectOption = {
connectionType: 'SSE',
url: url,
clientName: 'openmcp.connect.sse',
clientVersion: '0.0.1'
}
}
const bridge = useMessageBridge();
bridge.postMessage({
command: 'connect',
data: connectOption
return new Promise((resolve, reject) => {
// 监听 connect
bridge.addCommandListener('connect', async data => {
const { code, msg } = data;
connectionResult.success = (code === 200);
connectionResult.logString = msg;
const res = await getServerVersion() as { name: string, version: string };
connectionResult.serverInfo.name = res.name || '';
connectionResult.serverInfo.version = res.version || '';
resolve(void 0);
}, { once: true });
if (connectionMethods.current === 'STDIO') {
if (connectionArgs.commandString.length === 0) {
return;
}
const commandComponents = connectionArgs.commandString.split(/\s+/g);
const command = commandComponents[0];
commandComponents.shift();
connectOption = {
connectionType: 'STDIO',
command: command,
args: commandComponents,
clientName: 'openmcp.connect.stdio.' + command,
clientVersion: '0.0.1'
}
} else {
const url = connectionArgs.urlString;
if (url.length === 0) {
return;
}
connectOption = {
connectionType: 'SSE',
url: url,
clientName: 'openmcp.connect.sse',
clientVersion: '0.0.1'
}
}
bridge.postMessage({
command: 'connect',
data: connectOption
});
});
}
@ -106,33 +120,47 @@ export async function launchConnect() {
// 后续需要考虑到不同的连接方式
connectionMethods.current = 'STDIO';
const bridge = useMessageBridge();
pinkLog('请求启动参数');
const { commandString, cwd } = await getLaunchCommand();
connectionArgs.commandString = commandString;
if (connectionArgs.commandString.length === 0) {
return;
}
const commandComponents = connectionArgs.commandString.split(/\s+/g);
const command = commandComponents[0];
commandComponents.shift();
return new Promise<void>((resolve, reject) => {
// 监听 connect
bridge.addCommandListener('connect', async data => {
const { code, msg } = data;
connectionResult.success = (code === 200);
connectionResult.logString = msg;
const connectOption = {
connectionType: 'STDIO',
command: command,
args: commandComponents,
cwd: cwd,
clientName: 'openmcp.connect.stdio.' + command,
clientVersion: '0.0.1'
};
const res = await getServerVersion() as { name: string, version: string };
connectionResult.serverInfo.name = res.name || '';
connectionResult.serverInfo.version = res.version || '';
resolve(void 0);
}, { once: true });
const bridge = useMessageBridge();
bridge.postMessage({
command: 'connect',
data: connectOption
const commandComponents = connectionArgs.commandString.split(/\s+/g);
const command = commandComponents[0];
commandComponents.shift();
const connectOption = {
connectionType: 'STDIO',
command: command,
args: commandComponents,
cwd: cwd,
clientName: 'openmcp.connect.stdio.' + command,
clientVersion: '0.0.1'
};
bridge.postMessage({
command: 'connect',
data: connectOption
});
});
}
@ -170,18 +198,18 @@ export const connectionResult = reactive({
});
export function getServerVersion() {
return new Promise((resolve, reject) => {
const bridge = useMessageBridge();
bridge.addCommandListener('server/version', data => {
if (data.code === 200) {
resolve(data.msg);
} else {
reject(data.msg);
}
}, { once: true });
return new Promise((resolve, reject) => {
const bridge = useMessageBridge();
bridge.addCommandListener('server/version', data => {
if (data.code === 200) {
resolve(data.msg);
} else {
reject(data.msg);
}
}, { once: true });
bridge.postMessage({
command: 'server/version',
});
});
bridge.postMessage({
command: 'server/version',
});
});
}

View File

@ -3,7 +3,7 @@
<Welcome v-show="!haveActiveTab"></Welcome>
<!-- 如果存在激活标签页则根据标签页进行渲染 -->
<div v-show="haveActiveTab" style="height: 100%;">
<div v-show="haveActiveTab" v-if="panelLoaded" style="height: 100%;">
<!-- vscode/trae 下面存在初始化问题 -->
<component
v-show="tab === tabs.content[tabs.activeIndex]"

View File

@ -36,5 +36,8 @@ module.exports = defineConfig({
},
css: {
extract: false
},
devServer: {
port: 8081
}
});

View File

@ -1,5 +1,5 @@
{
"currentIndex": 0,
"currentIndex": 1,
"tabs": [
{
"name": "资源",
@ -7,54 +7,7 @@
"type": "blank",
"componentIndex": 0,
"storage": {
"currentResourceName": "greeting",
"lastResourceReadResponse": {
"contents": [
{
"uri": "greeting://12312",
"mimeType": "text/plain",
"text": "Hello, 12312!"
}
]
}
}
},
{
"name": "交互测试",
"icon": "icon-robot",
"type": "blank",
"componentIndex": 3,
"storage": {
"messages": [],
"settings": {
"modelIndex": 0,
"enableTools": [
{
"name": "add",
"description": "对两个数字进行实数域的加法",
"enabled": true
},
{
"name": "multiply",
"description": "对两个数字进行实数域的乘法运算",
"enabled": true
},
{
"name": "is_even",
"description": "判断一个整数是否为偶数",
"enabled": true
},
{
"name": "capitalize",
"description": "将字符串首字母大写",
"enabled": true
}
],
"enableWebSearch": false,
"temperature": 0.7,
"contextLength": 10,
"systemPrompt": ""
}
"currentResourceName": "greeting"
}
},
{