```
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:
118
panel/index.html
118
panel/index.html
@@ -1,17 +1,105 @@
|
||||
<div style="padding: 10px;">
|
||||
<ui-prop name="Node ID">
|
||||
<ui-input id="nodeId" placeholder="Click 'Get' to fetch ID"></ui-input>
|
||||
</ui-prop>
|
||||
<ui-prop name="New Name">
|
||||
<ui-input id="newName" placeholder="Enter new name"></ui-input>
|
||||
</ui-prop>
|
||||
<div class="mcp-container">
|
||||
<div class="toolbar">
|
||||
<div class="ctrl-group">
|
||||
<span>Port:</span>
|
||||
<ui-input id="portInput" value="3456"></ui-input>
|
||||
<ui-button id="btnToggle" class="green">Start</ui-button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 10px; display: flex; flex-direction: column; gap: 5px;">
|
||||
<ui-button id="btn-get" class="green">获取选中节点 ID</ui-button>
|
||||
<ui-button id="btn-set" class="blue">修改节点名称</ui-button>
|
||||
</div>
|
||||
<!-- 新增的自动启动勾选框 -->
|
||||
<div class="ctrl-group" style="margin-left: 15px">
|
||||
<ui-checkbox id="autoStartCheck">Auto Start</ui-checkbox>
|
||||
</div>
|
||||
|
||||
<div id="log" style="margin-top: 15px; font-size: 12px; color: #66ccff; border-top: 1px solid #555; padding-top: 5px;">
|
||||
Status: Ready
|
||||
</div>
|
||||
</div>
|
||||
<div class="spacer"></div>
|
||||
<ui-button id="btnClear" class="transparent">Clear</ui-button>
|
||||
<ui-button id="btnCopy" class="transparent">Copy All</ui-button>
|
||||
</div>
|
||||
|
||||
<!-- 日志区域 -->
|
||||
<div id="logConsole" class="log-view"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:host {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
background-color: #2d2d2d;
|
||||
}
|
||||
.mcp-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 5px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 5px;
|
||||
border-bottom: 1px solid #444;
|
||||
flex-shrink: 0; /* 禁止头部压缩 */
|
||||
}
|
||||
.ctrl-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.log-view {
|
||||
flex: 1; /* 自动撑满剩余空间 */
|
||||
background: #1a1a1a;
|
||||
margin-top: 5px;
|
||||
border-radius: 2px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 8px;
|
||||
font-family: "Consolas", "Monaco", monospace;
|
||||
font-size: 12px;
|
||||
|
||||
/* 【关键】允许鼠标选中文字 */
|
||||
-webkit-user-select: text;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
margin-bottom: 3px;
|
||||
line-height: 1.4;
|
||||
border-left: 4px solid #444;
|
||||
padding-left: 8px;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
/* 颜色修正 */
|
||||
.time {
|
||||
color: #5c6370;
|
||||
margin-right: 8px;
|
||||
font-weight: normal;
|
||||
user-select: none;
|
||||
}
|
||||
.info {
|
||||
border-left-color: #61afef;
|
||||
color: #abb2bf;
|
||||
}
|
||||
.success {
|
||||
border-left-color: #98c379;
|
||||
color: #98c379;
|
||||
}
|
||||
.warn {
|
||||
border-left-color: #e5c07b;
|
||||
color: #e5c07b;
|
||||
}
|
||||
.error {
|
||||
border-left-color: #e06c75;
|
||||
color: #e06c75;
|
||||
}
|
||||
.mcp {
|
||||
border-left-color: #c678dd;
|
||||
color: #d19a66;
|
||||
}
|
||||
</style>
|
||||
|
||||
114
panel/index.js
114
panel/index.js
@@ -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";
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user