From c9e0f45c9d7c2e5d09154f03fc21a2ac7f1a35a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=81=AB=E7=84=B0=E5=BA=93=E6=8B=89?= Date: Sat, 28 Feb 2026 09:33:36 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84:=20=E5=B0=86=20TypeScript=20?= =?UTF-8?q?=E8=BD=AC=E4=B8=BA=20JavaScript=20=E5=B9=B6=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE=E6=96=87=E4=BB=B6=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 src/IpcManager.ts 和 src/IpcUi.ts 手动转写为干净的 JavaScript,消除编译产物中的 polyfill 代码 - 移动 main.js、scene-script.js、mcp-proxy.js 至 src/ 目录,统一源码管理 - 移动 DEVELOPMENT.md、IPC_MESSAGES.md、UPDATE_LOG.md、注意事项.md 至 docs/ 目录 - 删除 dist/ 编译产物目录和 tsconfig.json - 更新 package.json 入口路径、所有模块引用路径 - 更新 README.md 和 DEVELOPMENT.md 中的架构说明、文件路径引用和项目规范 - 更新 .gitignore 启用 dist 忽略规则 --- .gitignore | 2 +- DEVELOPMENT.md | 537 ------------------------ README.md | 27 +- dist/IpcManager.js | 141 ------- dist/IpcUi.js | 353 ---------------- IPC_MESSAGES.md => docs/IPC_MESSAGES.md | 0 UPDATE_LOG.md => docs/UPDATE_LOG.md | 0 注意事项.md => docs/注意事项.md | 0 package.json | 4 +- panel/index.js | 4 +- src/IpcManager.js | 95 +++++ src/IpcManager.ts | 106 ----- src/IpcUi.js | 226 ++++++++++ src/IpcUi.ts | 226 ---------- main.js => src/main.js | 2 +- mcp-proxy.js => src/mcp-proxy.js | 0 scene-script.js => src/scene-script.js | 0 tsconfig.json | 20 - 18 files changed, 342 insertions(+), 1401 deletions(-) delete mode 100644 DEVELOPMENT.md delete mode 100644 dist/IpcManager.js delete mode 100644 dist/IpcUi.js rename IPC_MESSAGES.md => docs/IPC_MESSAGES.md (100%) rename UPDATE_LOG.md => docs/UPDATE_LOG.md (100%) rename 注意事项.md => docs/注意事项.md (100%) create mode 100644 src/IpcManager.js delete mode 100644 src/IpcManager.ts create mode 100644 src/IpcUi.js delete mode 100644 src/IpcUi.ts rename main.js => src/main.js (99%) rename mcp-proxy.js => src/mcp-proxy.js (100%) rename scene-script.js => src/scene-script.js (100%) delete mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index 8bdea99..331ad62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -# **/dist +**/dist **/node_modules **/package-lock.json \ No newline at end of file diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md deleted file mode 100644 index 0400c54..0000000 --- a/DEVELOPMENT.md +++ /dev/null @@ -1,537 +0,0 @@ -# MCP Bridge 插件开发流程文档 - -本文档记录了 MCP Bridge 插件的完整开发流程,包括核心架构设计、功能实现、测试与调试等各个阶段。 - -## 0. 项目开发规范 (Project Rules) - -> [!IMPORTANT] -> 所有贡献者必须严格遵守以下规则: - -1. **语言与沟通**: 所有注释、文档、计划、任务及 AI 回复必须使用 **简体中文 (Simplified Chinese)**。 -2. **技术栈**: 新脚本必须使用 **TypeScript** (`.ts`)。禁止创建新的 `.js` 文件 (除非是构建脚本或测试配置)。 -3. **文档**: 所有修改或创建的脚本必须包含详细的 JSDoc 格式注释。 -4. **架构**: 严禁引入新的架构模式或重型外部库。必须复用现有的 Cocos Creator 管理器和工具类。 -5. **隔离原则**: 保持 `main.js` (主进程) 与 `scene-script.js` (渲染进程) 的严格职责分离。即便是在主进程中,也应通过 IPC 与场景脚本交互。 -6. **主体校验规则 (Subject Validation Rule)**: 在对节点、组件或属性进行任何“写”操作之前,AI 必须先验证主体的存在性与类型正确性。严禁基于假设进行操作。 - -## 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}`); - } -} -``` - -## 4. 开发历程与里程碑 - -### 2026-02-10: Undo 系统深度修复与规范化 - -- **问题分析**: 修复了 `TypeError: Cannot read property '_name' of null`。该错误是由于直接修改节点属性(绕过 Undo 系统)与分组事务混用导致的。 -- **重构要点**: 将 `update-node-transform` 中所有的直接赋值替换为 `scene:set-property` IPC 调用,确保所有变换修改均受撤销系统监控。 -- **缺陷修正**: 修复了 `manage_undo` 在 `begin_group` 时传递错误参数导致 "Unknown object to record" 的问题。 -- **全量汉化与文档同步**: 完成了 `main.js` 和 `scene-script.js` 的 100% 简体中文翻译。同步更新了 `README.md`、`DEVELOPMENT.md` 及 `注意事项.md`。 - -### 2026-02-13: 新增 `open_prefab` 功能与消息协议修正 - -- **需求实现**: 新增 `open_prefab` 工具,允许 AI 直接打开预制体进入编辑模式。 -- **协议修正**: 经过测试对比,最终确认使用 `scene:enter-prefab-edit-mode` 消息并配合 `Editor.Ipc.sendToAll` 是进入预制体编辑模式的最佳方案,解决了 `scene:open-by-uuid` 无法触发编辑状态的问题。 -- **文档深度补全**: 遵循全局开发规范,同步更新了所有技术文档,确保 100% 简体中文覆盖及详尽的 JSDoc 注释。 - -### 2026-02-25: 修复 manage_script 路径引用错误与强制生成 Meta - -- **缺陷修正**: 修复了 `main.js` 中 `manageScript` 处理 `create` 动作时由于变量名解构导致 `path.dirname` 找不到 `path` 引用的问题。现已改为使用预设的 `pathModule` 模块。 -- **规范强化**: 将 `manage_script` 的工具提示(Prompt)更新为强制要求调用 `refresh_editor` 生成脚本的 `.meta` 文件,以确保新创建的脚本能够被正确挂载为组件,同时不增加整体 Token 消耗。 - -### 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. 开发路线图 (Roadmap) - -### 11.1 第三阶段开发计划(已完成) - -| 任务 | 状态 | 描述 | -| ---------------------- | ------- | ------------------------------------------------------------------- | -| 编辑器管理工具实现 | ✅ 完成 | 实现 manage_editor 工具,支持编辑器状态控制和操作执行 | -| 游戏对象查找工具实现 | ✅ 完成 | 实现 find_gameobjects 工具,支持根据条件查找场景节点 | -| 材质和纹理管理工具实现 | ✅ 完成 | 实现 manage_material 和 manage_texture 工具,支持材质和纹理资源管理 | -| 菜单项执行工具实现 | ✅ 完成 | 实现 execute_menu_item 工具,支持执行 Cocos Creator 菜单项 | -| 代码编辑增强工具实现 | ✅ 完成 | 实现 apply_text_edits 工具,支持文本编辑操作应用 | -| 控制台读取工具实现 | ✅ 完成 | 实现 read_console 工具,支持读取编辑器控制台输出 | -| 脚本验证工具实现 | ✅ 完成 | 实现 validate_script 工具,支持脚本语法验证 | - -### 11.2 第四阶段开发计划(已完成) - -| 任务 | 状态 | 描述 | -| ------------ | ------- | ---------------------------------------------- | -| 测试功能实现 | ✅ 完成 | 实现 run_tests.js 脚本,支持运行自动化测试用例 | -| 错误处理增强 | ✅ 完成 | 完善 HTTP 服务和工具调用的错误日志记录 | - -### 11.3 差异填补阶段(Gap Filling)- 已完成 - -| 任务 | 状态 | 描述 | -| -------- | ------- | ---------------------------------------------- | -| 特效管理 | ✅ 完成 | 实现 manage_vfx 工具,支持粒子系统管理 | -| 文件哈希 | ✅ 完成 | 实现 get_sha 工具,支持文件 SHA-256 计算 | -| 动画管理 | ✅ 完成 | 实现 manage_animation 工具,支持动画播放与控制 | - -### 11.4 第六阶段:可靠性与体验优化(已完成) - -| 任务 | 状态 | 描述 | -| ---------------- | ------- | -------------------------------------------------------------------------- | -| IPC 工具增强 | ✅ 完成 | 修复 IpcManager 返回值解析,优化测试面板 (JSON 参数、筛选) | -| 脚本可靠性修复 | ✅ 完成 | 解决脚本编译时序导致的挂载失败问题 (文档引导 + 刷新机制) | -| 组件智能解析修复 | ✅ 完成 | 修复组件属性赋值时的 UUID 类型转换,支持压缩 UUID 及自定义组件 (`$_$ctor`) | - -### 11.5 第七阶段开发计划(已完成) - -| 任务 | 状态 | 描述 | -| ---------- | ------- | ----------------------------------------- | -| 插件发布 | ✅ 完成 | 准备发布,提交到 Cocos 插件商店 | -| 文档完善 | ✅ 完成 | 完善 API 文档 ("Getting Started" 教程) | -| 界面美化 | ✅ 完成 | 优化面板 UI 体验 | -| 国际化支持 | ✅ 完成 | 添加多语言 (i18n) 支持 (主要是中文本地化) | -| 工具扩展 | ✅ 完成 | 添加更多高级工具 | - -## 12. Unity-MCP 对比分析 - -### 12.1 功能差距 (Gap Analysis) - -通过与 Unity-MCP 对比,Cocos-MCP 已实现绝大多数核心功能。 - -| 功能类别 | Unity-MCP 功能 | Cocos-MCP 状态 | 备注 | -| ---------------- | ----------------- | -------------- | ------------------------------------- | -| 编辑器管理 | manage_editor | ✅ 已实现 | | -| 游戏对象管理 | find_gameobjects | ✅ 已实现 | | -| 材质管理 | manage_material | ✅ 已实现 | | -| 纹理管理 | manage_texture | ✅ 已实现 | | -| 代码编辑 | apply_text_edits | ✅ 已实现 | | -| 全局搜索 | search_project | ✅ 已实现 | 升级版,支持正则和路径限定 | -| 控制台 | read_console | ✅ 已实现 | | -| 菜单执行 | execute_menu_item | ✅ 已实现 | 移除不稳定映射,推荐 delete-node:UUID | -| 脚本验证 | validate_script | ✅ 已实现 | | -| 撤销/重做 | undo/redo | ✅ 已实现 | | -| VFX 管理 | manage_vfx | ✅ 已实现 | | -| Git 集成 | get_sha | ✅ 已实现 | 虽然优先级中等,但已根据需求完成 | -| 动画管理 | manage_animation | ✅ 已实现 | 支持播放、暂停、停止及信息获取 | -| ScriptableObject | manage_so | ❌ 未实现 | 使用 AssetDB 替代 | - -## 13. 风险评估 - -### 13.1 潜在风险 - -| 风险 | 影响 | 缓解措施 | -| --------------- | ------------ | ----------------------------------------- | -| 编辑器 API 变更 | 插件功能失效 | 定期检查 Cocos 更新,适配新 API | -| 性能问题 | 插件响应缓慢 | 优化批处理 (batch_execute),减少 IPC 通讯 | -| 安全漏洞 | 未授权访问 | (规划中) 面板设置 IP 白名单/Token 认证 | -| 兼容性问题 | 多版本不兼容 | 测试主流版本 (2.4.x),提供兼容层 | - -## 14. 结论 - -Cocos-MCP 插件的开发计划已顺利完成多个迭代阶段。目前插件实现了包括编辑器管理、场景操作、资源管理在内的全套核心功能,并完成了针对性的可靠性加固(IPC 通信、脚本时序、组件解析)。 - -插件功能已趋于稳定,后续工作重点将转向 **发布准备**、**文档体系建设** 以及 **用户体验优化**,力求为 Cocos Creator 开发者提供高质量的 AI 辅助开发工具。 diff --git a/README.md b/README.md index fd972a0..1e02aaa 100644 --- a/README.md +++ b/README.md @@ -62,13 +62,13 @@ ``` Command: node -Args: [Cocos Creator 项目的绝对路径]/packages/mcp-bridge/mcp-proxy.js +Args: [Cocos Creator 项目的绝对路径]/packages/mcp-bridge/src/mcp-proxy.js ``` 例如,在你的项目中,完整路径应该是: ``` -Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.js +Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/src/mcp-proxy.js ``` ### 或者添加 JSON 配置: @@ -78,13 +78,13 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j "mcpServers": { "cocos-creator": { "command": "node", - "args": ["[Cocos Creator 项目的绝对路径]/packages/mcp-bridge/mcp-proxy.js"] + "args": ["[Cocos Creator 项目的绝对路径]/packages/mcp-bridge/src/mcp-proxy.js"] } } } ``` -注意:请将上述配置中的路径替换为你自己项目中 `mcp-proxy.js` 文件的实际绝对路径。 +注意:请将上述配置中的路径替换为你自己项目中 `src/mcp-proxy.js` 文件的实际绝对路径。 ## API 接口 @@ -373,8 +373,11 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j 插件采用了典型的 Cocos Creator 扩展架构,包含以下几个部分: -- **main.js**: 插件主入口,负责启动 HTTP 服务和处理 MCP 请求 -- **scene-script.js**: 场景脚本,负责实际执行节点操作 +- **src/main.js**: 插件主入口,负责启动 HTTP 服务和处理 MCP 请求 +- **src/scene-script.js**: 场景脚本,负责实际执行节点操作 +- **src/mcp-proxy.js**: MCP 代理,负责在 AI 工具和插件之间转发请求 +- **src/IpcManager.js**: IPC 消息管理器 +- **src/IpcUi.js**: IPC 测试面板 UI - **panel/**: 面板界面,提供用户交互界面 - `index.html`: 面板 UI 结构 - `index.js`: 面板交互逻辑 @@ -393,9 +396,9 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j ### 数据流 1. 外部工具发送 MCP 请求到插件的 HTTP 接口 -2. main.js 接收请求并解析参数 -3. 通过 Editor.Scene.callSceneScript 将请求转发给 scene-script.js -4. scene-script.js 在场景线程中执行具体操作 +2. src/main.js 接收请求并解析参数 +3. 通过 Editor.Scene.callSceneScript 将请求转发给 src/scene-script.js +4. src/scene-script.js 在场景线程中执行具体操作 5. 将结果返回给外部工具 ## 开发指南 @@ -404,9 +407,9 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j 要在插件中添加新的 MCP 工具,需要: -1. 在 main.js 的 `/list-tools` 响应中添加工具定义 +1. 在 src/main.js 的 `/list-tools` 响应中添加工具定义 2. 在 handleMcpCall 函数中添加对应的处理逻辑 -3. 如需在场景线程中执行,需要在 scene-script.js 中添加对应函数 +3. 如需在场景线程中执行,需要在 src/scene-script.js 中添加对应函数 ### 日志管理 @@ -439,7 +442,7 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j ## 更新日志 -请查阅 [UPDATE_LOG.md](./UPDATE_LOG.md) 了解详细的版本更新历史、功能优化与修复过程。 +请查阅 [UPDATE_LOG.md](./docs/UPDATE_LOG.md) 了解详细的版本更新历史、功能优化与修复过程。 ## 贡献 diff --git a/dist/IpcManager.js b/dist/IpcManager.js deleted file mode 100644 index 6ce3fe6..0000000 --- a/dist/IpcManager.js +++ /dev/null @@ -1,141 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); - return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.IpcManager = void 0; -// @ts-ignore -var fs = require('fs'); -// @ts-ignore -var path = require('path'); -/** - * IPC 消息管理器 - * 负责解析 IPC 文档并执行消息测试 - */ -var IpcManager = /** @class */ (function () { - function IpcManager() { - } - /** - * 获取所有 IPC 消息列表 - * @returns 消息定义列表 - */ - IpcManager.getIpcMessages = function () { - // 获取文档路径 - // @ts-ignore - var docPath = Editor.url('packages://mcp-bridge/IPC_MESSAGES.md'); - if (!fs.existsSync(docPath)) { - // @ts-ignore - Editor.error("[IPC Manager] Document not found: ".concat(docPath)); - return []; - } - var content = fs.readFileSync(docPath, 'utf-8'); - var messages = []; - // 正则匹配 ### `message-name` - var regex = /### `(.*?)`\r?\n([\s\S]*?)(?=### `|$)/g; - var match; - while ((match = regex.exec(content)) !== null) { - var name_1 = match[1]; - var body = match[2]; - // 解析用途 - var purposeMatch = body.match(/- \*\*用途\*\*: (.*)/); - var description = purposeMatch ? purposeMatch[1].trim() : "无描述"; - // 解析参数 - var paramsMatch = body.match(/- \*\*参数\*\*: (.*)/); - var params = paramsMatch ? paramsMatch[1].trim() : "无"; - // 解析返回值 - var returnMatch = body.match(/- \*\*返回值\*\*: (.*)/); - var returns = returnMatch ? returnMatch[1].trim() : "无"; - // 解析类型 - var typeMatch = body.match(/- \*\*类型\*\*: (.*)/); - var type = typeMatch ? typeMatch[1].trim() : "未定义"; - // 解析状态 - var statusMatch = body.match(/- \*\*状态\*\*: (.*)/); - var status_1 = statusMatch ? statusMatch[1].trim() : "未测试"; - // 过滤掉广播事件和渲染进程监听的事件 - if (type === "广播事件" || type === "Events listened by Renderer Process" || type === "渲染进程监听") { - continue; - } - messages.push({ - name: name_1, - description: description, - params: params, - returns: returns, - type: type, - status: status_1 - }); - } - return messages; - }; - /** - * 测试发送 IPC 消息 - * @param name 消息名称 - * @param args 参数 - * @returns Promise 测试结果 - */ - IpcManager.testIpcMessage = function (name_2) { - return __awaiter(this, arguments, void 0, function (name, args) { - if (args === void 0) { args = null; } - return __generator(this, function (_a) { - return [2 /*return*/, new Promise(function (resolve) { - // 简单防呆:防止执行危险操作 - // 如果消息包含 "delete", "remove", "close", "stop" 且没有明确参数确认,则警告 - // 但用户要求"快速验证",所以我们默认允许,但如果是无参调用可能有风险 - // 这里我们尝试使用 Editor.Ipc.sendToMain 或 requestToMain - // @ts-ignore - // 简单的测试:只是发送看看是否报错。 - // 对于 request 类型的消息,我们期望有回调 - // Cocos Creator 2.4 API: Editor.Ipc.sendToMain(message, ...args) - try { - // @ts-ignore - if (Editor.Ipc.sendToMain) { - // @ts-ignore - Editor.Ipc.sendToMain(name, args); - resolve({ success: true, message: "Message sent (sendToMain)" }); - } - else { - resolve({ success: false, message: "Editor.Ipc.sendToMain not available" }); - } - } - catch (e) { - resolve({ success: false, message: "Error: ".concat(e.message) }); - } - })]; - }); - }); - }; - return IpcManager; -}()); -exports.IpcManager = IpcManager; diff --git a/dist/IpcUi.js b/dist/IpcUi.js deleted file mode 100644 index 322c0c3..0000000 --- a/dist/IpcUi.js +++ /dev/null @@ -1,353 +0,0 @@ -"use strict"; -var __awaiter = - (this && this.__awaiter) || - function (thisArg, _arguments, P, generator) { - function adopt(value) { - return value instanceof P - ? value - : new P(function (resolve) { - resolve(value); - }); - } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { - try { - step(generator.next(value)); - } catch (e) { - reject(e); - } - } - function rejected(value) { - try { - step(generator["throw"](value)); - } catch (e) { - reject(e); - } - } - function step(result) { - result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); - } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); - }; -var __generator = - (this && this.__generator) || - function (thisArg, body) { - var _ = { - label: 0, - sent: function () { - if (t[0] & 1) throw t[1]; - return t[1]; - }, - trys: [], - ops: [], - }, - f, - y, - t, - g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); - return ( - (g.next = verb(0)), - (g["throw"] = verb(1)), - (g["return"] = verb(2)), - typeof Symbol === "function" && - (g[Symbol.iterator] = function () { - return this; - }), - g - ); - function verb(n) { - return function (v) { - return step([n, v]); - }; - } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while ((g && ((g = 0), op[0] && (_ = 0)), _)) - try { - if ( - ((f = 1), - y && - (t = - op[0] & 2 - ? y["return"] - : op[0] - ? y["throw"] || ((t = y["return"]) && t.call(y), 0) - : y.next) && - !(t = t.call(y, op[1])).done) - ) - return t; - if (((y = 0), t)) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: - case 1: - t = op; - break; - case 4: - _.label++; - return { value: op[1], done: false }; - case 5: - _.label++; - y = op[1]; - op = [0]; - continue; - case 7: - op = _.ops.pop(); - _.trys.pop(); - continue; - default: - if ( - !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) && - (op[0] === 6 || op[0] === 2) - ) { - _ = 0; - continue; - } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { - _.label = op[1]; - break; - } - if (op[0] === 6 && _.label < t[1]) { - _.label = t[1]; - t = op; - break; - } - if (t && _.label < t[2]) { - _.label = t[2]; - _.ops.push(op); - break; - } - if (t[2]) _.ops.pop(); - _.trys.pop(); - continue; - } - op = body.call(thisArg, _); - } catch (e) { - op = [6, e]; - y = 0; - } finally { - f = t = 0; - } - if (op[0] & 5) throw op[1]; - return { value: op[0] ? op[1] : void 0, done: true }; - } - }; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.IpcUi = void 0; -// @ts-ignore -var Editor = window.Editor; -var IpcUi = /** @class */ (function () { - function IpcUi(root) { - this.logArea = null; - this.ipcList = null; - this.allMessages = []; - this.filterSelect = null; - this.paramInput = null; - this.root = root; - this.bindEvents(); - } - IpcUi.prototype.bindEvents = function () { - var _this = this; - var btnScan = this.root.querySelector("#btnScanIpc"); - var btnTest = this.root.querySelector("#btnTestIpc"); - var cbSelectAll = this.root.querySelector("#cbSelectAllIpc"); - this.logArea = this.root.querySelector("#ipcLog"); - this.ipcList = this.root.querySelector("#ipcList"); - this.filterSelect = this.root.querySelector("#ipcFilter"); - this.paramInput = this.root.querySelector("#ipcParams"); - if (btnScan) { - btnScan.addEventListener("confirm", function () { - return _this.scanMessages(); - }); - } - if (btnTest) { - btnTest.addEventListener("confirm", function () { - return _this.testSelected(); - }); - } - if (cbSelectAll) { - cbSelectAll.addEventListener("change", function (e) { - return _this.toggleAll(e.detail ? e.detail.value : e.target.value === "true" || e.target.checked); - }); - } - if (this.filterSelect) { - this.filterSelect.addEventListener("change", function () { - return _this.filterMessages(); - }); - } - }; - IpcUi.prototype.scanMessages = function () { - var _this = this; - this.log("正在扫描 IPC 消息..."); - // @ts-ignore - Editor.Ipc.sendToMain("mcp-bridge:scan-ipc-messages", function (err, msgs) { - if (err) { - _this.log("扫描错误: ".concat(err)); - return; - } - if (msgs) { - _this.allMessages = msgs; - _this.filterMessages(); - _this.log("找到 ".concat(msgs.length, " 条消息。")); - } else { - _this.log("未找到任何消息。"); - } - }); - }; - IpcUi.prototype.filterMessages = function () { - if (!this.allMessages) return; - var filter = this.filterSelect ? this.filterSelect.value : "all"; - var filtered = this.allMessages; - if (filter === "available") { - filtered = this.allMessages.filter(function (m) { - return m.status === "可用"; - }); - } else if (filter === "unavailable") { - filtered = this.allMessages.filter(function (m) { - return m.status && m.status.includes("不可用"); - }); - } else if (filter === "untested") { - filtered = this.allMessages.filter(function (m) { - return !m.status || m.status === "未测试"; - }); - } - this.renderList(filtered); - }; - IpcUi.prototype.renderList = function (msgs) { - var _this = this; - if (!this.ipcList) return; - this.ipcList.innerHTML = ""; - msgs.forEach(function (msg) { - var item = document.createElement("div"); - item.className = "ipc-item"; - item.style.padding = "4px"; - item.style.borderBottom = "1px solid #333"; - item.style.display = "flex"; - item.style.alignItems = "center"; - // Checkbox - var checkbox = document.createElement("ui-checkbox"); - // @ts-ignore - checkbox.value = false; - checkbox.setAttribute("data-name", msg.name); - checkbox.style.marginRight = "8px"; - // Content - var content = document.createElement("div"); - content.style.flex = "1"; - content.style.fontSize = "11px"; - var statusColor = "#888"; // Untested - if (msg.status === "可用") - statusColor = "#4CAF50"; // Green - else if (msg.status && msg.status.includes("不可用")) statusColor = "#F44336"; // Red - content.innerHTML = - '\n
\n ' - .concat(msg.name, '\n ') - .concat( - msg.status || "未测试", - '\n
\n
', - ) - .concat( - msg.description || "无描述", - '
\n
参数: ', - ) - .concat(msg.params || "无", "
\n "); - // Action Button - var btnRun = document.createElement("ui-button"); - btnRun.innerText = "执行"; - btnRun.className = "tiny"; - btnRun.style.height = "20px"; - btnRun.style.lineHeight = "20px"; - btnRun.addEventListener("confirm", function () { - _this.runTest(msg.name); - }); - item.appendChild(checkbox); - item.appendChild(content); - item.appendChild(btnRun); - _this.ipcList.appendChild(item); - }); - }; - IpcUi.prototype.testSelected = function () { - return __awaiter(this, void 0, void 0, function () { - var checkboxes, toTest, _i, toTest_1, name_1; - return __generator(this, function (_a) { - switch (_a.label) { - case 0: - checkboxes = this.root.querySelectorAll("#ipcList ui-checkbox"); - toTest = []; - checkboxes.forEach(function (cb) { - // In Cocos 2.x, ui-checkbox value is boolean - if (cb.checked || cb.value === true) { - toTest.push(cb.getAttribute("data-name")); - } - }); - if (toTest.length === 0) { - this.log("未选择任何消息。"); - return [2 /*return*/]; - } - this.log("开始批量测试 ".concat(toTest.length, " 条消息...")); - ((_i = 0), (toTest_1 = toTest)); - _a.label = 1; - case 1: - if (!(_i < toTest_1.length)) return [3 /*break*/, 4]; - name_1 = toTest_1[_i]; - return [4 /*yield*/, this.runTest(name_1)]; - case 2: - _a.sent(); - _a.label = 3; - case 3: - _i++; - return [3 /*break*/, 1]; - case 4: - this.log("批量测试完成。"); - return [2 /*return*/]; - } - }); - }); - }; - IpcUi.prototype.runTest = function (name) { - var _this = this; - return new Promise(function (resolve) { - var params = null; - if (_this.paramInput && _this.paramInput.value.trim()) { - try { - params = JSON.parse(_this.paramInput.value.trim()); - } catch (e) { - _this.log("[错误] 无效的 JSON 参数: ".concat(e)); - resolve(); - return; - } - } - _this.log("正在测试: ".concat(name, ",参数: ").concat(JSON.stringify(params), "...")); - // @ts-ignore - Editor.Ipc.sendToMain( - "mcp-bridge:test-ipc-message", - { name: name, params: params }, - function (err, result) { - if (err) { - _this.log("[".concat(name, "] 失败: ").concat(err)); - } else { - _this.log("[".concat(name, "] 成功: ").concat(JSON.stringify(result))); - } - resolve(); - }, - ); - }); - }; - IpcUi.prototype.toggleAll = function (checked) { - var checkboxes = this.root.querySelectorAll("#ipcList ui-checkbox"); - checkboxes.forEach(function (cb) { - cb.value = checked; - }); - }; - IpcUi.prototype.log = function (msg) { - if (this.logArea) { - // @ts-ignore - var time = new Date().toLocaleTimeString(); - this.logArea.value += "[".concat(time, "] ").concat(msg, "\n"); - this.logArea.scrollTop = this.logArea.scrollHeight; - } - }; - return IpcUi; -})(); -exports.IpcUi = IpcUi; diff --git a/IPC_MESSAGES.md b/docs/IPC_MESSAGES.md similarity index 100% rename from IPC_MESSAGES.md rename to docs/IPC_MESSAGES.md diff --git a/UPDATE_LOG.md b/docs/UPDATE_LOG.md similarity index 100% rename from UPDATE_LOG.md rename to docs/UPDATE_LOG.md diff --git a/注意事项.md b/docs/注意事项.md similarity index 100% rename from 注意事项.md rename to docs/注意事项.md diff --git a/package.json b/package.json index 85ddd51..881e9d0 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "1.0.0", "description": "Cocos Creator MCP 桥接插件", "author": "Firekula", - "main": "main.js", - "scene-script": "scene-script.js", + "main": "src/main.js", + "scene-script": "src/scene-script.js", "main-menu": { "MCP 桥接器/开启测试面板": { "message": "mcp-bridge:open-test-panel" diff --git a/panel/index.js b/panel/index.js index b071f04..a0fb0b7 100644 --- a/panel/index.js +++ b/panel/index.js @@ -6,7 +6,7 @@ */ const fs = require("fs"); -const { IpcUi } = require("../dist/IpcUi"); +const { IpcUi } = require("../src/IpcUi"); Editor.Panel.extend({ /** @@ -87,7 +87,7 @@ Editor.Panel.extend({ } }); - // 初始化 IPC 调试专用 UI (由 TypeScript 编写并编译到 dist) + // 初始化 IPC 调试专用 UI new IpcUi(root); // 2. 标签页切换逻辑 diff --git a/src/IpcManager.js b/src/IpcManager.js new file mode 100644 index 0000000..082ac01 --- /dev/null +++ b/src/IpcManager.js @@ -0,0 +1,95 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); + +/** + * IPC 消息管理器 + * 负责解析 IPC 文档并执行消息测试 + */ +class IpcManager { + /** + * 获取所有 IPC 消息列表 + * @returns {Array} 消息定义列表 + */ + static getIpcMessages() { + // 获取文档路径 + const docPath = Editor.url("packages://mcp-bridge/docs/IPC_MESSAGES.md"); + if (!fs.existsSync(docPath)) { + Editor.error(`[IPC 管理器] 找不到文档文件: ${docPath}`); + return []; + } + + const content = fs.readFileSync(docPath, "utf-8"); + const messages = []; + + // 正则匹配 ### `message-name` + const regex = /### `(.*?)`\r?\n([\s\S]*?)(?=### `|$)/g; + let match; + + while ((match = regex.exec(content)) !== null) { + const name = match[1]; + const body = match[2]; + + // 解析用途 + const purposeMatch = body.match(/- \*\*用途\*\*: (.*)/); + const description = purposeMatch ? purposeMatch[1].trim() : "无描述"; + + // 解析参数 + const paramsMatch = body.match(/- \*\*参数\*\*: (.*)/); + const params = paramsMatch ? paramsMatch[1].trim() : "无"; + + // 解析返回值 + const returnMatch = body.match(/- \*\*返回值\*\*: (.*)/); + const returns = returnMatch ? returnMatch[1].trim() : "无"; + + // 解析类型 + const typeMatch = body.match(/- \*\*类型\*\*: (.*)/); + const type = typeMatch ? typeMatch[1].trim() : "未定义"; + + // 解析状态 + const statusMatch = body.match(/- \*\*状态\*\*: (.*)/); + const status = statusMatch ? statusMatch[1].trim() : "未测试"; + + // 过滤掉广播事件和渲染进程监听的事件 + if (type === "广播事件" || type === "Events listened by Renderer Process" || type === "渲染进程监听") { + continue; + } + + messages.push({ + name, + description, + params, + returns, + type, + status, + }); + } + + return messages; + } + + /** + * 测试发送 IPC 消息 + * @param {string} name 消息名称 + * @param {*} args 参数 + * @returns {Promise} 测试结果 + */ + static testIpcMessage(name, args) { + if (args === undefined) args = null; + return new Promise((resolve) => { + try { + if (Editor.Ipc.sendToMain) { + Editor.Ipc.sendToMain(name, args); + resolve({ success: true, message: "消息已发送 (sendToMain)" }); + } else { + resolve({ success: false, message: "Editor.Ipc.sendToMain 不可用" }); + } + } catch (e) { + resolve({ success: false, message: `错误: ${e.message}` }); + } + }); + } +} + +module.exports = { IpcManager }; diff --git a/src/IpcManager.ts b/src/IpcManager.ts deleted file mode 100644 index 5a3a309..0000000 --- a/src/IpcManager.ts +++ /dev/null @@ -1,106 +0,0 @@ -// @ts-ignore -const fs = require("fs"); -// @ts-ignore -const path = require("path"); - -/** - * IPC 消息管理器 - * 负责解析 IPC 文档并执行消息测试 - */ -export class IpcManager { - /** - * 获取所有 IPC 消息列表 - * @returns 消息定义列表 - */ - public static getIpcMessages(): any[] { - // 获取文档路径 - // @ts-ignore - const docPath = Editor.url("packages://mcp-bridge/IPC_MESSAGES.md"); - if (!fs.existsSync(docPath)) { - // @ts-ignore - Editor.error(`[IPC 管理器] 找不到文档文件: ${docPath}`); - return []; - } - - const content = fs.readFileSync(docPath, "utf-8"); - const messages: any[] = []; - - // 正则匹配 ### `message-name` - const regex = /### `(.*?)`\r?\n([\s\S]*?)(?=### `|$)/g; - let match; - - while ((match = regex.exec(content)) !== null) { - const name = match[1]; - const body = match[2]; - - // 解析用途 - const purposeMatch = body.match(/- \*\*用途\*\*: (.*)/); - const description = purposeMatch ? purposeMatch[1].trim() : "无描述"; - - // 解析参数 - const paramsMatch = body.match(/- \*\*参数\*\*: (.*)/); - const params = paramsMatch ? paramsMatch[1].trim() : "无"; - - // 解析返回值 - const returnMatch = body.match(/- \*\*返回值\*\*: (.*)/); - const returns = returnMatch ? returnMatch[1].trim() : "无"; - - // 解析类型 - const typeMatch = body.match(/- \*\*类型\*\*: (.*)/); - const type = typeMatch ? typeMatch[1].trim() : "未定义"; - - // 解析状态 - const statusMatch = body.match(/- \*\*状态\*\*: (.*)/); - const status = statusMatch ? statusMatch[1].trim() : "未测试"; - - // 过滤掉广播事件和渲染进程监听的事件 - if (type === "广播事件" || type === "Events listened by Renderer Process" || type === "渲染进程监听") { - continue; - } - - messages.push({ - name, - description, - params, - returns, - type, - status, - }); - } - - return messages; - } - - /** - * 测试发送 IPC 消息 - * @param name 消息名称 - * @param args 参数 - * @returns Promise 测试结果 - */ - public static async testIpcMessage(name: string, args: any = null): Promise { - return new Promise((resolve) => { - // 简单防呆:防止执行危险操作 - // 如果消息包含 "delete", "remove", "close", "stop" 且没有明确参数确认,则警告 - // 但用户要求"快速验证",所以我们默认允许,但如果是无参调用可能有风险 - // 这里我们尝试使用 Editor.Ipc.sendToMain 或 requestToMain - - // @ts-ignore - // 简单的测试:只是发送看看是否报错。 - // 对于 request 类型的消息,我们期望有回调 - // Cocos Creator 2.4 API: Editor.Ipc.sendToMain(message, ...args) - - try { - // @ts-ignore - if (Editor.Ipc.sendToMain) { - // @ts-ignore - Editor.Ipc.sendToMain(name, args); - resolve({ success: true, message: "消息已发送 (sendToMain)" }); - } else { - resolve({ success: false, message: "Editor.Ipc.sendToMain 不可用" }); - } - } catch (e: any) { - resolve({ success: false, message: `错误: ${e.message}` }); - } - }); - } -} diff --git a/src/IpcUi.js b/src/IpcUi.js new file mode 100644 index 0000000..d5568c6 --- /dev/null +++ b/src/IpcUi.js @@ -0,0 +1,226 @@ +"use strict"; + +/** + * IPC 测试面板 UI 管理器 + * 负责在面板中展示和测试 IPC 消息 + */ +class IpcUi { + /** + * 构造函数 + * @param {ShadowRoot} root Shadow UI 根节点 + */ + constructor(root) { + this.root = root; + this.logArea = null; + this.ipcList = null; + this.allMessages = []; + this.filterSelect = null; + this.paramInput = null; + this.bindEvents(); + } + + /** + * 绑定 UI 事件 + */ + bindEvents() { + const btnScan = this.root.querySelector("#btnScanIpc"); + const btnTest = this.root.querySelector("#btnTestIpc"); + const cbSelectAll = this.root.querySelector("#cbSelectAllIpc"); + this.logArea = this.root.querySelector("#ipcLog"); + this.ipcList = this.root.querySelector("#ipcList"); + this.filterSelect = this.root.querySelector("#ipcFilter"); + this.paramInput = this.root.querySelector("#ipcParams"); + + if (btnScan) { + btnScan.addEventListener("confirm", () => this.scanMessages()); + } + if (btnTest) { + btnTest.addEventListener("confirm", () => this.testSelected()); + } + if (cbSelectAll) { + cbSelectAll.addEventListener("change", (e) => + this.toggleAll(e.detail ? e.detail.value : e.target.value === "true" || e.target.checked), + ); + } + if (this.filterSelect) { + this.filterSelect.addEventListener("change", () => this.filterMessages()); + } + } + + /** + * 扫描 IPC 消息 + */ + scanMessages() { + this.log("正在扫描 IPC 消息..."); + Editor.Ipc.sendToMain("mcp-bridge:scan-ipc-messages", (err, msgs) => { + if (err) { + this.log(`扫描错误: ${err}`); + return; + } + if (msgs) { + this.allMessages = msgs; + this.filterMessages(); + this.log(`找到 ${msgs.length} 条消息。`); + } else { + this.log("未找到任何消息。"); + } + }); + } + + /** + * 根据当前选择器过滤消息列表 + */ + filterMessages() { + if (!this.allMessages) return; + const filter = this.filterSelect ? this.filterSelect.value : "all"; + + let filtered = this.allMessages; + if (filter === "available") { + filtered = this.allMessages.filter((m) => m.status === "可用"); + } else if (filter === "unavailable") { + filtered = this.allMessages.filter((m) => m.status && m.status.includes("不可用")); + } else if (filter === "untested") { + filtered = this.allMessages.filter((m) => !m.status || m.status === "未测试"); + } + + this.renderList(filtered); + } + + /** + * 渲染消息列表 UI + * @param {Array} msgs 消息对象数组 + */ + renderList(msgs) { + if (!this.ipcList) return; + this.ipcList.innerHTML = ""; + + msgs.forEach((msg) => { + const item = document.createElement("div"); + item.className = "ipc-item"; + item.style.padding = "4px"; + item.style.borderBottom = "1px solid #333"; + item.style.display = "flex"; + item.style.alignItems = "center"; + + // 复选框 + const checkbox = document.createElement("ui-checkbox"); + checkbox.value = false; + checkbox.setAttribute("data-name", msg.name); + checkbox.style.marginRight = "8px"; + + // 内容区域 + const content = document.createElement("div"); + content.style.flex = "1"; + content.style.fontSize = "11px"; + + let statusColor = "#888"; // 未测试 + if (msg.status === "可用") + statusColor = "#4CAF50"; // 绿色 + else if (msg.status && msg.status.includes("不可用")) statusColor = "#F44336"; // 红色 + + content.innerHTML = ` +
+ ${msg.name} + ${msg.status || "未测试"} +
+
${msg.description || "无描述"}
+
参数: ${msg.params || "无"}
+ `; + + // 执行按钮 + const btnRun = document.createElement("ui-button"); + btnRun.innerText = "执行"; + btnRun.className = "tiny"; + btnRun.style.height = "20px"; + btnRun.style.lineHeight = "20px"; + btnRun.addEventListener("confirm", () => { + this.runTest(msg.name); + }); + + item.appendChild(checkbox); + item.appendChild(content); + item.appendChild(btnRun); + this.ipcList.appendChild(item); + }); + } + + /** + * 测试所有选中的 IPC 消息 + */ + async testSelected() { + const checkboxes = this.root.querySelectorAll("#ipcList ui-checkbox"); + const toTest = []; + checkboxes.forEach((cb) => { + // 在 Cocos 2.x 中, ui-checkbox 的值是布尔型 + if (cb.checked || cb.value === true) { + toTest.push(cb.getAttribute("data-name")); + } + }); + + if (toTest.length === 0) { + this.log("未选择任何消息。"); + return; + } + + this.log(`开始批量测试 ${toTest.length} 条消息...`); + for (const name of toTest) { + await this.runTest(name); + } + this.log("批量测试完成。"); + } + + /** + * 运行单个测试请求 + * @param {string} name 消息名称 + * @returns {Promise} + */ + runTest(name) { + return new Promise((resolve) => { + let params = null; + if (this.paramInput && this.paramInput.value.trim()) { + try { + params = JSON.parse(this.paramInput.value.trim()); + } catch (e) { + this.log(`[错误] 无效的 JSON 参数: ${e}`); + resolve(); + return; + } + } + + this.log(`正在测试: ${name},参数: ${JSON.stringify(params)}...`); + Editor.Ipc.sendToMain("mcp-bridge:test-ipc-message", { name, params }, (err, result) => { + if (err) { + this.log(`[${name}] 失败: ${err}`); + } else { + this.log(`[${name}] 成功: ${JSON.stringify(result)}`); + } + resolve(); + }); + }); + } + + /** + * 全选/取消全选 + * @param {boolean} checked 是否选中 + */ + toggleAll(checked) { + const checkboxes = this.root.querySelectorAll("#ipcList ui-checkbox"); + checkboxes.forEach((cb) => { + cb.value = checked; + }); + } + + /** + * 输出日志到界面 + * @param {string} msg 日志消息 + */ + log(msg) { + if (this.logArea) { + const time = new Date().toLocaleTimeString(); + this.logArea.value += `[${time}] ${msg}\n`; + this.logArea.scrollTop = this.logArea.scrollHeight; + } + } +} + +module.exports = { IpcUi }; diff --git a/src/IpcUi.ts b/src/IpcUi.ts deleted file mode 100644 index eb23128..0000000 --- a/src/IpcUi.ts +++ /dev/null @@ -1,226 +0,0 @@ -// @ts-ignore -const Editor = window.Editor; - -export class IpcUi { - private root: ShadowRoot; - private logArea: HTMLTextAreaElement | null = null; - private ipcList: HTMLElement | null = null; - private allMessages: any[] = []; - private filterSelect: HTMLSelectElement | null = null; - private paramInput: HTMLTextAreaElement | null = null; - - /** - * 构造函数 - * @param root Shadow UI 根节点 - */ - constructor(root: ShadowRoot) { - this.root = root; - this.bindEvents(); - } - - /** - * 绑定 UI 事件 - */ - private bindEvents() { - const btnScan = this.root.querySelector("#btnScanIpc"); - const btnTest = this.root.querySelector("#btnTestIpc"); - const cbSelectAll = this.root.querySelector("#cbSelectAllIpc"); - this.logArea = this.root.querySelector("#ipcLog") as HTMLTextAreaElement; - this.ipcList = this.root.querySelector("#ipcList") as HTMLElement; - this.filterSelect = this.root.querySelector("#ipcFilter") as HTMLSelectElement; - this.paramInput = this.root.querySelector("#ipcParams") as HTMLTextAreaElement; - - if (btnScan) { - btnScan.addEventListener("confirm", () => this.scanMessages()); - } - if (btnTest) { - btnTest.addEventListener("confirm", () => this.testSelected()); - } - if (cbSelectAll) { - cbSelectAll.addEventListener("change", (e: any) => - this.toggleAll(e.detail ? e.detail.value : e.target.value === "true" || e.target.checked), - ); - } - if (this.filterSelect) { - this.filterSelect.addEventListener("change", () => this.filterMessages()); - } - } - - /** - * 扫描 IPC 消息 - */ - private scanMessages() { - this.log("正在扫描 IPC 消息..."); - // @ts-ignore - Editor.Ipc.sendToMain("mcp-bridge:scan-ipc-messages", (err: any, msgs: any[]) => { - if (err) { - this.log(`扫描错误: ${err}`); - return; - } - if (msgs) { - this.allMessages = msgs; - this.filterMessages(); - this.log(`找到 ${msgs.length} 条消息。`); - } else { - this.log("未找到任何消息。"); - } - }); - } - - /** - * 根据当前选择器过滤消息列表 - */ - private filterMessages() { - if (!this.allMessages) return; - const filter = this.filterSelect ? this.filterSelect.value : "all"; - - let filtered = this.allMessages; - if (filter === "available") { - filtered = this.allMessages.filter((m) => m.status === "可用"); - } else if (filter === "unavailable") { - filtered = this.allMessages.filter((m) => m.status && m.status.includes("不可用")); - } else if (filter === "untested") { - filtered = this.allMessages.filter((m) => !m.status || m.status === "未测试"); - } - - this.renderList(filtered); - } - - /** - * 渲染消息列表 UI - * @param msgs 消息对象数组 - */ - private renderList(msgs: any[]) { - if (!this.ipcList) return; - this.ipcList.innerHTML = ""; - - msgs.forEach((msg) => { - const item = document.createElement("div"); - item.className = "ipc-item"; - item.style.padding = "4px"; - item.style.borderBottom = "1px solid #333"; - item.style.display = "flex"; - item.style.alignItems = "center"; - - // 复选框 - const checkbox = document.createElement("ui-checkbox"); - // @ts-ignore - checkbox.value = false; - checkbox.setAttribute("data-name", msg.name); - checkbox.style.marginRight = "8px"; - - // 内容区域 - const content = document.createElement("div"); - content.style.flex = "1"; - content.style.fontSize = "11px"; - - let statusColor = "#888"; // 未测试 - if (msg.status === "可用") - statusColor = "#4CAF50"; // 绿色 - else if (msg.status && msg.status.includes("不可用")) statusColor = "#F44336"; // 红色 - - content.innerHTML = ` -
- ${msg.name} - ${msg.status || "未测试"} -
-
${msg.description || "无描述"}
-
参数: ${msg.params || "无"}
- `; - - // 执行按钮 - const btnRun = document.createElement("ui-button"); - btnRun.innerText = "执行"; - btnRun.className = "tiny"; - btnRun.style.height = "20px"; - btnRun.style.lineHeight = "20px"; - btnRun.addEventListener("confirm", () => { - this.runTest(msg.name); - }); - - item.appendChild(checkbox); - item.appendChild(content); - item.appendChild(btnRun); - this.ipcList!.appendChild(item); - }); - } - - /** - * 测试所有选中的 IPC 消息 - */ - private async testSelected() { - const checkboxes = this.root.querySelectorAll("#ipcList ui-checkbox"); - const toTest: string[] = []; - checkboxes.forEach((cb: any) => { - // 在 Cocos 2.x 中, ui-checkbox 的值是布尔型 - if (cb.checked || cb.value === true) { - toTest.push(cb.getAttribute("data-name")); - } - }); - - if (toTest.length === 0) { - this.log("未选择任何消息。"); - return; - } - - this.log(`开始批量测试 ${toTest.length} 条消息...`); - for (const name of toTest) { - await this.runTest(name); - } - this.log("批量测试完成。"); - } - - /** - * 运行单个测试请求 - * @param name 消息名称 - */ - private runTest(name: string): Promise { - return new Promise((resolve) => { - let params = null; - if (this.paramInput && this.paramInput.value.trim()) { - try { - params = JSON.parse(this.paramInput.value.trim()); - } catch (e) { - this.log(`[错误] 无效的 JSON 参数: ${e}`); - resolve(); - return; - } - } - - this.log(`正在测试: ${name},参数: ${JSON.stringify(params)}...`); - // @ts-ignore - Editor.Ipc.sendToMain("mcp-bridge:test-ipc-message", { name, params }, (err: any, result: any) => { - if (err) { - this.log(`[${name}] 失败: ${err}`); - } else { - this.log(`[${name}] 成功: ${JSON.stringify(result)}`); - } - resolve(); - }); - }); - } - - /** - * 全选/取消全选 - * @param checked 是否选中 - */ - private toggleAll(checked: boolean) { - const checkboxes = this.root.querySelectorAll("#ipcList ui-checkbox"); - checkboxes.forEach((cb: any) => { - cb.value = checked; - }); - } - - /** - * 输出日志到界面 - * @param msg 日志消息 - */ - private log(msg: string) { - if (this.logArea) { - // @ts-ignore - const time = new Date().toLocaleTimeString(); - this.logArea.value += `[${time}] ${msg}\n`; - this.logArea.scrollTop = this.logArea.scrollHeight; - } - } -} diff --git a/main.js b/src/main.js similarity index 99% rename from main.js rename to src/main.js index f165365..8bc9ef1 100644 --- a/main.js +++ b/src/main.js @@ -1,5 +1,5 @@ "use strict"; -const { IpcManager } = require("./dist/IpcManager"); +const { IpcManager } = require("./IpcManager"); const http = require("http"); const pathModule = require("path"); diff --git a/mcp-proxy.js b/src/mcp-proxy.js similarity index 100% rename from mcp-proxy.js rename to src/mcp-proxy.js diff --git a/scene-script.js b/src/scene-script.js similarity index 100% rename from scene-script.js rename to src/scene-script.js diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 1255432..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "lib": [ - "dom", - "es5", - "es2015.promise" - ], - "target": "es5", - "experimentalDecorators": true, - "skipLibCheck": true, - "outDir": "./dist", - "rootDir": "./src", - "forceConsistentCasingInFileNames": true, - "strict": false - }, - "include": [ - "src/**/*" - ] -} \ No newline at end of file