feat(mcp-bridge): 实现MCP服务器功能增强和日志系统

- 添加日志缓冲区和封装日志函数,支持多种日志类型(info, success, warn, error, mcp)
- 实现MCP服务器启动/停止功能,支持端口配置和状态管理
- 添加配置文件管理(auto-start, last-port),支持持久化设置
- 实现完整的工具API接口(get_selected_node, set_node_name, save_scene等)
- 统一处理MCP调用逻辑,便于日志记录和错误处理
- 更新面板界面,添加端口输入、自动启动开关、日志查看等功能
- 优化错误处理和响应格式,符合MCP标准规范
```
This commit is contained in:
火焰库拉
2026-01-29 14:53:06 +08:00
parent f901bcc38f
commit 0b2f8cd50f
4 changed files with 523 additions and 339 deletions

View File

@@ -1,59 +1,89 @@
"use strict";
const fs = require("fs");
const path = require("path");
Editor.Panel.extend({
// 读取样式和模板
style: fs.readFileSync(Editor.url("packages://mcp-bridge/panel/index.html"), "utf-8"),
template: fs.readFileSync(Editor.url("packages://mcp-bridge/panel/index.html"), "utf-8"),
// 面板渲染成功后的回调
messages: {
"mcp-bridge:on-log"(event, log) {
this.renderLog(log);
},
"mcp-bridge:state-changed"(event, config) {
this.updateUI(config.active);
},
},
ready() {
// 使用 querySelector 确保能拿到元素,避免依赖可能为 undefined 的 this.$
const btnGet = this.shadowRoot.querySelector("#btn-get");
const btnSet = this.shadowRoot.querySelector("#btn-set");
const nodeIdInput = this.shadowRoot.querySelector("#nodeId");
const newNameInput = this.shadowRoot.querySelector("#newName");
const logDiv = this.shadowRoot.querySelector("#log");
const portInput = this.shadowRoot.querySelector("#portInput");
const btnToggle = this.shadowRoot.querySelector("#btnToggle");
const autoStartCheck = this.shadowRoot.querySelector("#autoStartCheck");
const btnClear = this.shadowRoot.querySelector("#btnClear");
const btnCopy = this.shadowRoot.querySelector("#btnCopy");
const logView = this.shadowRoot.querySelector("#logConsole");
if (!btnGet || !btnSet) {
Editor.error("Failed to find UI elements. Check if IDs in HTML match.");
return;
}
// 测试获取信息
btnGet.addEventListener("confirm", () => {
Editor.Ipc.sendToMain("mcp-bridge:get-selected-info", (err, ids) => {
if (ids && ids.length > 0) {
nodeIdInput.value = ids[0];
logDiv.innerText = "Status: Selected Node " + ids[0];
} else {
logDiv.innerText = "Status: No node selected";
}
});
// 初始化
Editor.Ipc.sendToMain("mcp-bridge:get-server-state", (err, data) => {
if (data) {
portInput.value = data.config.port;
this.updateUI(data.config.active);
data.logs.forEach((log) => this.renderLog(log));
}
});
// 测试修改信息
btnSet.addEventListener("confirm", () => {
let data = {
id: nodeIdInput.value,
path: "name",
value: newNameInput.value,
};
btnToggle.addEventListener("confirm", () => {
Editor.Ipc.sendToMain("mcp-bridge:toggle-server", parseInt(portInput.value));
});
if (!data.id) {
logDiv.innerText = "Error: Please get Node ID first";
return;
btnClear.addEventListener("confirm", () => {
logView.innerHTML = "";
Editor.Ipc.sendToMain("mcp-bridge:clear-logs");
});
btnCopy.addEventListener("confirm", () => {
require("electron").clipboard.writeText(logView.innerText);
Editor.success("All logs copied!");
});
Editor.Ipc.sendToMain("mcp-bridge:get-server-state", (err, data) => {
if (data) {
portInput.value = data.config.port;
this.updateUI(data.config.active);
// 设置自动启动复选框状态
autoStartCheck.value = data.autoStart;
data.logs.forEach((log) => this.renderLog(log));
}
Editor.Ipc.sendToMain("mcp-bridge:set-node-property", data, (err, res) => {
if (err) {
logDiv.innerText = "Error: " + err;
} else {
logDiv.innerText = "Success: " + res;
}
});
});
autoStartCheck.addEventListener("change", (event) => {
// event.target.value 在 ui-checkbox 中是布尔值
Editor.Ipc.sendToMain("mcp-bridge:set-auto-start", event.target.value);
});
},
renderLog(log) {
const logView = this.shadowRoot.querySelector("#logConsole");
if (!logView) return;
// 记录当前滚动条位置
const isAtBottom = logView.scrollHeight - logView.scrollTop <= logView.clientHeight + 50;
const el = document.createElement("div");
el.className = `log-item ${log.type}`;
el.innerHTML = `<span class="time">${log.time}</span><span class="msg">${log.content}</span>`;
logView.appendChild(el);
// 如果用户正在向上翻看,不自动滚动;否则自动滚到底部
if (isAtBottom) {
logView.scrollTop = logView.scrollHeight;
}
},
updateUI(isActive) {
const btnToggle = this.shadowRoot.querySelector("#btnToggle");
if (!btnToggle) return;
btnToggle.innerText = isActive ? "Stop" : "Start";
btnToggle.style.backgroundColor = isActive ? "#aa4444" : "#44aa44";
},
});