diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md
new file mode 100644
index 0000000..a062884
--- /dev/null
+++ b/DEVELOPMENT.md
@@ -0,0 +1,420 @@
+# MCP Bridge 插件开发流程文档
+
+本文档记录了 MCP Bridge 插件的完整开发流程,包括核心架构设计、功能实现、测试与调试等各个阶段。
+
+## 1. 项目初始化
+
+### 1.1 目录结构搭建
+
+```
+mcp-bridge/
+├── main.js # 插件主入口
+├── scene-script.js # 场景脚本
+├── mcp-proxy.js # MCP 代理
+├── README.md # 项目说明
+├── DEVELOPMENT.md # 开发流程文档
+├── package.json # 插件配置
+└── panel/ # 面板目录
+ ├── index.html # 面板界面
+ └── index.js # 面板逻辑
+```
+
+### 1.2 插件配置
+
+在 `package.json` 中配置插件信息:
+
+```json
+{
+ "name": "mcp-bridge",
+ "version": "1.0.0",
+ "description": "MCP Bridge for Cocos Creator",
+ "main": "main.js",
+ "panel": {
+ "main": "panel/index.html",
+ "type": "dockable",
+ "title": "MCP Bridge",
+ "width": 800,
+ "height": 600
+ },
+ "contributions": {
+ "menu": [
+ {
+ "path": "Packages/MCP Bridge",
+ "label": "Open Test Panel",
+ "message": "open-test-panel"
+ }
+ ]
+ }
+}
+```
+
+## 2. 核心架构设计
+
+### 2.1 系统架构
+
+```
+┌────────────────────┐ HTTP ┌────────────────────┐ IPC ┌────────────────────┐
+│ 外部 AI 工具 │ ──────────> │ main.js (HTTP服务) │ ─────────> │ scene-script.js │
+│ (Cursor/VS Code) │ <──────── │ (MCP 协议处理) │ <──────── │ (场景操作执行) │
+└────────────────────┘ JSON └────────────────────┘ JSON └────────────────────┘
+```
+
+### 2.2 核心模块
+
+1. **HTTP 服务模块**:处理外部请求,解析 MCP 协议
+2. **MCP 工具模块**:实现各种操作工具
+3. **场景操作模块**:执行场景相关操作
+4. **资源管理模块**:处理脚本和资源文件
+5. **面板界面模块**:提供用户交互界面
+
+## 3. 功能模块实现
+
+### 3.1 HTTP 服务实现
+
+在 `main.js` 中实现 HTTP 服务:
+
+```javascript
+startServer(port) {
+ try {
+ const http = require('http');
+ mcpServer = http.createServer((req, res) => {
+ // 处理请求...
+ });
+ mcpServer.listen(port, () => {
+ addLog("success", `MCP Server running at http://127.0.0.1:${port}`);
+ });
+ } catch (e) {
+ addLog("error", `Failed to start server: ${e.message}`);
+ }
+}
+```
+
+### 3.2 MCP 工具注册
+
+在 `/list-tools` 接口中注册工具:
+
+```javascript
+const tools = [
+ {
+ name: "get_selected_node",
+ description: "获取当前选中的节点",
+ parameters: []
+ },
+ // 其他工具...
+];
+```
+
+### 3.3 场景操作实现
+
+在 `scene-script.js` 中实现场景相关操作:
+
+```javascript
+const sceneScript = {
+ 'create-node'(params, callback) {
+ // 创建节点逻辑...
+ },
+ 'set-property'(params, callback) {
+ // 设置属性逻辑...
+ },
+ // 其他操作...
+};
+```
+
+### 3.4 脚本管理实现
+
+在 `main.js` 中实现脚本管理功能:
+
+```javascript
+manageScript(args, callback) {
+ const { action, path, content } = args;
+ switch (action) {
+ case "create":
+ // 确保父目录存在
+ const fs = require('fs');
+ const pathModule = require('path');
+ const absolutePath = Editor.assetdb.urlToFspath(path);
+ const dirPath = pathModule.dirname(absolutePath);
+ if (!fs.existsSync(dirPath)) {
+ fs.mkdirSync(dirPath, { recursive: true });
+ }
+ // 创建 TypeScript 脚本
+ Editor.assetdb.create(path, content || `const { ccclass, property } = cc._decorator;
+
+@ccclass
+export default class NewScript extends cc.Component {
+ // LIFE-CYCLE CALLBACKS:
+
+ onLoad () {}
+
+ start () {}
+
+ update (dt) {}
+}`, (err) => {
+ callback(err, err ? null : `Script created at ${path}`);
+ });
+ break;
+ // 其他操作...
+ }
+}
+```
+
+### 3.5 批处理执行实现
+
+在 `main.js` 中实现批处理功能:
+
+```javascript
+batchExecute(args, callback) {
+ const { operations } = args;
+ const results = [];
+ let completed = 0;
+
+ if (!operations || operations.length === 0) {
+ return callback("No operations provided");
+ }
+
+ operations.forEach((operation, index) => {
+ this.handleMcpCall(operation.tool, operation.params, (err, result) => {
+ results[index] = { tool: operation.tool, error: err, result: result };
+ completed++;
+
+ if (completed === operations.length) {
+ callback(null, results);
+ }
+ });
+ });
+}
+```
+
+### 3.6 资产管理实现
+
+在 `main.js` 中实现资产管理功能:
+
+```javascript
+manageAsset(args, callback) {
+ const { action, path, targetPath, content } = args;
+
+ switch (action) {
+ case "create":
+ // 确保父目录存在
+ const fs = require('fs');
+ const pathModule = require('path');
+ const absolutePath = Editor.assetdb.urlToFspath(path);
+ const dirPath = pathModule.dirname(absolutePath);
+ if (!fs.existsSync(dirPath)) {
+ fs.mkdirSync(dirPath, { recursive: true });
+ }
+ Editor.assetdb.create(path, content || '', (err) => {
+ callback(err, err ? null : `Asset created at ${path}`);
+ });
+ break;
+ // 其他操作...
+ }
+}
+```
+
+### 3.7 面板界面实现
+
+在 `panel/index.html` 中实现标签页界面:
+
+```html
+
+
+
+ Main
+ Tool Test
+
+
+
+
+
+
+
+
+
+
+```
+
+## 4. 测试与调试
+
+### 4.1 本地测试
+
+1. **启动服务**:在面板中点击 "Start" 按钮
+2. **测试工具**:在 "Tool Test" 标签页中测试各个工具
+3. **查看日志**:在主面板中查看操作日志
+
+### 4.2 常见错误及修复
+
+#### 4.2.1 面板加载错误
+
+**错误信息**:`Panel info not found for panel mcp-bridge`
+
+**解决方案**:
+- 检查 `package.json` 中的面板配置
+- 确保 `panel` 字段配置正确,移除冲突的 `panels` 字段
+
+#### 4.2.2 资源创建错误
+
+**错误信息**:`Parent path ... is not exists`
+
+**解决方案**:
+- 在创建资源前添加目录检查和创建逻辑
+- 使用 `fs.mkdirSync(dirPath, { recursive: true })` 递归创建目录
+
+#### 4.2.3 脚本语法错误
+
+**错误信息**:`SyntaxError: Invalid or unexpected token`
+
+**解决方案**:
+- 使用模板字符串(反引号)处理多行字符串
+- 避免变量名冲突
+
+### 4.3 性能优化
+
+1. **批处理执行**:使用 `batch_execute` 工具减少 HTTP 请求次数
+2. **异步操作**:使用回调函数处理异步操作,避免阻塞主线程
+3. **错误处理**:完善错误处理机制,提高插件稳定性
+
+## 5. 文档编写
+
+### 5.1 README.md
+
+- 项目简介
+- 功能特性
+- 安装使用
+- API 文档
+- 技术实现
+
+### 5.2 API 文档
+
+为每个 MCP 工具编写详细的 API 文档,包括:
+- 工具名称
+- 功能描述
+- 参数说明
+- 返回值格式
+- 使用示例
+
+### 5.3 开发文档
+
+- 项目架构
+- 开发流程
+- 代码规范
+- 贡献指南
+
+## 6. 部署与使用
+
+### 6.1 部署方式
+
+1. **本地部署**:将插件复制到 Cocos Creator 项目的 `packages` 目录
+2. **远程部署**:通过版本控制系统管理插件代码
+
+### 6.2 使用流程
+
+1. **启动服务**:
+ - 打开 Cocos Creator 编辑器
+ - 选择 `Packages/MCP Bridge/Open Test Panel`
+ - 点击 "Start" 按钮启动服务
+
+2. **连接 AI 编辑器**:
+ - 在 AI 编辑器中配置 MCP 代理
+ - 使用 `node [项目路径]/packages/mcp-bridge/mcp-proxy.js` 作为命令
+
+3. **执行操作**:
+ - 通过 AI 编辑器发送 MCP 请求
+ - 或在测试面板中直接测试工具
+
+### 6.3 配置选项
+
+- **端口设置**:默认 3456,可自定义
+- **自动启动**:支持编辑器启动时自动开启服务
+
+## 7. 功能扩展
+
+### 7.1 添加新工具
+
+1. **在 `main.js` 中注册工具**:
+ - 在 `/list-tools` 响应中添加工具定义
+ - 在 `handleMcpCall` 函数中添加处理逻辑
+
+2. **在面板中添加示例**:
+ - 在 `panel/index.js` 中添加工具示例参数
+ - 更新工具列表
+
+3. **更新文档**:
+ - 在 `README.md` 中添加工具文档
+ - 更新功能特性列表
+
+### 7.2 集成新 API
+
+1. **了解 Cocos Creator API**:
+ - 查阅 Cocos Creator 编辑器 API 文档
+ - 了解场景脚本 API
+
+2. **实现集成**:
+ - 在 `main.js` 或 `scene-script.js` 中添加对应功能
+ - 处理异步操作和错误情况
+
+3. **测试验证**:
+ - 编写测试用例
+ - 验证功能正确性
+
+## 8. 版本管理
+
+### 8.1 版本控制
+
+- 使用 Git 进行版本控制
+- 遵循语义化版本规范
+
+### 8.2 发布流程
+
+1. **代码审查**:检查代码质量和功能完整性
+2. **测试验证**:确保所有功能正常工作
+3. **文档更新**:更新 README 和相关文档
+4. **版本发布**:标记版本号并发布
+
+## 9. 技术栈
+
+- **JavaScript**:主要开发语言
+- **Node.js**:HTTP 服务和文件操作
+- **Cocos Creator API**:编辑器功能集成
+- **HTML/CSS**:面板界面
+- **MCP 协议**:与 AI 工具通信
+
+## 10. 最佳实践
+
+1. **代码组织**:
+ - 模块化设计,职责分离
+ - 合理使用回调函数处理异步操作
+
+2. **错误处理**:
+ - 完善的错误捕获和处理
+ - 详细的错误日志记录
+
+3. **用户体验**:
+ - 直观的面板界面
+ - 实时的操作反馈
+ - 详细的日志信息
+
+4. **安全性**:
+ - 验证输入参数
+ - 防止路径遍历攻击
+ - 限制服务访问范围
+
+## 11. 总结
+
+MCP Bridge 插件通过 HTTP 服务和 MCP 协议,为外部 AI 工具提供了与 Cocos Creator 编辑器交互的能力。插件支持场景操作、资源管理、组件管理、脚本管理等多种功能,为 Cocos Creator 项目的开发和自动化提供了有力的支持。
+
+通过本文档的开发流程,我们构建了一个功能完整、稳定可靠的 MCP Bridge 插件,为 Cocos Creator 生态系统增添了新的工具和能力。
diff --git a/README.md b/README.md
index cd93560..7d5e974 100644
--- a/README.md
+++ b/README.md
@@ -11,6 +11,10 @@
- **HTTP 服务接口**: 提供标准 HTTP 接口,外部工具可以通过 MCP 协议调用 Cocos Creator 编辑器功能
- **场景节点操作**: 获取、创建、修改场景中的节点
- **资源管理**: 创建场景、预制体,打开指定资源
+- **组件管理**: 添加、删除、获取节点组件
+- **脚本管理**: 创建、删除、读取、写入脚本文件
+- **批处理执行**: 批量执行多个 MCP 工具操作,提高效率
+- **资产管理**: 创建、删除、移动、获取资源信息
- **实时日志**: 提供详细的操作日志记录和展示
- **自动启动**: 支持编辑器启动时自动开启服务
@@ -126,6 +130,43 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
- `parentId`: 父节点 UUID (可选,不传则挂在场景根部)
- `type`: 节点预设类型(`empty`, `sprite`, `label`, `canvas`)
+### 10. manage_components
+
+- **描述**: 管理节点组件
+- **参数**:
+ - `nodeId`: 节点 UUID
+ - `action`: 操作类型(`add`, `remove`, `get`)
+ - `componentType`: 组件类型,如 `cc.Sprite`(用于 `add` 操作)
+ - `componentId`: 组件 ID(用于 `remove` 操作)
+ - `properties`: 组件属性(用于 `add` 操作)
+
+### 11. manage_script
+
+- **描述**: 管理脚本文件,默认创建 TypeScript 脚本
+- **参数**:
+ - `action`: 操作类型(`create`, `delete`, `read`, `write`)
+ - `path`: 脚本路径,如 `db://assets/scripts/NewScript.ts`
+ - `content`: 脚本内容(用于 `create` 和 `write` 操作)
+ - `name`: 脚本名称(用于 `create` 操作)
+- **默认模板**: 当未提供 content 时,会使用 TypeScript 格式的默认模板
+
+### 12. batch_execute
+
+- **描述**: 批处理执行多个操作
+- **参数**:
+ - `operations`: 操作列表
+ - `tool`: 工具名称
+ - `params`: 工具参数
+
+### 13. manage_asset
+
+- **描述**: 管理资源
+- **参数**:
+ - `action`: 操作类型(`create`, `delete`, `move`, `get_info`)
+ - `path`: 资源路径,如 `db://assets/textures`
+ - `targetPath`: 目标路径(用于 `move` 操作)
+ - `content`: 资源内容(用于 `create` 操作)
+
## 技术实现
### 架构设计
diff --git a/main.js b/main.js
index 295a831..2204b86 100644
--- a/main.js
+++ b/main.js
@@ -133,7 +133,7 @@ const getToolsList = () => {
},
{
name: "open_scene",
- description: "在编辑器中打开指定的场景文件",
+ description: "打开场景文件。注意:这是一个异步且耗时的操作,打开后请等待几秒再进行节点创建或保存操作。",
inputSchema: {
type: "object",
properties: {
@@ -165,8 +165,74 @@ const getToolsList = () => {
required: ["name"],
},
},
+ {
+ name: "manage_components",
+ description: "管理节点组件",
+ inputSchema: {
+ type: "object",
+ properties: {
+ nodeId: { type: "string", description: "节点 UUID" },
+ action: { type: "string", enum: ["add", "remove", "get"], description: "操作类型" },
+ componentType: { type: "string", description: "组件类型,如 cc.Sprite" },
+ componentId: { type: "string", description: "组件 ID (用于 remove 操作)" },
+ properties: { type: "object", description: "组件属性 (用于 add 操作)" },
+ },
+ required: ["nodeId", "action"],
+ },
+ },
+ {
+ name: "manage_script",
+ description: "管理脚本文件",
+ inputSchema: {
+ type: "object",
+ properties: {
+ action: { type: "string", enum: ["create", "delete", "read", "write"], description: "操作类型" },
+ path: { type: "string", description: "脚本路径,如 db://assets/scripts/NewScript.js" },
+ content: { type: "string", description: "脚本内容 (用于 create 和 write 操作)" },
+ name: { type: "string", description: "脚本名称 (用于 create 操作)" },
+ },
+ required: ["action", "path"],
+ },
+ },
+ {
+ name: "batch_execute",
+ description: "批处理执行多个操作",
+ inputSchema: {
+ type: "object",
+ properties: {
+ operations: {
+ type: "array",
+ items: {
+ type: "object",
+ properties: {
+ tool: { type: "string", description: "工具名称" },
+ params: { type: "object", description: "工具参数" },
+ },
+ required: ["tool", "params"],
+ },
+ description: "操作列表",
+ },
+ },
+ required: ["operations"],
+ },
+ },
+ {
+ name: "manage_asset",
+ description: "管理资源",
+ inputSchema: {
+ type: "object",
+ properties: {
+ action: { type: "string", enum: ["create", "delete", "move", "get_info"], description: "操作类型" },
+ path: { type: "string", description: "资源路径,如 db://assets/textures" },
+ targetPath: { type: "string", description: "目标路径 (用于 move 操作)" },
+ content: { type: "string", description: "资源内容 (用于 create 操作)" },
+ },
+ required: ["action", "path"],
+ },
+ },
];
};
+let isSceneBusy = false;
module.exports = {
"scene-script": "scene-script.js",
@@ -279,6 +345,9 @@ module.exports = {
// 统一处理逻辑,方便日志记录
handleMcpCall(name, args, callback) {
+ if (isSceneBusy && (name === "save_scene" || name === "create_node")) {
+ return callback("Editor is busy (Processing Scene), please wait a moment.");
+ }
switch (name) {
case "get_selected_node":
const ids = Editor.Selection.curSelection("node");
@@ -299,8 +368,18 @@ module.exports = {
break;
case "save_scene":
- Editor.Ipc.sendToMain("scene:save-scene");
- callback(null, "Scene saved successfully");
+ isSceneBusy = true;
+ addLog("info", "Preparing to save scene... Waiting for UI sync.");
+ // 强制延迟保存,防止死锁
+ setTimeout(() => {
+ Editor.Ipc.sendToMain("scene:save-scene");
+ addLog("info", "Executing Safe Save...");
+ setTimeout(() => {
+ isSceneBusy = false;
+ addLog("info", "Safe Save completed.");
+ callback(null, "Scene saved successfully.");
+ }, 1000);
+ }, 500);
break;
case "get_scene_hierarchy":
@@ -328,11 +407,16 @@ module.exports = {
break;
case "open_scene":
+ isSceneBusy = true; // 锁定
const openUuid = Editor.assetdb.urlToUuid(args.url);
if (openUuid) {
Editor.Ipc.sendToMain("scene:open-by-uuid", openUuid);
- callback(null, `Success: Opening scene ${args.url}`);
+ setTimeout(() => {
+ isSceneBusy = false;
+ callback(null, `Success: Opening scene ${args.url}`);
+ }, 2000);
} else {
+ isSceneBusy = false;
callback(`Could not find asset with URL ${args.url}`);
}
break;
@@ -341,11 +425,175 @@ module.exports = {
Editor.Scene.callSceneScript("mcp-bridge", "create-node", args, callback);
break;
+ case "manage_components":
+ Editor.Scene.callSceneScript("mcp-bridge", "manage-components", args, callback);
+ break;
+
+ case "manage_script":
+ this.manageScript(args, callback);
+ break;
+
+ case "batch_execute":
+ this.batchExecute(args, callback);
+ break;
+
+ case "manage_asset":
+ this.manageAsset(args, callback);
+ break;
+
default:
callback(`Unknown tool: ${name}`);
break;
}
},
+
+ // 管理脚本文件
+ manageScript(args, callback) {
+ const { action, path, content } = args;
+
+ switch (action) {
+ case "create":
+ if (Editor.assetdb.exists(path)) {
+ return callback(`Script already exists at ${path}`);
+ }
+ // 确保父目录存在
+ const fs = require('fs');
+ const pathModule = require('path');
+ const absolutePath = Editor.assetdb.urlToFspath(path);
+ const dirPath = pathModule.dirname(absolutePath);
+ if (!fs.existsSync(dirPath)) {
+ fs.mkdirSync(dirPath, { recursive: true });
+ }
+ Editor.assetdb.create(path, content || `const { ccclass, property } = cc._decorator;
+
+@ccclass
+export default class NewScript extends cc.Component {
+ @property(cc.Label)
+ label: cc.Label = null;
+
+ @property
+ text: string = 'hello';
+
+ // LIFE-CYCLE CALLBACKS:
+
+ onLoad () {}
+
+ start () {}
+
+ update (dt) {}
+}`, (err) => {
+ callback(err, err ? null : `Script created at ${path}`);
+ });
+ break;
+
+ case "delete":
+ if (!Editor.assetdb.exists(path)) {
+ return callback(`Script not found at ${path}`);
+ }
+ Editor.assetdb.delete([path], (err) => {
+ callback(err, err ? null : `Script deleted at ${path}`);
+ });
+ break;
+
+ case "read":
+ Editor.assetdb.queryInfoByUuid(Editor.assetdb.urlToUuid(path), (err, info) => {
+ if (err) {
+ return callback(`Failed to get script info: ${err}`);
+ }
+ Editor.assetdb.loadAny(path, (err, content) => {
+ callback(err, err ? null : content);
+ });
+ });
+ break;
+
+ case "write":
+ Editor.assetdb.create(path, content, (err) => {
+ callback(err, err ? null : `Script updated at ${path}`);
+ });
+ break;
+
+ default:
+ callback(`Unknown script action: ${action}`);
+ break;
+ }
+ },
+
+ // 批处理执行
+ batchExecute(args, callback) {
+ const { operations } = args;
+ const results = [];
+ let completed = 0;
+
+ if (!operations || operations.length === 0) {
+ return callback("No operations provided");
+ }
+
+ operations.forEach((operation, index) => {
+ this.handleMcpCall(operation.tool, operation.params, (err, result) => {
+ results[index] = { tool: operation.tool, error: err, result: result };
+ completed++;
+
+ if (completed === operations.length) {
+ callback(null, results);
+ }
+ });
+ });
+ },
+
+ // 管理资源
+ manageAsset(args, callback) {
+ const { action, path, targetPath, content } = args;
+
+ switch (action) {
+ case "create":
+ if (Editor.assetdb.exists(path)) {
+ return callback(`Asset already exists at ${path}`);
+ }
+ // 确保父目录存在
+ const fs = require('fs');
+ const pathModule = require('path');
+ const absolutePath = Editor.assetdb.urlToFspath(path);
+ const dirPath = pathModule.dirname(absolutePath);
+ if (!fs.existsSync(dirPath)) {
+ fs.mkdirSync(dirPath, { recursive: true });
+ }
+ Editor.assetdb.create(path, content || '', (err) => {
+ callback(err, err ? null : `Asset created at ${path}`);
+ });
+ break;
+
+ case "delete":
+ if (!Editor.assetdb.exists(path)) {
+ return callback(`Asset not found at ${path}`);
+ }
+ Editor.assetdb.delete([path], (err) => {
+ callback(err, err ? null : `Asset deleted at ${path}`);
+ });
+ break;
+
+ case "move":
+ if (!Editor.assetdb.exists(path)) {
+ return callback(`Asset not found at ${path}`);
+ }
+ if (Editor.assetdb.exists(targetPath)) {
+ return callback(`Target asset already exists at ${targetPath}`);
+ }
+ Editor.assetdb.move(path, targetPath, (err) => {
+ callback(err, err ? null : `Asset moved from ${path} to ${targetPath}`);
+ });
+ break;
+
+ case "get_info":
+ Editor.assetdb.queryInfoByUuid(Editor.assetdb.urlToUuid(path), (err, info) => {
+ callback(err, err ? null : info);
+ });
+ break;
+
+ default:
+ callback(`Unknown asset action: ${action}`);
+ break;
+ }
+ },
// 暴露给 MCP 或面板的 API 封装
messages: {
"open-test-panel"() {
diff --git a/panel/index.html b/panel/index.html
index 37e9a40..34f718c 100644
--- a/panel/index.html
+++ b/panel/index.html
@@ -1,23 +1,71 @@
-
diff --git a/panel/index.js b/panel/index.js
index f662c04..f427e82 100644
--- a/panel/index.js
+++ b/panel/index.js
@@ -23,6 +23,24 @@ Editor.Panel.extend({
const btnCopy = this.shadowRoot.querySelector("#btnCopy");
const logView = this.shadowRoot.querySelector("#logConsole");
+ // 标签页元素
+ const tabMain = this.shadowRoot.querySelector("#tabMain");
+ const tabTest = this.shadowRoot.querySelector("#tabTest");
+ const panelMain = this.shadowRoot.querySelector("#panelMain");
+ const panelTest = this.shadowRoot.querySelector("#panelTest");
+
+ // 测试面板元素
+ const toolNameInput = this.shadowRoot.querySelector("#toolName");
+ const toolParamsTextarea = this.shadowRoot.querySelector("#toolParams");
+ const toolsList = this.shadowRoot.querySelector("#toolsList");
+ const testBtn = this.shadowRoot.querySelector("#testBtn");
+ const listToolsBtn = this.shadowRoot.querySelector("#listToolsBtn");
+ const clearBtn = this.shadowRoot.querySelector("#clearBtn");
+ const resultContent = this.shadowRoot.querySelector("#resultContent");
+
+ let tools = [];
+ const API_BASE = 'http://localhost:3456';
+
// 初始化
Editor.Ipc.sendToMain("mcp-bridge:get-server-state", (err, data) => {
if (data) {
@@ -32,6 +50,23 @@ Editor.Panel.extend({
}
});
+ // 标签页切换
+ tabMain.addEventListener("confirm", () => {
+ tabMain.classList.add("active");
+ tabTest.classList.remove("active");
+ panelMain.classList.add("active");
+ panelTest.classList.remove("active");
+ });
+
+ tabTest.addEventListener("confirm", () => {
+ tabTest.classList.add("active");
+ tabMain.classList.remove("active");
+ panelTest.classList.add("active");
+ panelMain.classList.remove("active");
+ // 自动获取工具列表
+ this.getToolsList();
+ });
+
btnToggle.addEventListener("confirm", () => {
Editor.Ipc.sendToMain("mcp-bridge:toggle-server", parseInt(portInput.value));
});
@@ -45,6 +80,7 @@ Editor.Panel.extend({
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;
@@ -56,10 +92,218 @@ Editor.Panel.extend({
data.logs.forEach((log) => this.renderLog(log));
}
});
+
autoStartCheck.addEventListener("change", (event) => {
// event.target.value 在 ui-checkbox 中是布尔值
Editor.Ipc.sendToMain("mcp-bridge:set-auto-start", event.target.value);
});
+
+ // 测试面板事件
+ testBtn.addEventListener("confirm", () => this.testTool());
+ listToolsBtn.addEventListener("confirm", () => this.getToolsList());
+ clearBtn.addEventListener("confirm", () => this.clearResult());
+
+ // 获取工具列表
+ this.getToolsList = function() {
+ this.showResult('获取工具列表中...');
+
+ fetch(`${API_BASE}/list-tools`)
+ .then(response => {
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return response.json();
+ })
+ .then(data => {
+ if (data.tools) {
+ tools = data.tools;
+ this.displayToolsList(tools);
+ this.showResult(`成功获取 ${tools.length} 个工具`, 'success');
+ } else {
+ this.showResult('获取工具列表失败:未找到工具数据', 'error');
+ }
+ })
+ .catch(error => {
+ this.showResult(`获取工具列表失败:${error.message}`, 'error');
+ });
+ };
+
+ // 显示工具列表
+ this.displayToolsList = function(tools) {
+ toolsList.innerHTML = '';
+
+ tools.forEach(tool => {
+ const toolItem = document.createElement('div');
+ toolItem.className = 'tool-item';
+ toolItem.textContent = `${tool.name} - ${tool.description}`;
+ toolItem.addEventListener('click', () => {
+ toolNameInput.value = tool.name;
+ // 尝试填充示例参数
+ this.fillExampleParams(tool);
+ });
+ toolsList.appendChild(toolItem);
+ });
+ };
+
+ // 填充示例参数
+ this.fillExampleParams = function(tool) {
+ let exampleParams = {};
+
+ switch (tool.name) {
+ case 'get_selected_node':
+ case 'save_scene':
+ case 'get_scene_hierarchy':
+ exampleParams = {};
+ break;
+
+ case 'set_node_name':
+ exampleParams = {
+ "id": "节点UUID",
+ "newName": "新节点名称"
+ };
+ break;
+
+ case 'update_node_transform':
+ exampleParams = {
+ "id": "节点UUID",
+ "x": 100,
+ "y": 100,
+ "scaleX": 1,
+ "scaleY": 1
+ };
+ break;
+
+ case 'create_scene':
+ exampleParams = {
+ "sceneName": "NewScene"
+ };
+ break;
+
+ case 'create_prefab':
+ exampleParams = {
+ "nodeId": "节点UUID",
+ "prefabName": "NewPrefab"
+ };
+ break;
+
+ case 'open_scene':
+ exampleParams = {
+ "url": "db://assets/NewScene.fire"
+ };
+ break;
+
+ case 'create_node':
+ exampleParams = {
+ "name": "NewNode",
+ "parentId": "父节点UUID",
+ "type": "empty"
+ };
+ break;
+
+ case 'manage_components':
+ exampleParams = {
+ "nodeId": "节点UUID",
+ "action": "add",
+ "componentType": "cc.Button"
+ };
+ break;
+
+ case 'manage_script':
+ exampleParams = {
+ "action": "create",
+ "path": "db://assets/scripts/TestScript.ts",
+ "content": "const { ccclass, property } = cc._decorator;\n\n@ccclass\nexport default class TestScript extends cc.Component {\n // LIFE-CYCLE CALLBACKS:\n\n onLoad () {}\n\n start () {}\n\n update (dt) {}\n}"
+ };
+ break;
+
+ case 'batch_execute':
+ exampleParams = {
+ "operations": [
+ {
+ "tool": "get_selected_node",
+ "params": {}
+ }
+ ]
+ };
+ break;
+
+ case 'manage_asset':
+ exampleParams = {
+ "action": "create",
+ "path": "db://assets/test.txt",
+ "content": "Hello, MCP!"
+ };
+ break;
+ }
+
+ toolParamsTextarea.value = JSON.stringify(exampleParams, null, 2);
+ };
+
+ // 测试工具
+ this.testTool = function() {
+ const toolName = toolNameInput.value.trim();
+ const toolParamsStr = toolParamsTextarea.value.trim();
+
+ if (!toolName) {
+ this.showResult('请输入工具名称', 'error');
+ return;
+ }
+
+ let toolParams;
+ try {
+ toolParams = toolParamsStr ? JSON.parse(toolParamsStr) : {};
+ } catch (error) {
+ this.showResult(`参数格式错误:${error.message}`, 'error');
+ return;
+ }
+
+ this.showResult('测试工具中...');
+
+ fetch(`${API_BASE}/call-tool`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ name: toolName,
+ arguments: toolParams
+ })
+ })
+ .then(response => {
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return response.json();
+ })
+ .then(data => {
+ if (data.error) {
+ this.showResult(`测试失败:${data.error}`, 'error');
+ } else {
+ this.showResult(JSON.stringify(data, null, 2), 'success');
+ }
+ })
+ .catch(error => {
+ this.showResult(`测试失败:${error.message}`, 'error');
+ });
+ };
+
+ // 显示结果
+ this.showResult = function(message, type = 'info') {
+ resultContent.value = message;
+
+ // 移除旧样式
+ resultContent.className = '';
+
+ // 添加新样式
+ if (type === 'error' || type === 'success') {
+ resultContent.className = type;
+ }
+ };
+
+ // 清空结果
+ this.clearResult = function() {
+ this.showResult('点击"测试工具"按钮开始测试');
+ };
},
renderLog(log) {
diff --git a/scene-script.js b/scene-script.js
index c4104a6..f28bddb 100644
--- a/scene-script.js
+++ b/scene-script.js
@@ -91,6 +91,10 @@ module.exports = {
"create-node": function (event, args) {
const { name, parentId, type } = args;
const scene = cc.director.getScene();
+ if (!scene || !cc.director.getRunningScene()) {
+ if (event.reply) event.reply(new Error("Scene not ready or loading."));
+ return;
+ }
let newNode = null;
@@ -119,24 +123,149 @@ module.exports = {
// 设置层级
let parent = parentId ? cc.engine.getInstanceById(parentId) : scene;
- if (newNode) {
+ if (parent) {
newNode.parent = parent;
- // 坐标居中处理(如果是 Canvas 子节点)
- if (parent.name === "Canvas") {
- newNode.setPosition(0, 0);
- } else {
- newNode.setPosition(cc.v2(cc.winSize.width / 2, cc.winSize.height / 2));
- }
-
- // 通知编辑器刷新
+ // 【优化】通知主进程场景变脏
Editor.Ipc.sendToMain("scene:dirty");
- Editor.Ipc.sendToAll("scene:node-created", {
- uuid: newNode.uuid,
- parentUuid: parent.uuid,
- });
+
+ // 【关键】使用 setTimeout 延迟通知 UI 刷新,让出主循环
+ setTimeout(() => {
+ Editor.Ipc.sendToAll("scene:node-created", {
+ uuid: newNode.uuid,
+ parentUuid: parent.uuid,
+ });
+ }, 10);
if (event.reply) event.reply(null, newNode.uuid);
}
},
+
+ "manage-components": function (event, args) {
+ const { nodeId, action, componentType, componentId, properties } = args;
+ let node = cc.engine.getInstanceById(nodeId);
+
+ if (!node) {
+ if (event.reply) event.reply(new Error("Node not found"));
+ return;
+ }
+
+ switch (action) {
+ case "add":
+ if (!componentType) {
+ if (event.reply) event.reply(new Error("Component type is required"));
+ return;
+ }
+
+ try {
+ // 解析组件类型
+ let compClass = null;
+ if (componentType.startsWith("cc.")) {
+ const className = componentType.replace("cc.", "");
+ compClass = cc[className];
+ } else {
+ // 尝试获取自定义组件
+ compClass = cc.js.getClassByName(componentType);
+ }
+
+ if (!compClass) {
+ if (event.reply) event.reply(new Error(`Component type not found: ${componentType}`));
+ return;
+ }
+
+ // 添加组件
+ const component = node.addComponent(compClass);
+
+ // 设置属性
+ if (properties) {
+ for (const [key, value] of Object.entries(properties)) {
+ if (component[key] !== undefined) {
+ component[key] = value;
+ }
+ }
+ }
+
+ Editor.Ipc.sendToMain("scene:dirty");
+ Editor.Ipc.sendToAll("scene:node-changed", { uuid: nodeId });
+
+ if (event.reply) event.reply(null, `Component ${componentType} added`);
+ } catch (err) {
+ if (event.reply) event.reply(new Error(`Failed to add component: ${err.message}`));
+ }
+ break;
+
+ case "remove":
+ if (!componentId) {
+ if (event.reply) event.reply(new Error("Component ID is required"));
+ return;
+ }
+
+ try {
+ // 查找并移除组件
+ const component = node.getComponentById(componentId);
+ if (component) {
+ node.removeComponent(component);
+ Editor.Ipc.sendToMain("scene:dirty");
+ Editor.Ipc.sendToAll("scene:node-changed", { uuid: nodeId });
+ if (event.reply) event.reply(null, "Component removed");
+ } else {
+ if (event.reply) event.reply(new Error("Component not found"));
+ }
+ } catch (err) {
+ if (event.reply) event.reply(new Error(`Failed to remove component: ${err.message}`));
+ }
+ break;
+
+ case "get":
+ try {
+ const components = node._components.map((c) => {
+ // 获取组件属性
+ const properties = {};
+ for (const key in c) {
+ if (typeof c[key] !== "function" &&
+ !key.startsWith("_") &&
+ c[key] !== undefined) {
+ try {
+ properties[key] = c[key];
+ } catch (e) {
+ // 忽略无法序列化的属性
+ }
+ }
+ }
+ return {
+ type: c.__typename,
+ uuid: c.uuid,
+ properties: properties
+ };
+ });
+ if (event.reply) event.reply(null, components);
+ } catch (err) {
+ if (event.reply) event.reply(new Error(`Failed to get components: ${err.message}`));
+ }
+ break;
+
+ default:
+ if (event.reply) event.reply(new Error(`Unknown component action: ${action}`));
+ break;
+ }
+ },
+
+ "get-component-properties": function (component) {
+ const properties = {};
+
+ // 遍历组件属性
+ for (const key in component) {
+ if (typeof component[key] !== "function" &&
+ !key.startsWith("_") &&
+ component[key] !== undefined) {
+ try {
+ properties[key] = component[key];
+ } catch (e) {
+ // 忽略无法序列化的属性
+ }
+ }
+ }
+
+ return properties;
+ },
};