更新大量官方api工具,目前已支持超过150个工具调用,支持除了预制体操作之外的所有cocos 操作。同时mcp端口更健壮,ai使用起来准确率更高。修复大量bug

This commit is contained in:
root
2025-07-22 23:40:03 +08:00
parent 0fe68c0fc8
commit d7ab237707
35 changed files with 9905 additions and 675 deletions

View File

@@ -8,7 +8,7 @@ Cocos Creator MCP 服务器是一个全面的 Model Context Protocol (MCP) 服
## 工具分类 ## 工具分类
MCP 服务器提供了 **80 个工具**,按功能分为 9 个主要类别: MCP 服务器提供了 **151 个工具**,按功能分为 13 个主要类别:
1. [场景操作工具 (Scene Tools)](#1-场景操作工具-scene-tools) 1. [场景操作工具 (Scene Tools)](#1-场景操作工具-scene-tools)
2. [节点操作工具 (Node Tools)](#2-节点操作工具-node-tools) 2. [节点操作工具 (Node Tools)](#2-节点操作工具-node-tools)
@@ -19,6 +19,10 @@ MCP 服务器提供了 **80 个工具**,按功能分为 9 个主要类别:
7. [偏好设置工具 (Preferences Tools)](#7-偏好设置工具-preferences-tools) 7. [偏好设置工具 (Preferences Tools)](#7-偏好设置工具-preferences-tools)
8. [服务器工具 (Server Tools)](#8-服务器工具-server-tools) 8. [服务器工具 (Server Tools)](#8-服务器工具-server-tools)
9. [广播工具 (Broadcast Tools)](#9-广播工具-broadcast-tools) 9. [广播工具 (Broadcast Tools)](#9-广播工具-broadcast-tools)
10. [高级资源工具 (Asset Advanced Tools)](#10-高级资源工具-asset-advanced-tools)
11. [参考图像工具 (Reference Image Tools)](#11-参考图像工具-reference-image-tools)
12. [高级场景工具 (Scene Advanced Tools)](#12-高级场景工具-scene-advanced-tools)
13. [场景视图工具 (Scene View Tools)](#13-场景视图工具-scene-view-tools)
--- ---
@@ -155,10 +159,15 @@ MCP 服务器提供了 **80 个工具**,按功能分为 9 个主要类别:
**参数**: **参数**:
- `name` (string, 必需): 节点名称 - `name` (string, 必需): 节点名称
- `parentUuid` (string, 可选): 父节点UUID如果不提供则在当前编辑器选择位置创建 - `parentUuid` (string, **强烈建议**): 父节点UUID。**重要**:强烈建议始终提供此参数。使用 `get_current_scene``get_all_nodes` 查找父节点UUID。如果不提供节点将在场景根节点创建
- `nodeType` (string, 可选): 节点类型,可选值:`Node``2DNode``3DNode`,默认为 `Node` - `nodeType` (string, 可选): 节点类型,可选值:`Node``2DNode``3DNode`,默认为 `Node`
- `siblingIndex` (number, 可选): 同级索引,-1 表示添加到末尾,默认为 -1 - `siblingIndex` (number, 可选): 同级索引,-1 表示添加到末尾,默认为 -1
**重要提示**: 为了确保节点创建在预期位置,请始终提供 `parentUuid` 参数。您可以通过以下方式获取父节点UUID
- 使用 `scene_get_current_scene` 获取场景根节点UUID
- 使用 `node_get_all_nodes` 查看所有节点及其UUID
- 使用 `node_find_node_by_name` 查找特定节点的UUID
**示例**: **示例**:
```json ```json
{ {
@@ -316,9 +325,14 @@ MCP 服务器提供了 **80 个工具**,按功能分为 9 个主要类别:
向指定节点添加组件 向指定节点添加组件
**参数**: **参数**:
- `nodeUuid` (string, 必需): 目标节点UUID - `nodeUuid` (string, **必需**): 目标节点UUID。**重要**:必须指定要添加组件的确切节点。使用 `get_all_nodes``find_node_by_name` 获取所需节点的UUID。
- `componentType` (string, 必需): 组件类型(如 cc.Sprite、cc.Label、cc.Button - `componentType` (string, 必需): 组件类型(如 cc.Sprite、cc.Label、cc.Button
**重要提示**: 在添加组件之前,请确保:
1. 先使用 `node_get_all_nodes``node_find_node_by_name` 找到目标节点的UUID
2. 验证节点存在且UUID正确
3. 选择合适的组件类型
**示例**: **示例**:
```json ```json
{ {
@@ -442,6 +456,8 @@ MCP 服务器提供了 **80 个工具**,按功能分为 9 个主要类别:
## 4. 预制体操作工具 (Prefab Tools) ## 4. 预制体操作工具 (Prefab Tools)
**⚠️ 已知问题**: 使用标准 Cocos Creator API 进行预制体实例化时,可能无法正确恢复包含子节点的复杂预制体结构。虽然预制体创建功能可以正确保存所有子节点信息,但通过 `create-node` 配合 `assetUuid` 进行的实例化过程存在限制,可能导致实例化的预制体中缺少子节点。
### 4.1 prefab_get_prefab_list ### 4.1 prefab_get_prefab_list
获取项目中所有预制体 获取项目中所有预制体
@@ -494,6 +510,8 @@ MCP 服务器提供了 **80 个工具**,按功能分为 9 个主要类别:
} }
``` ```
**⚠️ 功能限制**: 包含子节点的复杂预制体可能无法正确实例化。由于 Cocos Creator API 在标准 `create-node` 方法中使用 `assetUuid` 的限制,可能只创建根节点,子节点可能会丢失。这是当前实现的已知问题。
### 4.4 prefab_create_prefab ### 4.4 prefab_create_prefab
从节点创建预制体 从节点创建预制体
@@ -1056,6 +1074,61 @@ MCP 服务器提供了 **80 个工具**,按功能分为 9 个主要类别:
} }
``` ```
### 6.8 debug_get_project_logs
从 temp/logs/project.log 文件获取项目日志
**参数**:
- `lines` (number, 可选): 从日志文件末尾读取的行数默认值为100范围1-10000
- `filterKeyword` (string, 可选): 按指定关键词过滤日志
- `logLevel` (string, 可选): 按日志级别过滤,选项:`ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE`, `ALL`,默认为 `ALL`
**示例**:
```json
{
"tool": "debug_get_project_logs",
"arguments": {
"lines": 200,
"filterKeyword": "prefab",
"logLevel": "INFO"
}
}
```
### 6.9 debug_get_log_file_info
获取项目日志文件信息
**参数**: 无
**返回**: 文件大小、最后修改时间、行数和文件路径信息
**示例**:
```json
{
"tool": "debug_get_log_file_info",
"arguments": {}
}
```
### 6.10 debug_search_project_logs
在项目日志中搜索特定模式或错误
**参数**:
- `pattern` (string, 必需): 搜索模式(支持正则表达式)
- `maxResults` (number, 可选): 最大匹配结果数量默认为20范围1-100
- `contextLines` (number, 可选): 匹配结果周围显示的上下文行数默认为2范围0-10
**示例**:
```json
{
"tool": "debug_search_project_logs",
"arguments": {
"pattern": "error|failed|exception",
"maxResults": 10,
"contextLines": 3
}
}
```
--- ---
## 7. 偏好设置工具 (Preferences Tools) ## 7. 偏好设置工具 (Preferences Tools)

View File

@@ -8,7 +8,7 @@ This document provides detailed information about all available MCP tools and th
## Tool Categories ## Tool Categories
The MCP server provides **80 tools** organized into 9 main categories: The MCP server provides **151 tools** organized into 13 main categories:
1. [Scene Tools](#1-scene-tools) 1. [Scene Tools](#1-scene-tools)
2. [Node Tools](#2-node-tools) 2. [Node Tools](#2-node-tools)
@@ -19,6 +19,10 @@ The MCP server provides **80 tools** organized into 9 main categories:
7. [Preferences Tools](#7-preferences-tools) 7. [Preferences Tools](#7-preferences-tools)
8. [Server Tools](#8-server-tools) 8. [Server Tools](#8-server-tools)
9. [Broadcast Tools](#9-broadcast-tools) 9. [Broadcast Tools](#9-broadcast-tools)
10. [Asset Advanced Tools](#10-asset-advanced-tools)
11. [Reference Image Tools](#11-reference-image-tools)
12. [Scene Advanced Tools](#12-scene-advanced-tools)
13. [Scene View Tools](#13-scene-view-tools)
--- ---
@@ -442,6 +446,8 @@ Get list of available component types
## 4. Prefab Tools ## 4. Prefab Tools
**⚠️ Known Issue**: Prefab instantiation using the standard Cocos Creator API may not properly restore complex prefab structures with child nodes. While prefab creation correctly saves all child node information, the instantiation process through `create-node` with `assetUuid` has limitations and may result in missing child nodes in the instantiated prefab.
### 4.1 prefab_get_prefab_list ### 4.1 prefab_get_prefab_list
Get all prefabs in the project Get all prefabs in the project
@@ -494,6 +500,8 @@ Instantiate a prefab in the scene
} }
``` ```
**⚠️ Limitation**: Complex prefabs with child nodes may not instantiate correctly. Only the root node may be created, and child nodes may be missing due to Cocos Creator API limitations in the standard `create-node` method with `assetUuid`. This is a known issue with the current implementation.
### 4.4 prefab_create_prefab ### 4.4 prefab_create_prefab
Create a prefab from a node Create a prefab from a node
@@ -1056,6 +1064,61 @@ Get editor and environment information
} }
``` ```
### 6.8 debug_get_project_logs
Get project logs from temp/logs/project.log file
**Parameters**:
- `lines` (number, optional): Number of lines to read from the end of the log file, default is 100, range: 1-10000
- `filterKeyword` (string, optional): Filter logs containing specific keyword
- `logLevel` (string, optional): Filter by log level, options: `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE`, `ALL`, default is `ALL`
**Example**:
```json
{
"tool": "debug_get_project_logs",
"arguments": {
"lines": 200,
"filterKeyword": "prefab",
"logLevel": "INFO"
}
}
```
### 6.9 debug_get_log_file_info
Get information about the project log file
**Parameters**: None
**Returns**: File size, last modified time, line count, and file path information
**Example**:
```json
{
"tool": "debug_get_log_file_info",
"arguments": {}
}
```
### 6.10 debug_search_project_logs
Search for specific patterns or errors in project logs
**Parameters**:
- `pattern` (string, required): Search pattern (supports regex)
- `maxResults` (number, optional): Maximum number of matching results, default is 20, range: 1-100
- `contextLines` (number, optional): Number of context lines to show around each match, default is 2, range: 0-10
**Example**:
```json
{
"tool": "debug_search_project_logs",
"arguments": {
"pattern": "error|failed|exception",
"maxResults": 10,
"contextLines": 3
}
}
```
--- ---
## 7. Preferences Tools ## 7. Preferences Tools

50
README.md Executable file → Normal file
View File

@@ -4,12 +4,12 @@
A comprehensive MCP (Model Context Protocol) server plugin for Cocos Creator 3.8+, enabling AI assistants to interact with the Cocos Creator editor through standardized protocols. One-click installation and use, eliminating all cumbersome environments and configurations. Claude clients Claude CLI and Cursor have been tested, and other editors are also perfectly supported in theory. A comprehensive MCP (Model Context Protocol) server plugin for Cocos Creator 3.8+, enabling AI assistants to interact with the Cocos Creator editor through standardized protocols. One-click installation and use, eliminating all cumbersome environments and configurations. Claude clients Claude CLI and Cursor have been tested, and other editors are also perfectly supported in theory.
**🚀 Now provides 80 tools in 9 categories, achieving 95% editor control! (Prefabs cannot be manipulated for the time being)** **🚀 Now provides 151 tools in 13 categories, achieving 98% editor control! (Prefab instantiation has known child node restoration issues)**
## Quick Links ## Quick Links
- **[📖 Complete Feature Guide (English)](FEATURE_GUIDE_EN.md)** - Detailed documentation for all 80 tools - **[📖 Complete Feature Guide (English)](FEATURE_GUIDE_EN.md)** - Detailed documentation for all 151 tools(To be completed)
- **[📖 完整功能指南 (中文)](FEATURE_GUIDE_CN.md)** - 所有80个工具的详细文档 - **[📖 完整功能指南 (中文)](FEATURE_GUIDE_CN.md)** - 所有151个工具的详细文档(To be completed)
**Claude cli configuration:** **Claude cli configuration:**
@@ -84,6 +84,7 @@ claude mcp add --transport http http://localhost:3000/mcp (use the port number y
- Load, instantiate, and create prefabs - Load, instantiate, and create prefabs
- Update existing prefabs and revert prefab instances - Update existing prefabs and revert prefab instances
- Get detailed prefab information including dependencies - Get detailed prefab information including dependencies
- **⚠️ Known Issue**: Prefab instantiation may not properly restore child nodes due to Cocos Creator API limitations
### 🚀 Project Control ### 🚀 Project Control
- Run project in preview mode (browser/simulator) - Run project in preview mode (browser/simulator)
@@ -105,6 +106,9 @@ claude mcp add --transport http http://localhost:3000/mcp (use the port number y
- **Message Broadcasting**: Listen to and broadcast custom messages - **Message Broadcasting**: Listen to and broadcast custom messages
- **Asset Management**: Create, copy, move, delete, and query assets - **Asset Management**: Create, copy, move, delete, and query assets
- **Build System**: Project building and preview server control - **Build System**: Project building and preview server control
- **Reference Image Management**: Add, remove, and manage reference images in scene view
- **Scene View Controls**: Control gizmo tools, coordinate systems, and view modes
- **Advanced Scene Operations**: Undo/redo, snapshots, and advanced node manipulation
## Installation ## Installation
@@ -150,7 +154,7 @@ npm run build
1. Open the MCP Server panel from `Extension > Cocos MCP Server` 1. Open the MCP Server panel from `Extension > Cocos MCP Server`
2. Configure settings: 2. Configure settings:
- **Port**: WebSocket server port (default: 3000) - **Port**: HTTP server port (default: 3000)
- **Auto Start**: Automatically start server when editor opens - **Auto Start**: Automatically start server when editor opens
- **Debug Logging**: Enable detailed logging for development - **Debug Logging**: Enable detailed logging for development
- **Max Connections**: Maximum concurrent connections allowed - **Max Connections**: Maximum concurrent connections allowed
@@ -159,7 +163,7 @@ npm run build
### Connecting AI Assistants ### Connecting AI Assistants
The server exposes a WebSocket endpoint at `ws://localhost:3000` (or your configured port). The server exposes an HTTP endpoint at `http://localhost:3000/mcp` (or your configured port).
AI assistants can connect using the MCP protocol and access all available tools. AI assistants can connect using the MCP protocol and access all available tools.
@@ -170,14 +174,17 @@ Tools are organized by category with naming convention: `category_toolname`
- **scene_\***: Scene-related operations (8 tools) - **scene_\***: Scene-related operations (8 tools)
- **node_\***: Node manipulation (9 tools) - **node_\***: Node manipulation (9 tools)
- **component_\***: Component management (7 tools) - **component_\***: Component management (7 tools)
- **prefab_\***: Prefab operations (8 tools) - **prefab_\***: Prefab operations (11 tools)
- **project_\***: Project control (22 tools) - **project_\***: Project control (22 tools)
- **debug_\***: Debugging utilities (7 tools) - **debug_\***: Debugging utilities (10 tools)
- **preferences_\***: Editor preferences (6 tools) - **preferences_\***: Editor preferences (7 tools)
- **server_\***: Server information (8 tools) - **server_\***: Server information (6 tools)
- **broadcast_\***: Message broadcasting (5 tools) - **broadcast_\***: Message broadcasting (5 tools)
- **assetAdvanced_\***: Advanced asset operations (10 tools)
- **referenceImage_\***: Reference image management (12 tools)
- **sceneAdvanced_\***: Advanced scene operations (23 tools)
- **sceneView_\***: Scene view controls (14 tools)
**Total: 80 tools** for comprehensive editor control.
📖 **[View Complete Tool Documentation](FEATURE_GUIDE_EN.md)** for detailed usage examples and parameters. 📖 **[View Complete Tool Documentation](FEATURE_GUIDE_EN.md)** for detailed usage examples and parameters.
@@ -216,6 +223,7 @@ Tools are organized by category with naming convention: `category_toolname`
} }
} }
``` ```
**⚠️ Note**: Complex prefabs with child nodes may not instantiate correctly due to Cocos Creator API limitations. Child nodes may be missing in the instantiated prefab.
### Run project in browser ### Run project in browser
```json ```json
@@ -260,7 +268,21 @@ cocos-mcp-server/
│ ├── settings.ts # Settings management │ ├── settings.ts # Settings management
│ ├── types/ # TypeScript type definitions │ ├── types/ # TypeScript type definitions
│ ├── tools/ # Tool implementations │ ├── tools/ # Tool implementations
└── panels/ # UI panel implementation │ ├── scene-tools.ts
│ │ ├── node-tools.ts
│ │ ├── component-tools.ts
│ │ ├── prefab-tools.ts
│ │ ├── project-tools.ts
│ │ ├── debug-tools.ts
│ │ ├── preferences-tools.ts
│ │ ├── server-tools.ts
│ │ ├── broadcast-tools.ts
│ │ ├── scene-advanced-tools.ts
│ │ ├── scene-view-tools.ts
│ │ ├── reference-image-tools.ts
│ │ └── asset-advanced-tools.ts
│ ├── panels/ # UI panel implementation
│ └── test/ # Test files
├── dist/ # Compiled JavaScript output ├── dist/ # Compiled JavaScript output
├── static/ # Static assets (icons, etc.) ├── static/ # Static assets (icons, etc.)
├── i18n/ # Internationalization files ├── i18n/ # Internationalization files
@@ -316,7 +338,7 @@ node test-mcp-server.js
1. **Server won't start**: Check port availability and firewall settings 1. **Server won't start**: Check port availability and firewall settings
2. **Tools not working**: Ensure scene is loaded and UUIDs are valid 2. **Tools not working**: Ensure scene is loaded and UUIDs are valid
3. **Build errors**: Run `npm run build` to check for TypeScript errors 3. **Build errors**: Run `npm run build` to check for TypeScript errors
4. **Connection issues**: Verify WebSocket URL and server status 4. **Connection issues**: Verify HTTP URL and server status
### Debug Mode ### Debug Mode
@@ -346,10 +368,10 @@ Enable debug logging in the plugin panel for detailed operation logs.
## Architecture Notes ## Architecture Notes
This plugin uses a simplified MCP protocol implementation that is compatible with Cocos Creator's CommonJS environment. The WebSocket server provides a JSON-RPC interface for AI assistants to interact with the editor. This plugin uses a simplified MCP protocol implementation that is compatible with Cocos Creator's CommonJS environment. The HTTP server provides a JSON-RPC interface for AI assistants to interact with the editor.
### Protocol Support ### Protocol Support
- **WebSocket Connection**: `ws://localhost:3000` (configurable port) - **HTTP Connection**: `http://localhost:3000/mcp` (configurable port)
- **JSON-RPC 2.0**: Standard request/response format - **JSON-RPC 2.0**: Standard request/response format
- **Tool Discovery**: `tools/list` method returns available tools - **Tool Discovery**: `tools/list` method returns available tools
- **Tool Execution**: `tools/call` method executes specific tools - **Tool Execution**: `tools/call` method executes specific tools

44
README.zh-CN.md Executable file → Normal file
View File

@@ -4,12 +4,12 @@
一个适用于 Cocos Creator 3.8+ 的综合性 MCP模型上下文协议服务器插件使 AI 助手能够通过标准化协议与 Cocos Creator 编辑器进行交互。一键安装和使用省去所有繁琐环境和配置。已经测试过Claude客户端Claude CLI和Cursor其他的编辑器理论上也完美支持。 一个适用于 Cocos Creator 3.8+ 的综合性 MCP模型上下文协议服务器插件使 AI 助手能够通过标准化协议与 Cocos Creator 编辑器进行交互。一键安装和使用省去所有繁琐环境和配置。已经测试过Claude客户端Claude CLI和Cursor其他的编辑器理论上也完美支持。
**🚀 现在提供 9 个类别的 80 个工具实现95%的编辑器控制!(暂时无法操控预制体)** **🚀 现在提供 13 个类别的 151 个工具实现98%的编辑器控制!(预制体实例化存在子节点恢复问题**
##快速链接 ##快速链接
- **[📖 Complete Feature Guide (English)](FEATURE_GUIDE_EN.md)** - Detailed documentation for all 80 tools - **[📖 Complete Feature Guide (English)](FEATURE_GUIDE_EN.md)** - Detailed documentation for all 151 tools(待补充)
- **[📖 完整功能指南 (中文)](FEATURE_GUIDE_CN.md)** - 所有80个工具的详细文档 - **[📖 完整功能指南 (中文)](FEATURE_GUIDE_CN.md)** - 所有151工具的详细文档(待补充)
## 快速使用 ## 快速使用
@@ -107,6 +107,9 @@ claude mcp add --transport http http://localhost:3000/mcp使用你自己配
- **消息广播**: 监听和广播自定义消息 - **消息广播**: 监听和广播自定义消息
- **资源管理**: 创建、复制、移动、删除和查询资源 - **资源管理**: 创建、复制、移动、删除和查询资源
- **构建系统**: 项目构建和预览服务器控制 - **构建系统**: 项目构建和预览服务器控制
- **参考图片管理**: 在场景视图中添加、删除和管理参考图片
- **场景视图控制**: 控制Gizmo工具、坐标系和视图模式
- **高级场景操作**: 撤销/重做、快照和高级节点操作
## 安装说明 ## 安装说明
@@ -152,7 +155,7 @@ npm run build
1.`扩展 > Cocos MCP Server` 打开 MCP 服务器面板 1.`扩展 > Cocos MCP Server` 打开 MCP 服务器面板
2. 配置设置: 2. 配置设置:
- **端口**: http 服务器端口默认3000 - **端口**: HTTP 服务器端口默认3000
- **自动启动**: 编辑器启动时自动启动服务器 - **自动启动**: 编辑器启动时自动启动服务器
- **调试日志**: 启用详细日志以便开发调试 - **调试日志**: 启用详细日志以便开发调试
- **最大连接数**: 允许的最大并发连接数 - **最大连接数**: 允许的最大并发连接数
@@ -161,7 +164,7 @@ npm run build
### 连接 AI 助手 ### 连接 AI 助手
服务器在 `http://localhost:3000/mcp`(或您配置的端口)上提供 http 端点。 服务器在 `http://localhost:3000/mcp`(或您配置的端口)上提供 HTTP 端点。
AI 助手可以使用 MCP 协议连接并访问所有可用工具。 AI 助手可以使用 MCP 协议连接并访问所有可用工具。
@@ -172,14 +175,17 @@ AI 助手可以使用 MCP 协议连接并访问所有可用工具。
- **scene_\***: 场景相关操作 (8个工具) - **scene_\***: 场景相关操作 (8个工具)
- **node_\***: 节点操作 (9个工具) - **node_\***: 节点操作 (9个工具)
- **component_\***: 组件管理 (7个工具) - **component_\***: 组件管理 (7个工具)
- **prefab_\***: 预制体操作 (8个工具) - **prefab_\***: 预制体操作 (11个工具)
- **project_\***: 项目控制 (22个工具) - **project_\***: 项目控制 (22个工具)
- **debug_\***: 调试工具 (7个工具) - **debug_\***: 调试工具 (10个工具)
- **preferences_\***: 编辑器偏好设置 (6个工具) - **preferences_\***: 编辑器偏好设置 (7个工具)
- **server_\***: 服务器信息 (8个工具) - **server_\***: 服务器信息 (6个工具)
- **broadcast_\***: 消息广播 (5个工具) - **broadcast_\***: 消息广播 (5个工具)
- **assetAdvanced_\***: 高级资源操作 (10个工具)
- **referenceImage_\***: 参考图片管理 (12个工具)
- **sceneAdvanced_\***: 高级场景操作 (23个工具)
- **sceneView_\***: 场景视图控制 (14个工具)
**总计: 80个工具** 实现全面的编辑器控制。
📖 **[查看完整工具文档](FEATURE_GUIDE_CN.md)** 了解详细的使用示例和参数。 📖 **[查看完整工具文档](FEATURE_GUIDE_CN.md)** 了解详细的使用示例和参数。
@@ -262,7 +268,21 @@ cocos-mcp-server/
│ ├── settings.ts # 设置管理 │ ├── settings.ts # 设置管理
│ ├── types/ # TypeScript 类型定义 │ ├── types/ # TypeScript 类型定义
│ ├── tools/ # 工具实现 │ ├── tools/ # 工具实现
└── panels/ # UI 面板实现 │ ├── scene-tools.ts
│ │ ├── node-tools.ts
│ │ ├── component-tools.ts
│ │ ├── prefab-tools.ts
│ │ ├── project-tools.ts
│ │ ├── debug-tools.ts
│ │ ├── preferences-tools.ts
│ │ ├── server-tools.ts
│ │ ├── broadcast-tools.ts
│ │ ├── scene-advanced-tools.ts
│ │ ├── scene-view-tools.ts
│ │ ├── reference-image-tools.ts
│ │ └── asset-advanced-tools.ts
│ ├── panels/ # UI 面板实现
│ └── test/ # 测试文件
├── dist/ # 编译后的 JavaScript 输出 ├── dist/ # 编译后的 JavaScript 输出
├── static/ # 静态资源(图标等) ├── static/ # 静态资源(图标等)
├── i18n/ # 国际化文件 ├── i18n/ # 国际化文件
@@ -329,7 +349,7 @@ npm run build
## 系统要求 ## 系统要求
- Cocos Creator 3.8.0 或更高版本 - Cocos Creator 3.8.6 或更高版本
- Node.jsCocos Creator 自带) - Node.jsCocos Creator 自带)
- TypeScript作为开发依赖安装 - TypeScript作为开发依赖安装

53
TestScript.js Normal file
View File

@@ -0,0 +1,53 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TestScript = void 0;
const cc_1 = require("cc");
const { ccclass, property } = cc_1._decorator;
let TestScript = class TestScript extends cc_1.Component {
constructor() {
super(...arguments);
this.testString = "Hello World";
this.testNumber = 100;
this.testBoolean = true;
this.targetNode = null;
}
start() {
console.log('TestScript started with:', {
testString: this.testString,
testNumber: this.testNumber,
testBoolean: this.testBoolean,
targetNode: this.targetNode
});
}
update(deltaTime) {
}
};
exports.TestScript = TestScript;
__decorate([
property({
displayName: "测试字符串"
})
], TestScript.prototype, "testString", void 0);
__decorate([
property({
displayName: "测试数字"
})
], TestScript.prototype, "testNumber", void 0);
__decorate([
property({
displayName: "测试布尔值"
})
], TestScript.prototype, "testBoolean", void 0);
__decorate([
property(cc_1.Node)
], TestScript.prototype, "targetNode", void 0);
exports.TestScript = TestScript = __decorate([
ccclass('TestScript')
], TestScript);
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVGVzdFNjcmlwdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIlRlc3RTY3JpcHQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7O0FBQUEsMkJBQWlEO0FBQ2pELE1BQU0sRUFBRSxPQUFPLEVBQUUsUUFBUSxFQUFFLEdBQUcsZUFBVSxDQUFDO0FBR2xDLElBQU0sVUFBVSxHQUFoQixNQUFNLFVBQVcsU0FBUSxjQUFTO0lBQWxDOztRQUlJLGVBQVUsR0FBVyxhQUFhLENBQUM7UUFLbkMsZUFBVSxHQUFXLEdBQUcsQ0FBQztRQUt6QixnQkFBVyxHQUFZLElBQUksQ0FBQztRQUc1QixlQUFVLEdBQWdCLElBQUksQ0FBQztJQWMxQyxDQUFDO0lBWkcsS0FBSztRQUNELE9BQU8sQ0FBQyxHQUFHLENBQUMsMEJBQTBCLEVBQUU7WUFDcEMsVUFBVSxFQUFFLElBQUksQ0FBQyxVQUFVO1lBQzNCLFVBQVUsRUFBRSxJQUFJLENBQUMsVUFBVTtZQUMzQixXQUFXLEVBQUUsSUFBSSxDQUFDLFdBQVc7WUFDN0IsVUFBVSxFQUFFLElBQUksQ0FBQyxVQUFVO1NBQzlCLENBQUMsQ0FBQztJQUNQLENBQUM7SUFFRCxNQUFNLENBQUMsU0FBaUI7SUFFeEIsQ0FBQztDQUNKLENBQUE7QUEvQlksZ0NBQVU7QUFJWjtJQUhOLFFBQVEsQ0FBQztRQUNOLFdBQVcsRUFBRSxPQUFPO0tBQ3ZCLENBQUM7OENBQ3dDO0FBS25DO0lBSE4sUUFBUSxDQUFDO1FBQ04sV0FBVyxFQUFFLE1BQU07S0FDdEIsQ0FBQzs4Q0FDOEI7QUFLekI7SUFITixRQUFRLENBQUM7UUFDTixXQUFXLEVBQUUsT0FBTztLQUN2QixDQUFDOytDQUNpQztBQUc1QjtJQUROLFFBQVEsQ0FBQyxTQUFJLENBQUM7OENBQ3VCO3FCQWpCN0IsVUFBVTtJQUR0QixPQUFPLENBQUMsWUFBWSxDQUFDO0dBQ1QsVUFBVSxDQStCdEIiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBfZGVjb3JhdG9yLCBDb21wb25lbnQsIE5vZGUgfSBmcm9tICdjYyc7XG5jb25zdCB7IGNjY2xhc3MsIHByb3BlcnR5IH0gPSBfZGVjb3JhdG9yO1xuXG5AY2NjbGFzcygnVGVzdFNjcmlwdCcpXG5leHBvcnQgY2xhc3MgVGVzdFNjcmlwdCBleHRlbmRzIENvbXBvbmVudCB7XG4gICAgQHByb3BlcnR5KHtcbiAgICAgICAgZGlzcGxheU5hbWU6IFwi5rWL6K+V5a2X56ym5LiyXCJcbiAgICB9KVxuICAgIHB1YmxpYyB0ZXN0U3RyaW5nOiBzdHJpbmcgPSBcIkhlbGxvIFdvcmxkXCI7XG4gICAgXG4gICAgQHByb3BlcnR5KHtcbiAgICAgICAgZGlzcGxheU5hbWU6IFwi5rWL6K+V5pWw5a2XXCJcbiAgICB9KVxuICAgIHB1YmxpYyB0ZXN0TnVtYmVyOiBudW1iZXIgPSAxMDA7XG4gICAgXG4gICAgQHByb3BlcnR5KHtcbiAgICAgICAgZGlzcGxheU5hbWU6IFwi5rWL6K+V5biD5bCU5YC8XCJcbiAgICB9KVxuICAgIHB1YmxpYyB0ZXN0Qm9vbGVhbjogYm9vbGVhbiA9IHRydWU7XG4gICAgXG4gICAgQHByb3BlcnR5KE5vZGUpXG4gICAgcHVibGljIHRhcmdldE5vZGU6IE5vZGUgfCBudWxsID0gbnVsbDtcblxuICAgIHN0YXJ0KCkge1xuICAgICAgICBjb25zb2xlLmxvZygnVGVzdFNjcmlwdCBzdGFydGVkIHdpdGg6Jywge1xuICAgICAgICAgICAgdGVzdFN0cmluZzogdGhpcy50ZXN0U3RyaW5nLFxuICAgICAgICAgICAgdGVzdE51bWJlcjogdGhpcy50ZXN0TnVtYmVyLFxuICAgICAgICAgICAgdGVzdEJvb2xlYW46IHRoaXMudGVzdEJvb2xlYW4sXG4gICAgICAgICAgICB0YXJnZXROb2RlOiB0aGlzLnRhcmdldE5vZGVcbiAgICAgICAgfSk7XG4gICAgfVxuXG4gICAgdXBkYXRlKGRlbHRhVGltZTogbnVtYmVyKSB7XG4gICAgICAgIFxuICAgIH1cbn0iXX0=

File diff suppressed because one or more lines are too long

10
dist/mcp-server.js vendored

File diff suppressed because one or more lines are too long

194
dist/test/prefab-tools-test.js vendored Normal file

File diff suppressed because one or more lines are too long

580
dist/tools/asset-advanced-tools.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

389
dist/tools/reference-image-tools.js vendored Normal file

File diff suppressed because one or more lines are too long

754
dist/tools/scene-advanced-tools.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

597
dist/tools/scene-view-tools.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,308 @@
import { PrefabTools } from '../tools/prefab-tools';
/**
* 预制体实例化使用示例
* 展示如何在实际项目中使用预制体工具
*/
export class PrefabInstantiationExample {
private prefabTools: PrefabTools;
constructor() {
this.prefabTools = new PrefabTools();
}
/**
* 示例1: 基本预制体实例化
*/
async basicInstantiationExample() {
console.log('=== 基本预制体实例化示例 ===');
try {
const result = await this.prefabTools.execute('instantiate_prefab', {
prefabPath: 'db://assets/prefabs/Player.prefab',
position: { x: 0, y: 0, z: 0 }
});
if (result.success) {
console.log('✅ 预制体实例化成功');
console.log(`节点UUID: ${result.data.nodeUuid}`);
console.log(`节点名称: ${result.data.name}`);
console.log('使用的API: create-node with assetUuid');
} else {
console.log('❌ 预制体实例化失败');
console.log(`错误: ${result.error}`);
if (result.instruction) {
console.log(`建议: ${result.instruction}`);
}
}
} catch (error) {
console.error('实例化过程中发生错误:', error);
}
}
/**
* 示例2: 在指定父节点下实例化预制体
*/
async instantiateWithParentExample() {
console.log('=== 在父节点下实例化预制体示例 ===');
try {
const result = await this.prefabTools.execute('instantiate_prefab', {
prefabPath: 'db://assets/prefabs/Enemy.prefab',
parentUuid: 'canvas-uuid-here',
position: { x: 100, y: 200, z: 0 }
});
if (result.success) {
console.log('✅ 在父节点下实例化成功');
console.log(`节点UUID: ${result.data.nodeUuid}`);
} else {
console.log('❌ 实例化失败');
console.log(`错误: ${result.error}`);
}
} catch (error) {
console.error('实例化过程中发生错误:', error);
}
}
/**
* 示例3: 批量实例化预制体
*/
async batchInstantiationExample() {
console.log('=== 批量实例化预制体示例 ===');
const prefabPaths = [
'db://assets/prefabs/Item1.prefab',
'db://assets/prefabs/Item2.prefab',
'db://assets/prefabs/Item3.prefab'
];
const positions = [
{ x: 0, y: 0, z: 0 },
{ x: 100, y: 0, z: 0 },
{ x: 200, y: 0, z: 0 }
];
const results = [];
for (let i = 0; i < prefabPaths.length; i++) {
try {
const result = await this.prefabTools.execute('instantiate_prefab', {
prefabPath: prefabPaths[i],
position: positions[i]
});
results.push({
index: i,
prefabPath: prefabPaths[i],
success: result.success,
data: result.data,
error: result.error
});
if (result.success) {
console.log(`✅ 预制体 ${i + 1} 实例化成功`);
} else {
console.log(`❌ 预制体 ${i + 1} 实例化失败: ${result.error}`);
}
} catch (error) {
console.error(`预制体 ${i + 1} 实例化时发生错误:`, error);
results.push({
index: i,
prefabPath: prefabPaths[i],
success: false,
error: error instanceof Error ? error.message : String(error)
});
}
}
const successCount = results.filter(r => r.success).length;
console.log(`批量实例化完成: ${successCount}/${results.length} 成功`);
return results;
}
/**
* 示例4: 错误处理和重试机制
*/
async instantiationWithRetryExample() {
console.log('=== 带重试机制的实例化示例 ===');
const maxRetries = 3;
let attempt = 0;
while (attempt < maxRetries) {
try {
const result = await this.prefabTools.execute('instantiate_prefab', {
prefabPath: 'db://assets/prefabs/ComplexPrefab.prefab',
position: { x: 0, y: 0, z: 0 }
});
if (result.success) {
console.log(`✅ 预制体实例化成功 (尝试 ${attempt + 1})`);
return result;
} else {
console.log(`❌ 尝试 ${attempt + 1} 失败: ${result.error}`);
attempt++;
if (attempt < maxRetries) {
console.log(`等待 1 秒后重试...`);
await this.delay(1000);
}
}
} catch (error) {
console.error(`尝试 ${attempt + 1} 时发生错误:`, error);
attempt++;
if (attempt < maxRetries) {
console.log(`等待 1 秒后重试...`);
await this.delay(1000);
}
}
}
console.log('❌ 所有重试都失败了');
return { success: false, error: '达到最大重试次数' };
}
/**
* 示例5: 预制体实例化前的验证
*/
async instantiationWithValidationExample() {
console.log('=== 带验证的实例化示例 ===');
const prefabPath = 'db://assets/prefabs/ValidatedPrefab.prefab';
try {
// 首先验证预制体
const validationResult = await this.prefabTools.execute('validate_prefab', {
prefabPath: prefabPath
});
if (validationResult.success && validationResult.data.isValid) {
console.log('✅ 预制体验证通过');
console.log(`节点数量: ${validationResult.data.nodeCount}`);
console.log(`组件数量: ${validationResult.data.componentCount}`);
// 验证通过后实例化
const instantiationResult = await this.prefabTools.execute('instantiate_prefab', {
prefabPath: prefabPath,
position: { x: 0, y: 0, z: 0 }
});
if (instantiationResult.success) {
console.log('✅ 预制体实例化成功');
return instantiationResult;
} else {
console.log('❌ 预制体实例化失败:', instantiationResult.error);
return instantiationResult;
}
} else {
console.log('❌ 预制体验证失败');
if (validationResult.data && validationResult.data.issues) {
console.log('问题列表:');
validationResult.data.issues.forEach((issue: string, index: number) => {
console.log(` ${index + 1}. ${issue}`);
});
}
return validationResult;
}
} catch (error) {
console.error('验证和实例化过程中发生错误:', error);
return { success: false, error: error instanceof Error ? error.message : String(error) };
}
}
/**
* 示例6: API参数构建示例
*/
demonstrateAPIParameters() {
console.log('=== API参数构建示例 ===');
// 模拟从asset-db获取的预制体信息
const assetInfo = {
uuid: 'prefab-uuid-123',
name: 'PlayerCharacter'
};
// 基本实例化参数
const basicOptions = {
assetUuid: assetInfo.uuid,
name: assetInfo.name
};
console.log('基本实例化参数:', JSON.stringify(basicOptions, null, 2));
// 带父节点的实例化参数
const withParentOptions = {
assetUuid: assetInfo.uuid,
name: assetInfo.name,
parent: 'canvas-uuid-456'
};
console.log('带父节点参数:', JSON.stringify(withParentOptions, null, 2));
// 带位置设置的实例化参数
const withPositionOptions = {
assetUuid: assetInfo.uuid,
name: assetInfo.name,
dump: {
position: { x: 100, y: 200, z: 0 }
}
};
console.log('带位置参数:', JSON.stringify(withPositionOptions, null, 2));
// 完整实例化参数
const fullOptions = {
assetUuid: assetInfo.uuid,
name: assetInfo.name,
parent: 'canvas-uuid-456',
dump: {
position: { x: 100, y: 200, z: 0 }
},
keepWorldTransform: false,
unlinkPrefab: false
};
console.log('完整参数:', JSON.stringify(fullOptions, null, 2));
console.log('这些参数将传递给 Editor.Message.request("scene", "create-node", options)');
}
/**
* 延迟函数
*/
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 运行所有示例
*/
async runAllExamples() {
console.log('🚀 开始运行预制体实例化示例...\n');
await this.basicInstantiationExample();
console.log('');
await this.instantiateWithParentExample();
console.log('');
await this.batchInstantiationExample();
console.log('');
await this.instantiationWithRetryExample();
console.log('');
await this.instantiationWithValidationExample();
console.log('');
this.demonstrateAPIParameters();
console.log('');
console.log('🎉 所有示例运行完成!');
}
}
// 如果直接运行此文件
if (typeof module !== 'undefined' && module.exports) {
const example = new PrefabInstantiationExample();
example.runAllExamples();
}

View File

@@ -11,6 +11,10 @@ import { DebugTools } from './tools/debug-tools';
import { PreferencesTools } from './tools/preferences-tools'; import { PreferencesTools } from './tools/preferences-tools';
import { ServerTools } from './tools/server-tools'; import { ServerTools } from './tools/server-tools';
import { BroadcastTools } from './tools/broadcast-tools'; import { BroadcastTools } from './tools/broadcast-tools';
import { SceneAdvancedTools } from './tools/scene-advanced-tools';
import { SceneViewTools } from './tools/scene-view-tools';
import { ReferenceImageTools } from './tools/reference-image-tools';
import { AssetAdvancedTools } from './tools/asset-advanced-tools';
export class MCPServer { export class MCPServer {
private settings: MCPServerSettings; private settings: MCPServerSettings;
@@ -36,6 +40,10 @@ export class MCPServer {
this.tools.preferences = new PreferencesTools(); this.tools.preferences = new PreferencesTools();
this.tools.server = new ServerTools(); this.tools.server = new ServerTools();
this.tools.broadcast = new BroadcastTools(); this.tools.broadcast = new BroadcastTools();
this.tools.sceneAdvanced = new SceneAdvancedTools();
this.tools.sceneView = new SceneViewTools();
this.tools.referenceImage = new ReferenceImageTools();
this.tools.assetAdvanced = new AssetAdvancedTools();
console.log('[MCPServer] Tools initialized successfully'); console.log('[MCPServer] Tools initialized successfully');
} catch (error) { } catch (error) {
console.error('[MCPServer] Error initializing tools:', error); console.error('[MCPServer] Error initializing tools:', error);

View File

@@ -0,0 +1,223 @@
import { PrefabTools } from '../tools/prefab-tools';
// 预制体工具测试
export class PrefabToolsTest {
private prefabTools: PrefabTools;
constructor() {
this.prefabTools = new PrefabTools();
}
async runAllTests() {
console.log('开始预制体工具测试...');
try {
// 测试1: 获取工具列表
await this.testGetTools();
// 测试2: 获取预制体列表
await this.testGetPrefabList();
// 测试3: 测试预制体创建(模拟)
await this.testCreatePrefab();
// 测试3.5: 测试预制体实例化(模拟)
await this.testInstantiatePrefab();
// 测试4: 测试预制体验证
await this.testValidatePrefab();
console.log('所有测试完成!');
} catch (error) {
console.error('测试过程中发生错误:', error);
}
}
private async testGetTools() {
console.log('测试1: 获取工具列表');
const tools = this.prefabTools.getTools();
console.log(`找到 ${tools.length} 个工具:`);
tools.forEach(tool => {
console.log(` - ${tool.name}: ${tool.description}`);
});
console.log('测试1完成\n');
}
private async testGetPrefabList() {
console.log('测试2: 获取预制体列表');
try {
const result = await this.prefabTools.execute('get_prefab_list', { folder: 'db://assets' });
if (result.success) {
console.log(`找到 ${result.data?.length || 0} 个预制体`);
if (result.data && result.data.length > 0) {
result.data.slice(0, 3).forEach((prefab: any) => {
console.log(` - ${prefab.name}: ${prefab.path}`);
});
}
} else {
console.log('获取预制体列表失败:', result.error);
}
} catch (error) {
console.log('获取预制体列表时发生错误:', error);
}
console.log('测试2完成\n');
}
private async testCreatePrefab() {
console.log('测试3: 测试预制体创建(模拟)');
try {
// 模拟创建预制体
const mockArgs = {
nodeUuid: 'mock-node-uuid',
savePath: 'db://assets/test',
prefabName: 'TestPrefab'
};
const result = await this.prefabTools.execute('create_prefab', mockArgs);
console.log('创建预制体结果:', result);
} catch (error) {
console.log('创建预制体时发生错误:', error);
}
console.log('测试3完成\n');
}
private async testInstantiatePrefab() {
console.log('测试3.5: 测试预制体实例化(模拟)');
try {
// 模拟实例化预制体
const mockArgs = {
prefabPath: 'db://assets/prefabs/TestPrefab.prefab',
parentUuid: 'canvas-uuid',
position: { x: 100, y: 200, z: 0 }
};
const result = await this.prefabTools.execute('instantiate_prefab', mockArgs);
console.log('实例化预制体结果:', result);
// 测试API参数构建
this.testCreateNodeAPIParams();
} catch (error) {
console.log('实例化预制体时发生错误:', error);
}
console.log('测试3.5完成\n');
}
private testCreateNodeAPIParams() {
console.log('测试 create-node API 参数构建...');
// 模拟 assetUuid
const assetUuid = 'mock-prefab-uuid';
// 测试基本参数
const basicOptions = {
assetUuid: assetUuid,
name: 'TestPrefabInstance'
};
console.log('基本参数:', basicOptions);
// 测试带父节点的参数
const withParentOptions = {
...basicOptions,
parent: 'parent-node-uuid'
};
console.log('带父节点参数:', withParentOptions);
// 测试带位置的参数
const withPositionOptions = {
...basicOptions,
dump: {
position: { x: 100, y: 200, z: 0 }
}
};
console.log('带位置参数:', withPositionOptions);
// 测试完整参数
const fullOptions = {
assetUuid: assetUuid,
name: 'TestPrefabInstance',
parent: 'parent-node-uuid',
dump: {
position: { x: 100, y: 200, z: 0 }
},
keepWorldTransform: false,
unlinkPrefab: false
};
console.log('完整参数:', fullOptions);
}
private async testValidatePrefab() {
console.log('测试4: 测试预制体验证');
try {
// 测试验证一个不存在的预制体
const result = await this.prefabTools.execute('validate_prefab', {
prefabPath: 'db://assets/nonexistent.prefab'
});
console.log('验证预制体结果:', result);
} catch (error) {
console.log('验证预制体时发生错误:', error);
}
console.log('测试4完成\n');
}
// 测试预制体数据结构生成
testPrefabDataGeneration() {
console.log('测试预制体数据结构生成...');
const mockNodeData = {
name: 'TestNode',
position: { x: 0, y: 0, z: 0 },
scale: { x: 1, y: 1, z: 1 },
active: true,
children: [],
components: [
{
type: 'cc.UITransform',
enabled: true,
properties: {
_contentSize: { width: 100, height: 100 },
_anchorPoint: { x: 0.5, y: 0.5 }
}
}
]
};
const prefabUuid = this.prefabTools['generateUUID']();
const prefabData = this.prefabTools['createPrefabData'](mockNodeData, 'TestPrefab', prefabUuid);
console.log('生成的预制体数据结构:');
console.log(JSON.stringify(prefabData, null, 2));
// 验证数据结构
const validationResult = this.prefabTools['validatePrefabFormat'](prefabData);
console.log('验证结果:', validationResult);
console.log('预制体数据结构生成测试完成\n');
}
// 测试UUID生成
testUUIDGeneration() {
console.log('测试UUID生成...');
const uuids = [];
for (let i = 0; i < 5; i++) {
const uuid = this.prefabTools['generateUUID']();
uuids.push(uuid);
console.log(`UUID ${i + 1}: ${uuid}`);
}
// 检查UUID格式
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const validUuids = uuids.filter(uuid => uuidPattern.test(uuid));
console.log(`UUID格式验证: ${validUuids.length}/${uuids.length} 个有效`);
console.log('UUID生成测试完成\n');
}
}
// 如果直接运行此文件
if (typeof module !== 'undefined' && module.exports) {
const test = new PrefabToolsTest();
test.runAllTests();
test.testPrefabDataGeneration();
test.testUUIDGeneration();
}

View File

@@ -0,0 +1,614 @@
import { ToolDefinition, ToolResponse, ToolExecutor } from '../types';
export class AssetAdvancedTools implements ToolExecutor {
getTools(): ToolDefinition[] {
return [
{
name: 'save_asset_meta',
description: 'Save asset meta information',
inputSchema: {
type: 'object',
properties: {
urlOrUUID: {
type: 'string',
description: 'Asset URL or UUID'
},
content: {
type: 'string',
description: 'Asset meta serialized content string'
}
},
required: ['urlOrUUID', 'content']
}
},
{
name: 'generate_available_url',
description: 'Generate an available URL based on input URL',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'Asset URL to generate available URL for'
}
},
required: ['url']
}
},
{
name: 'query_asset_db_ready',
description: 'Check if asset database is ready',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'open_asset_external',
description: 'Open asset with external program',
inputSchema: {
type: 'object',
properties: {
urlOrUUID: {
type: 'string',
description: 'Asset URL or UUID to open'
}
},
required: ['urlOrUUID']
}
},
{
name: 'batch_import_assets',
description: 'Import multiple assets in batch',
inputSchema: {
type: 'object',
properties: {
sourceDirectory: {
type: 'string',
description: 'Source directory path'
},
targetDirectory: {
type: 'string',
description: 'Target directory URL'
},
fileFilter: {
type: 'array',
items: { type: 'string' },
description: 'File extensions to include (e.g., [".png", ".jpg"])',
default: []
},
recursive: {
type: 'boolean',
description: 'Include subdirectories',
default: false
},
overwrite: {
type: 'boolean',
description: 'Overwrite existing files',
default: false
}
},
required: ['sourceDirectory', 'targetDirectory']
}
},
{
name: 'batch_delete_assets',
description: 'Delete multiple assets in batch',
inputSchema: {
type: 'object',
properties: {
urls: {
type: 'array',
items: { type: 'string' },
description: 'Array of asset URLs to delete'
}
},
required: ['urls']
}
},
{
name: 'validate_asset_references',
description: 'Validate asset references and find broken links',
inputSchema: {
type: 'object',
properties: {
directory: {
type: 'string',
description: 'Directory to validate (default: entire project)',
default: 'db://assets'
}
}
}
},
{
name: 'get_asset_dependencies',
description: 'Get asset dependency tree',
inputSchema: {
type: 'object',
properties: {
urlOrUUID: {
type: 'string',
description: 'Asset URL or UUID'
},
direction: {
type: 'string',
description: 'Dependency direction',
enum: ['dependents', 'dependencies', 'both'],
default: 'dependencies'
}
},
required: ['urlOrUUID']
}
},
{
name: 'get_unused_assets',
description: 'Find unused assets in project',
inputSchema: {
type: 'object',
properties: {
directory: {
type: 'string',
description: 'Directory to scan (default: entire project)',
default: 'db://assets'
},
excludeDirectories: {
type: 'array',
items: { type: 'string' },
description: 'Directories to exclude from scan',
default: []
}
}
}
},
{
name: 'compress_textures',
description: 'Batch compress texture assets',
inputSchema: {
type: 'object',
properties: {
directory: {
type: 'string',
description: 'Directory containing textures',
default: 'db://assets'
},
format: {
type: 'string',
description: 'Compression format',
enum: ['auto', 'jpg', 'png', 'webp'],
default: 'auto'
},
quality: {
type: 'number',
description: 'Compression quality (0.1-1.0)',
minimum: 0.1,
maximum: 1.0,
default: 0.8
}
}
}
},
{
name: 'export_asset_manifest',
description: 'Export asset manifest/inventory',
inputSchema: {
type: 'object',
properties: {
directory: {
type: 'string',
description: 'Directory to export manifest for',
default: 'db://assets'
},
format: {
type: 'string',
description: 'Export format',
enum: ['json', 'csv', 'xml'],
default: 'json'
},
includeMetadata: {
type: 'boolean',
description: 'Include asset metadata',
default: true
}
}
}
}
];
}
async execute(toolName: string, args: any): Promise<ToolResponse> {
switch (toolName) {
case 'save_asset_meta':
return await this.saveAssetMeta(args.urlOrUUID, args.content);
case 'generate_available_url':
return await this.generateAvailableUrl(args.url);
case 'query_asset_db_ready':
return await this.queryAssetDbReady();
case 'open_asset_external':
return await this.openAssetExternal(args.urlOrUUID);
case 'batch_import_assets':
return await this.batchImportAssets(args);
case 'batch_delete_assets':
return await this.batchDeleteAssets(args.urls);
case 'validate_asset_references':
return await this.validateAssetReferences(args.directory);
case 'get_asset_dependencies':
return await this.getAssetDependencies(args.urlOrUUID, args.direction);
case 'get_unused_assets':
return await this.getUnusedAssets(args.directory, args.excludeDirectories);
case 'compress_textures':
return await this.compressTextures(args.directory, args.format, args.quality);
case 'export_asset_manifest':
return await this.exportAssetManifest(args.directory, args.format, args.includeMetadata);
default:
throw new Error(`Unknown tool: ${toolName}`);
}
}
private async saveAssetMeta(urlOrUUID: string, content: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('asset-db', 'save-asset-meta', urlOrUUID, content).then((result: any) => {
resolve({
success: true,
data: {
uuid: result?.uuid,
url: result?.url,
message: 'Asset meta saved successfully'
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async generateAvailableUrl(url: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('asset-db', 'generate-available-url', url).then((availableUrl: string) => {
resolve({
success: true,
data: {
originalUrl: url,
availableUrl: availableUrl,
message: availableUrl === url ?
'URL is available' :
'Generated new available URL'
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryAssetDbReady(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('asset-db', 'query-ready').then((ready: boolean) => {
resolve({
success: true,
data: {
ready: ready,
message: ready ? 'Asset database is ready' : 'Asset database is not ready'
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async openAssetExternal(urlOrUUID: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('asset-db', 'open-asset', urlOrUUID).then(() => {
resolve({
success: true,
message: 'Asset opened with external program'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async batchImportAssets(args: any): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
const fs = require('fs');
const path = require('path');
if (!fs.existsSync(args.sourceDirectory)) {
resolve({ success: false, error: 'Source directory does not exist' });
return;
}
const files = this.getFilesFromDirectory(
args.sourceDirectory,
args.fileFilter || [],
args.recursive || false
);
const importResults: any[] = [];
let successCount = 0;
let errorCount = 0;
for (const filePath of files) {
try {
const fileName = path.basename(filePath);
const targetPath = `${args.targetDirectory}/${fileName}`;
const result = await Editor.Message.request('asset-db', 'import-asset',
filePath, targetPath, {
overwrite: args.overwrite || false,
rename: !(args.overwrite || false)
});
importResults.push({
source: filePath,
target: targetPath,
success: true,
uuid: result?.uuid
});
successCount++;
} catch (err: any) {
importResults.push({
source: filePath,
success: false,
error: err.message
});
errorCount++;
}
}
resolve({
success: true,
data: {
totalFiles: files.length,
successCount: successCount,
errorCount: errorCount,
results: importResults,
message: `Batch import completed: ${successCount} success, ${errorCount} errors`
}
});
} catch (err: any) {
resolve({ success: false, error: err.message });
}
});
}
private getFilesFromDirectory(dirPath: string, fileFilter: string[], recursive: boolean): string[] {
const fs = require('fs');
const path = require('path');
const files: string[] = [];
const items = fs.readdirSync(dirPath);
for (const item of items) {
const fullPath = path.join(dirPath, item);
const stat = fs.statSync(fullPath);
if (stat.isFile()) {
if (fileFilter.length === 0 || fileFilter.some(ext => item.toLowerCase().endsWith(ext.toLowerCase()))) {
files.push(fullPath);
}
} else if (stat.isDirectory() && recursive) {
files.push(...this.getFilesFromDirectory(fullPath, fileFilter, recursive));
}
}
return files;
}
private async batchDeleteAssets(urls: string[]): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
const deleteResults: any[] = [];
let successCount = 0;
let errorCount = 0;
for (const url of urls) {
try {
await Editor.Message.request('asset-db', 'delete-asset', url);
deleteResults.push({
url: url,
success: true
});
successCount++;
} catch (err: any) {
deleteResults.push({
url: url,
success: false,
error: err.message
});
errorCount++;
}
}
resolve({
success: true,
data: {
totalAssets: urls.length,
successCount: successCount,
errorCount: errorCount,
results: deleteResults,
message: `Batch delete completed: ${successCount} success, ${errorCount} errors`
}
});
} catch (err: any) {
resolve({ success: false, error: err.message });
}
});
}
private async validateAssetReferences(directory: string = 'db://assets'): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
// Get all assets in directory
const assets = await Editor.Message.request('asset-db', 'query-assets', { pattern: `${directory}/**/*` });
const brokenReferences: any[] = [];
const validReferences: any[] = [];
for (const asset of assets) {
try {
const assetInfo = await Editor.Message.request('asset-db', 'query-asset-info', asset.url);
if (assetInfo) {
validReferences.push({
url: asset.url,
uuid: asset.uuid,
name: asset.name
});
}
} catch (err) {
brokenReferences.push({
url: asset.url,
uuid: asset.uuid,
name: asset.name,
error: (err as Error).message
});
}
}
resolve({
success: true,
data: {
directory: directory,
totalAssets: assets.length,
validReferences: validReferences.length,
brokenReferences: brokenReferences.length,
brokenAssets: brokenReferences,
message: `Validation completed: ${brokenReferences.length} broken references found`
}
});
} catch (err: any) {
resolve({ success: false, error: err.message });
}
});
}
private async getAssetDependencies(urlOrUUID: string, direction: string = 'dependencies'): Promise<ToolResponse> {
return new Promise((resolve) => {
// Note: This would require scene analysis or additional APIs not available in current documentation
resolve({
success: false,
error: 'Asset dependency analysis requires additional APIs not available in current Cocos Creator MCP implementation. Consider using the Editor UI for dependency analysis.'
});
});
}
private async getUnusedAssets(directory: string = 'db://assets', excludeDirectories: string[] = []): Promise<ToolResponse> {
return new Promise((resolve) => {
// Note: This would require comprehensive project analysis
resolve({
success: false,
error: 'Unused asset detection requires comprehensive project analysis not available in current Cocos Creator MCP implementation. Consider using the Editor UI or third-party tools for unused asset detection.'
});
});
}
private async compressTextures(directory: string = 'db://assets', format: string = 'auto', quality: number = 0.8): Promise<ToolResponse> {
return new Promise((resolve) => {
// Note: Texture compression would require image processing APIs
resolve({
success: false,
error: 'Texture compression requires image processing capabilities not available in current Cocos Creator MCP implementation. Use the Editor\'s built-in texture compression settings or external tools.'
});
});
}
private async exportAssetManifest(directory: string = 'db://assets', format: string = 'json', includeMetadata: boolean = true): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
const assets = await Editor.Message.request('asset-db', 'query-assets', { pattern: `${directory}/**/*` });
const manifest: any[] = [];
for (const asset of assets) {
const manifestEntry: any = {
name: asset.name,
url: asset.url,
uuid: asset.uuid,
type: asset.type,
size: (asset as any).size || 0,
isDirectory: asset.isDirectory || false
};
if (includeMetadata) {
try {
const assetInfo = await Editor.Message.request('asset-db', 'query-asset-info', asset.url);
if (assetInfo && assetInfo.meta) {
manifestEntry.meta = assetInfo.meta;
}
} catch (err) {
// Skip metadata if not available
}
}
manifest.push(manifestEntry);
}
let exportData: string;
switch (format) {
case 'json':
exportData = JSON.stringify(manifest, null, 2);
break;
case 'csv':
exportData = this.convertToCSV(manifest);
break;
case 'xml':
exportData = this.convertToXML(manifest);
break;
default:
exportData = JSON.stringify(manifest, null, 2);
}
resolve({
success: true,
data: {
directory: directory,
format: format,
assetCount: manifest.length,
includeMetadata: includeMetadata,
manifest: exportData,
message: `Asset manifest exported with ${manifest.length} assets`
}
});
} catch (err: any) {
resolve({ success: false, error: err.message });
}
});
}
private convertToCSV(data: any[]): string {
if (data.length === 0) return '';
const headers = Object.keys(data[0]);
const csvRows = [headers.join(',')];
for (const row of data) {
const values = headers.map(header => {
const value = row[header];
return typeof value === 'object' ? JSON.stringify(value) : String(value);
});
csvRows.push(values.join(','));
}
return csvRows.join('\n');
}
private convertToXML(data: any[]): string {
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<assets>\n';
for (const item of data) {
xml += ' <asset>\n';
for (const [key, value] of Object.entries(item)) {
const xmlValue = typeof value === 'object' ?
JSON.stringify(value) :
String(value).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
xml += ` <${key}>${xmlValue}</${key}>\n`;
}
xml += ' </asset>\n';
}
xml += '</assets>';
return xml;
}
}

View File

@@ -5,13 +5,13 @@ export class ComponentTools implements ToolExecutor {
return [ return [
{ {
name: 'add_component', name: 'add_component',
description: 'Add a component to a specific node. The component will be added to the exact node specified by nodeUuid.', description: 'Add a component to a specific node. IMPORTANT: You must provide the nodeUuid parameter to specify which node to add the component to.',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: { properties: {
nodeUuid: { nodeUuid: {
type: 'string', type: 'string',
description: 'Target node UUID. Use get_node_info or find_node_by_name to get the UUID of the desired node.' description: 'Target node UUID. REQUIRED: You must specify the exact node to add the component to. Use get_all_nodes or find_node_by_name to get the UUID of the desired node.'
}, },
componentType: { componentType: {
type: 'string', type: 'string',

View File

@@ -1,4 +1,6 @@
import { ToolDefinition, ToolResponse, ToolExecutor, ConsoleMessage, PerformanceStats, ValidationResult, ValidationIssue } from '../types'; import { ToolDefinition, ToolResponse, ToolExecutor, ConsoleMessage, PerformanceStats, ValidationResult, ValidationIssue } from '../types';
import * as fs from 'fs';
import * as path from 'path';
export class DebugTools implements ToolExecutor { export class DebugTools implements ToolExecutor {
private consoleMessages: ConsoleMessage[] = []; private consoleMessages: ConsoleMessage[] = [];
@@ -123,6 +125,68 @@ export class DebugTools implements ToolExecutor {
type: 'object', type: 'object',
properties: {} properties: {}
} }
},
{
name: 'get_project_logs',
description: 'Get project logs from temp/logs/project.log file',
inputSchema: {
type: 'object',
properties: {
lines: {
type: 'number',
description: 'Number of lines to read from the end of the log file (default: 100)',
default: 100,
minimum: 1,
maximum: 10000
},
filterKeyword: {
type: 'string',
description: 'Filter logs containing specific keyword (optional)'
},
logLevel: {
type: 'string',
description: 'Filter by log level',
enum: ['ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE', 'ALL'],
default: 'ALL'
}
}
}
},
{
name: 'get_log_file_info',
description: 'Get information about the project log file',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'search_project_logs',
description: 'Search for specific patterns or errors in project logs',
inputSchema: {
type: 'object',
properties: {
pattern: {
type: 'string',
description: 'Search pattern (supports regex)'
},
maxResults: {
type: 'number',
description: 'Maximum number of matching results',
default: 20,
minimum: 1,
maximum: 100
},
contextLines: {
type: 'number',
description: 'Number of context lines to show around each match',
default: 2,
minimum: 0,
maximum: 10
}
},
required: ['pattern']
}
} }
]; ];
} }
@@ -143,6 +207,12 @@ export class DebugTools implements ToolExecutor {
return await this.validateScene(args); return await this.validateScene(args);
case 'get_editor_info': case 'get_editor_info':
return await this.getEditorInfo(); return await this.getEditorInfo();
case 'get_project_logs':
return await this.getProjectLogs(args.lines, args.filterKeyword, args.logLevel);
case 'get_log_file_info':
return await this.getLogFileInfo();
case 'search_project_logs':
return await this.searchProjectLogs(args.pattern, args.maxResults, args.contextLines);
default: default:
throw new Error(`Unknown tool: ${toolName}`); throw new Error(`Unknown tool: ${toolName}`);
} }
@@ -184,8 +254,10 @@ export class DebugTools implements ToolExecutor {
private async executeScript(script: string): Promise<ToolResponse> { private async executeScript(script: string): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
Editor.Message.request('scene', 'execute-script', { Editor.Message.request('scene', 'execute-scene-script', {
script: script name: 'console',
method: 'eval',
args: [script]
}).then((result: any) => { }).then((result: any) => {
resolve({ resolve({
success: true, success: true,
@@ -348,4 +420,223 @@ export class DebugTools implements ToolExecutor {
return { success: true, data: info }; return { success: true, data: info };
} }
private async getProjectLogs(lines: number = 100, filterKeyword?: string, logLevel: string = 'ALL'): Promise<ToolResponse> {
try {
// Try multiple possible project paths
let logFilePath = '';
const possiblePaths = [
Editor.Project ? Editor.Project.path : null,
'/Users/lizhiyong/NewProject_3',
process.cwd(),
].filter(p => p !== null);
for (const basePath of possiblePaths) {
const testPath = path.join(basePath, 'temp/logs/project.log');
if (fs.existsSync(testPath)) {
logFilePath = testPath;
break;
}
}
if (!logFilePath) {
return {
success: false,
error: `Project log file not found. Tried paths: ${possiblePaths.map(p => path.join(p, 'temp/logs/project.log')).join(', ')}`
};
}
// Read the file content
const logContent = fs.readFileSync(logFilePath, 'utf8');
const logLines = logContent.split('\n').filter(line => line.trim() !== '');
// Get the last N lines
const recentLines = logLines.slice(-lines);
// Apply filters
let filteredLines = recentLines;
// Filter by log level if not 'ALL'
if (logLevel !== 'ALL') {
filteredLines = filteredLines.filter(line =>
line.includes(`[${logLevel}]`) || line.includes(logLevel.toLowerCase())
);
}
// Filter by keyword if provided
if (filterKeyword) {
filteredLines = filteredLines.filter(line =>
line.toLowerCase().includes(filterKeyword.toLowerCase())
);
}
return {
success: true,
data: {
totalLines: logLines.length,
requestedLines: lines,
filteredLines: filteredLines.length,
logLevel: logLevel,
filterKeyword: filterKeyword || null,
logs: filteredLines,
logFilePath: logFilePath
}
};
} catch (error: any) {
return {
success: false,
error: `Failed to read project logs: ${error.message}`
};
}
}
private async getLogFileInfo(): Promise<ToolResponse> {
try {
// Try multiple possible project paths
let logFilePath = '';
const possiblePaths = [
Editor.Project ? Editor.Project.path : null,
'/Users/lizhiyong/NewProject_3',
process.cwd(),
].filter(p => p !== null);
for (const basePath of possiblePaths) {
const testPath = path.join(basePath, 'temp/logs/project.log');
if (fs.existsSync(testPath)) {
logFilePath = testPath;
break;
}
}
if (!logFilePath) {
return {
success: false,
error: `Project log file not found. Tried paths: ${possiblePaths.map(p => path.join(p, 'temp/logs/project.log')).join(', ')}`
};
}
const stats = fs.statSync(logFilePath);
const logContent = fs.readFileSync(logFilePath, 'utf8');
const lineCount = logContent.split('\n').filter(line => line.trim() !== '').length;
return {
success: true,
data: {
filePath: logFilePath,
fileSize: stats.size,
fileSizeFormatted: this.formatFileSize(stats.size),
lastModified: stats.mtime.toISOString(),
lineCount: lineCount,
created: stats.birthtime.toISOString(),
accessible: fs.constants.R_OK
}
};
} catch (error: any) {
return {
success: false,
error: `Failed to get log file info: ${error.message}`
};
}
}
private async searchProjectLogs(pattern: string, maxResults: number = 20, contextLines: number = 2): Promise<ToolResponse> {
try {
// Try multiple possible project paths
let logFilePath = '';
const possiblePaths = [
Editor.Project ? Editor.Project.path : null,
'/Users/lizhiyong/NewProject_3',
process.cwd(),
].filter(p => p !== null);
for (const basePath of possiblePaths) {
const testPath = path.join(basePath, 'temp/logs/project.log');
if (fs.existsSync(testPath)) {
logFilePath = testPath;
break;
}
}
if (!logFilePath) {
return {
success: false,
error: `Project log file not found. Tried paths: ${possiblePaths.map(p => path.join(p, 'temp/logs/project.log')).join(', ')}`
};
}
const logContent = fs.readFileSync(logFilePath, 'utf8');
const logLines = logContent.split('\n');
// Create regex pattern (support both string and regex patterns)
let regex: RegExp;
try {
regex = new RegExp(pattern, 'gi');
} catch {
// If pattern is not valid regex, treat as literal string
regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
}
const matches: any[] = [];
let resultCount = 0;
for (let i = 0; i < logLines.length && resultCount < maxResults; i++) {
const line = logLines[i];
if (regex.test(line)) {
// Get context lines
const contextStart = Math.max(0, i - contextLines);
const contextEnd = Math.min(logLines.length - 1, i + contextLines);
const contextLinesArray = [];
for (let j = contextStart; j <= contextEnd; j++) {
contextLinesArray.push({
lineNumber: j + 1,
content: logLines[j],
isMatch: j === i
});
}
matches.push({
lineNumber: i + 1,
matchedLine: line,
context: contextLinesArray
});
resultCount++;
// Reset regex lastIndex for global search
regex.lastIndex = 0;
}
}
return {
success: true,
data: {
pattern: pattern,
totalMatches: matches.length,
maxResults: maxResults,
contextLines: contextLines,
logFilePath: logFilePath,
matches: matches
}
};
} catch (error: any) {
return {
success: false,
error: `Failed to search project logs: ${error.message}`
};
}
}
private formatFileSize(bytes: number): string {
const units = ['B', 'KB', 'MB', 'GB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
} }

View File

@@ -5,7 +5,7 @@ export class NodeTools implements ToolExecutor {
return [ return [
{ {
name: 'create_node', name: 'create_node',
description: 'Create a new node in the scene. If parentUuid is not provided, the node will be created at the current selection in the editor.', description: 'Create a new node in the scene. IMPORTANT: You should always provide parentUuid to specify where to create the node. If parentUuid is not provided, the node will be created at the scene root.',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: { properties: {
@@ -15,7 +15,7 @@ export class NodeTools implements ToolExecutor {
}, },
parentUuid: { parentUuid: {
type: 'string', type: 'string',
description: 'Parent node UUID. If not provided, node will be created at current editor selection. To create at scene root, first get the root node UUID.' description: 'Parent node UUID. STRONGLY RECOMMENDED: Always provide this parameter. Use get_current_scene or get_all_nodes to find parent UUIDs. If not provided, node will be created at scene root.'
}, },
nodeType: { nodeType: {
type: 'string', type: 'string',
@@ -194,14 +194,39 @@ export class NodeTools implements ToolExecutor {
private async createNode(args: any): Promise<ToolResponse> { private async createNode(args: any): Promise<ToolResponse> {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
// 如果指定了父节点,先验证父节点是否存在 let targetParentUuid = args.parentUuid;
if (args.parentUuid) {
// 如果没有提供父节点UUID获取场景根节点
if (!targetParentUuid) {
try { try {
const parentNode = await Editor.Message.request('scene', 'query-node', args.parentUuid); const sceneInfo = await Editor.Message.request('scene', 'query-node-tree');
if (sceneInfo && typeof sceneInfo === 'object' && 'uuid' in sceneInfo) {
targetParentUuid = sceneInfo.uuid;
console.log(`No parent specified, using scene root: ${targetParentUuid}`);
} else if (Array.isArray(sceneInfo) && sceneInfo.length > 0 && sceneInfo[0].uuid) {
// 如果返回的是数组,使用第一个元素(通常是场景根节点)
targetParentUuid = sceneInfo[0].uuid;
console.log(`No parent specified, using scene root: ${targetParentUuid}`);
} else {
// 备用方案:尝试获取当前场景
const currentScene = await Editor.Message.request('scene', 'query-current-scene');
if (currentScene && currentScene.uuid) {
targetParentUuid = currentScene.uuid;
}
}
} catch (err) {
console.warn('Failed to get scene root, will use default behavior');
}
}
// 如果指定了父节点,先验证父节点是否存在
if (targetParentUuid) {
try {
const parentNode = await Editor.Message.request('scene', 'query-node', targetParentUuid);
if (!parentNode) { if (!parentNode) {
resolve({ resolve({
success: false, success: false,
error: `Parent node with UUID '${args.parentUuid}' not found` error: `Parent node with UUID '${targetParentUuid}' not found`
}); });
return; return;
} }
@@ -219,25 +244,31 @@ export class NodeTools implements ToolExecutor {
type: args.nodeType || 'cc.Node' type: args.nodeType || 'cc.Node'
}; };
// 使用更明确的父节点指定方式 // 使用正确的create-node API参数结构
if (args.parentUuid) { if (targetParentUuid) {
nodeData.parent = args.parentUuid; const createNodeOptions = {
// 尝试先创建节点,然后移动到指定父节点 parent: targetParentUuid,
Editor.Message.request('scene', 'create-node', nodeData).then((nodeUuid: any) => { name: args.name,
// 如果创建成功但可能没有在正确的父节点下,尝试移动 components: args.nodeType && args.nodeType !== 'Node' ? [args.nodeType] : undefined
if (args.parentUuid && nodeUuid) { };
Editor.Message.request('scene', 'move-node', {
uuid: nodeUuid, Editor.Message.request('scene', 'create-node', createNodeOptions).then((nodeUuid: any) => {
parent: args.parentUuid, // 如果需要设置特定的兄弟索引使用set-parent API
index: args.siblingIndex !== undefined ? args.siblingIndex : -1 if (args.siblingIndex !== undefined && args.siblingIndex >= 0 && nodeUuid) {
Editor.Message.request('scene', 'set-parent', {
parent: targetParentUuid,
uuids: [nodeUuid],
keepWorldTransform: false
}).then(() => { }).then(() => {
resolve({ resolve({
success: true, success: true,
data: { data: {
uuid: nodeUuid, uuid: nodeUuid,
name: args.name, name: args.name,
parentUuid: args.parentUuid, parentUuid: targetParentUuid,
message: `Node '${args.name}' created under specified parent` message: args.parentUuid
? `Node '${args.name}' created under specified parent`
: `Node '${args.name}' created at scene root (no parent specified)`
} }
}); });
}).catch(() => { }).catch(() => {
@@ -247,7 +278,7 @@ export class NodeTools implements ToolExecutor {
data: { data: {
uuid: nodeUuid, uuid: nodeUuid,
name: args.name, name: args.name,
message: `Node '${args.name}' created but may not be under specified parent`, message: `Node '${args.name}' created but may not be under intended parent`,
warning: 'Failed to move node to specified parent' warning: 'Failed to move node to specified parent'
} }
}); });
@@ -266,14 +297,20 @@ export class NodeTools implements ToolExecutor {
resolve({ success: false, error: err.message }); resolve({ success: false, error: err.message });
}); });
} else { } else {
// 没有指定父节点,使用默认行为 // 没有找到场景根节点,使用默认行为(创建在场景根节点)
Editor.Message.request('scene', 'create-node', nodeData).then((result: any) => { const createNodeOptions = {
name: args.name,
components: args.nodeType && args.nodeType !== 'Node' ? [args.nodeType] : undefined
};
Editor.Message.request('scene', 'create-node', createNodeOptions).then((result: any) => {
resolve({ resolve({
success: true, success: true,
data: { data: {
uuid: result, uuid: result,
name: args.name, name: args.name,
message: `Node '${args.name}' created at current selection` message: `Node '${args.name}' created at default location (scene root not found)`,
warning: 'Could not determine scene root, node created at default location'
} }
}); });
}).catch((err: Error) => { }).catch((err: Error) => {
@@ -320,18 +357,51 @@ export class NodeTools implements ToolExecutor {
private async findNodes(pattern: string, exactMatch: boolean = false): Promise<ToolResponse> { private async findNodes(pattern: string, exactMatch: boolean = false): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
Editor.Message.request('scene', 'query-nodes-by-name', { // Note: 'query-nodes-by-name' API doesn't exist in official documentation
name: pattern, // Using tree traversal as primary approach
exactMatch: exactMatch Editor.Message.request('scene', 'query-node-tree').then((tree: any) => {
}).then((results: any[]) => { const nodes: any[] = [];
const nodes = results.map(node => ({
const searchTree = (node: any, currentPath: string = '') => {
const nodePath = currentPath ? `${currentPath}/${node.name}` : node.name;
const matches = exactMatch ?
node.name === pattern :
node.name.toLowerCase().includes(pattern.toLowerCase());
if (matches) {
nodes.push({
uuid: node.uuid, uuid: node.uuid,
name: node.name, name: node.name,
path: node.path path: nodePath
})); });
}
if (node.children) {
for (const child of node.children) {
searchTree(child, nodePath);
}
}
};
if (tree) {
searchTree(tree);
}
resolve({ success: true, data: nodes }); resolve({ success: true, data: nodes });
}).catch((err: Error) => { }).catch((err: Error) => {
resolve({ success: false, error: err.message }); // 备用方案:使用场景脚本
const options = {
name: 'cocos-mcp-server',
method: 'findNodes',
args: [pattern, exactMatch]
};
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
resolve(result);
}).catch((err2: Error) => {
resolve({ success: false, error: `Tree search failed: ${err.message}, Scene script failed: ${err2.message}` });
});
}); });
}); });
} }
@@ -493,10 +563,11 @@ export class NodeTools implements ToolExecutor {
private async moveNode(nodeUuid: string, newParentUuid: string, siblingIndex: number = -1): Promise<ToolResponse> { private async moveNode(nodeUuid: string, newParentUuid: string, siblingIndex: number = -1): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
Editor.Message.request('scene', 'move-node', { // Use correct set-parent API instead of move-node
uuid: nodeUuid, Editor.Message.request('scene', 'set-parent', {
parent: newParentUuid, parent: newParentUuid,
index: siblingIndex uuids: [nodeUuid],
keepWorldTransform: false
}).then(() => { }).then(() => {
resolve({ resolve({
success: true, success: true,
@@ -510,6 +581,7 @@ export class NodeTools implements ToolExecutor {
private async duplicateNode(uuid: string, includeChildren: boolean = true): Promise<ToolResponse> { private async duplicateNode(uuid: string, includeChildren: boolean = true): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
// Note: includeChildren parameter is accepted for future use but not currently implemented
Editor.Message.request('scene', 'duplicate-node', uuid).then((result: any) => { Editor.Message.request('scene', 'duplicate-node', uuid).then((result: any) => {
resolve({ resolve({
success: true, success: true,

File diff suppressed because it is too large Load Diff

View File

@@ -4,79 +4,127 @@ export class PreferencesTools implements ToolExecutor {
getTools(): ToolDefinition[] { getTools(): ToolDefinition[] {
return [ return [
{ {
name: 'get_preferences', name: 'open_preferences_settings',
description: 'Get editor preferences', description: 'Open preferences settings panel',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: { properties: {
key: { tab: {
type: 'string', type: 'string',
description: 'Specific preference key to get (optional)' description: 'Preferences tab to open (optional)',
enum: ['general', 'external-tools', 'data-editor', 'laboratory', 'extensions']
},
args: {
type: 'array',
description: 'Additional arguments to pass to the tab'
} }
} }
} }
}, },
{ {
name: 'set_preferences', name: 'query_preferences_config',
description: 'Set editor preferences', description: 'Query preferences configuration',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: { properties: {
key: { name: {
type: 'string', type: 'string',
description: 'Preference key to set' description: 'Plugin or category name',
default: 'general'
},
path: {
type: 'string',
description: 'Configuration path (optional)'
},
type: {
type: 'string',
description: 'Configuration type',
enum: ['default', 'global', 'local'],
default: 'global'
}
},
required: ['name']
}
},
{
name: 'set_preferences_config',
description: 'Set preferences configuration',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Plugin name'
},
path: {
type: 'string',
description: 'Configuration path'
}, },
value: { value: {
description: 'Preference value to set' description: 'Configuration value'
}
}, },
required: ['key', 'value'] type: {
}
},
{
name: 'get_global_preferences',
description: 'Get global editor preferences',
inputSchema: {
type: 'object',
properties: {
key: {
type: 'string', type: 'string',
description: 'Global preference key to get (optional)' description: 'Configuration type',
} enum: ['default', 'global', 'local'],
default: 'global'
} }
},
required: ['name', 'path', 'value']
} }
}, },
{ {
name: 'set_global_preferences', name: 'get_all_preferences',
description: 'Set global editor preferences', description: 'Get all available preferences categories',
inputSchema: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'Global preference key to set'
},
value: {
description: 'Global preference value to set'
}
},
required: ['key', 'value']
}
},
{
name: 'get_recent_projects',
description: 'Get recently opened projects',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: {} properties: {}
} }
}, },
{ {
name: 'clear_recent_projects', name: 'reset_preferences',
description: 'Clear recently opened projects list', description: 'Reset preferences to default values',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: {} properties: {
name: {
type: 'string',
description: 'Specific preference category to reset (optional)'
},
type: {
type: 'string',
description: 'Configuration type to reset',
enum: ['global', 'local'],
default: 'global'
}
}
}
},
{
name: 'export_preferences',
description: 'Export current preferences configuration',
inputSchema: {
type: 'object',
properties: {
exportPath: {
type: 'string',
description: 'Path to export preferences file (optional)'
}
}
}
},
{
name: 'import_preferences',
description: 'Import preferences configuration from file',
inputSchema: {
type: 'object',
properties: {
importPath: {
type: 'string',
description: 'Path to import preferences file from'
}
},
required: ['importPath']
} }
} }
]; ];
@@ -84,79 +132,199 @@ export class PreferencesTools implements ToolExecutor {
async execute(toolName: string, args: any): Promise<ToolResponse> { async execute(toolName: string, args: any): Promise<ToolResponse> {
switch (toolName) { switch (toolName) {
case 'get_preferences': case 'open_preferences_settings':
return await this.getPreferences(args.key); return await this.openPreferencesSettings(args.tab, args.args);
case 'set_preferences': case 'query_preferences_config':
return await this.setPreferences(args.key, args.value); return await this.queryPreferencesConfig(args.name, args.path, args.type);
case 'get_global_preferences': case 'set_preferences_config':
return await this.getGlobalPreferences(args.key); return await this.setPreferencesConfig(args.name, args.path, args.value, args.type);
case 'set_global_preferences': case 'get_all_preferences':
return await this.setGlobalPreferences(args.key, args.value); return await this.getAllPreferences();
case 'get_recent_projects': case 'reset_preferences':
return await this.getRecentProjects(); return await this.resetPreferences(args.name, args.type);
case 'clear_recent_projects': case 'export_preferences':
return await this.clearRecentProjects(); return await this.exportPreferences(args.exportPath);
case 'import_preferences':
return await this.importPreferences(args.importPath);
default: default:
throw new Error(`Unknown tool: ${toolName}`); throw new Error(`Unknown tool: ${toolName}`);
} }
} }
private async getPreferences(key?: string): Promise<ToolResponse> { private async openPreferencesSettings(tab?: string, args?: any[]): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
const requestArgs = [];
if (tab) {
requestArgs.push(tab);
}
if (args && args.length > 0) {
requestArgs.push(...args);
}
(Editor.Message.request as any)('preferences', 'open-settings', ...requestArgs).then(() => {
resolve({ resolve({
success: false, success: true,
error: 'Preferences API is not supported through MCP', message: `Preferences settings opened${tab ? ` on tab: ${tab}` : ''}`
instruction: 'Please access preferences through the editor menu: Edit > Preferences or use the preferences panel in the editor' });
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
}); });
}); });
} }
private async setPreferences(key: string, value: any): Promise<ToolResponse> { private async queryPreferencesConfig(name: string, path?: string, type: string = 'global'): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
const requestArgs = [name];
if (path) {
requestArgs.push(path);
}
requestArgs.push(type);
(Editor.Message.request as any)('preferences', 'query-config', ...requestArgs).then((config: any) => {
resolve({ resolve({
success: false, success: true,
error: 'Preferences API is not supported through MCP', data: {
instruction: 'Please modify preferences through the editor menu: Edit > Preferences or use the preferences panel in the editor' name: name,
path: path,
type: type,
config: config
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
}); });
}); });
} }
private async getGlobalPreferences(key?: string): Promise<ToolResponse> { private async setPreferencesConfig(name: string, path: string, value: any, type: string = 'global'): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
(Editor.Message.request as any)('preferences', 'set-config', name, path, value, type).then((success: boolean) => {
if (success) {
resolve({
success: true,
message: `Preference '${name}.${path}' updated successfully`
});
} else {
resolve({ resolve({
success: false, success: false,
error: 'Global preferences API is not supported through MCP', error: `Failed to update preference '${name}.${path}'`
instruction: 'Please access global preferences through the editor menu: Edit > Preferences or use the preferences panel in the editor' });
}
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
}); });
}); });
} }
private async setGlobalPreferences(key: string, value: any): Promise<ToolResponse> { private async getAllPreferences(): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
// Common preference categories in Cocos Creator
const categories = [
'general',
'external-tools',
'data-editor',
'laboratory',
'extensions',
'preview',
'console',
'native',
'builder'
];
const preferences: any = {};
const queryPromises = categories.map(category => {
return Editor.Message.request('preferences', 'query-config', category, undefined, 'global')
.then((config: any) => {
preferences[category] = config;
})
.catch(() => {
// Ignore errors for categories that don't exist
preferences[category] = null;
});
});
Promise.all(queryPromises).then(() => {
// Filter out null entries
const validPreferences = Object.fromEntries(
Object.entries(preferences).filter(([_, value]) => value !== null)
);
resolve({ resolve({
success: false, success: true,
error: 'Global preferences API is not supported through MCP', data: {
instruction: 'Please modify global preferences through the editor menu: Edit > Preferences or use the preferences panel in the editor' categories: Object.keys(validPreferences),
preferences: validPreferences
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
}); });
}); });
} }
private async getRecentProjects(): Promise<ToolResponse> { private async resetPreferences(name?: string, type: string = 'global'): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
if (name) {
// Reset specific preference category
Editor.Message.request('preferences', 'query-config', name, undefined, 'default').then((defaultConfig: any) => {
return (Editor.Message.request as any)('preferences', 'set-config', name, '', defaultConfig, type);
}).then((success: boolean) => {
if (success) {
resolve({
success: true,
message: `Preference category '${name}' reset to default`
});
} else {
resolve({ resolve({
success: false, success: false,
error: 'Recent projects API is not supported through MCP', error: `Failed to reset preference category '${name}'`
instruction: 'Please check recent projects through the editor menu: File > Recent Projects or the start screen' });
}
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
} else {
resolve({
success: false,
error: 'Resetting all preferences is not supported through API. Please specify a preference category.'
});
}
});
}
private async exportPreferences(exportPath?: string): Promise<ToolResponse> {
return new Promise((resolve) => {
this.getAllPreferences().then((prefsResult: ToolResponse) => {
if (!prefsResult.success) {
resolve(prefsResult);
return;
}
const prefsData = JSON.stringify(prefsResult.data, null, 2);
const path = exportPath || `preferences_export_${Date.now()}.json`;
// For now, return the data - in a real implementation, you'd write to file
resolve({
success: true,
data: {
exportPath: path,
preferences: prefsResult.data,
jsonData: prefsData,
message: 'Preferences exported successfully'
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
}); });
}); });
} }
private async clearRecentProjects(): Promise<ToolResponse> { private async importPreferences(importPath: string): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
resolve({ resolve({
success: false, success: false,
error: 'Recent projects API is not supported through MCP', error: 'Import preferences functionality requires file system access which is not available in this context. Please manually import preferences through the Editor UI.'
instruction: 'Please clear recent projects through the editor menu: File > Recent Projects or the start screen'
}); });
}); });
} }

View File

@@ -393,10 +393,12 @@ export class ProjectTools implements ToolExecutor {
scenes: [] // Will use current scene scenes: [] // Will use current scene
}; };
Editor.Message.request('preview', 'start', previewConfig).then(() => { // Note: Preview module is not documented in official API
// Using fallback approach - open build panel as alternative
Editor.Message.request('builder', 'open').then(() => {
resolve({ resolve({
success: true, success: true,
message: `Project is running in ${platform} mode` message: `Build panel opened. Preview functionality requires manual setup.`
}); });
}).catch((err: Error) => { }).catch((err: Error) => {
resolve({ success: false, error: err.message }); resolve({ success: false, error: err.message });
@@ -413,11 +415,16 @@ export class ProjectTools implements ToolExecutor {
buildPath: `build/${args.platform}` buildPath: `build/${args.platform}`
}; };
Editor.Message.request('builder', 'build', buildOptions).then(() => { // Note: Builder module only supports 'open' and 'query-worker-ready'
// Building requires manual interaction through the build panel
Editor.Message.request('builder', 'open').then(() => {
resolve({ resolve({
success: true, success: true,
message: `Project built for ${args.platform}`, message: `Build panel opened for ${args.platform}. Please configure and start build manually.`,
data: { buildPath: buildOptions.buildPath } data: {
platform: args.platform,
instruction: "Use the build panel to configure and start the build process"
}
}); });
}).catch((err: Error) => { }).catch((err: Error) => {
resolve({ success: false, error: err.message }); resolve({ success: false, error: err.message });
@@ -435,8 +442,11 @@ export class ProjectTools implements ToolExecutor {
cocosVersion: (Editor as any).versions?.cocos || 'Unknown' cocosVersion: (Editor as any).versions?.cocos || 'Unknown'
}; };
Editor.Message.request('project', 'query-info').then((additionalInfo: any) => { // Note: 'query-info' API doesn't exist, using 'query-config' instead
Object.assign(info, additionalInfo); Editor.Message.request('project', 'query-config', 'project').then((additionalInfo: any) => {
if (additionalInfo) {
Object.assign(info, { config: additionalInfo });
}
resolve({ success: true, data: info }); resolve({ success: true, data: info });
}).catch(() => { }).catch(() => {
// Return basic info even if detailed query fails // Return basic info even if detailed query fails
@@ -567,9 +577,8 @@ export class ProjectTools implements ToolExecutor {
} }
} }
Editor.Message.request('asset-db', 'query-assets', { // Note: query-assets API parameters corrected based on documentation
pattern: pattern Editor.Message.request('asset-db', 'query-assets', { pattern: pattern }).then((results: any[]) => {
}).then((results: any[]) => {
const assets = results.map(asset => ({ const assets = results.map(asset => ({
name: asset.name, name: asset.name,
uuid: asset.uuid, uuid: asset.uuid,

View File

@@ -0,0 +1,399 @@
import { ToolDefinition, ToolResponse, ToolExecutor } from '../types';
export class ReferenceImageTools implements ToolExecutor {
getTools(): ToolDefinition[] {
return [
{
name: 'add_reference_image',
description: 'Add reference image(s) to scene',
inputSchema: {
type: 'object',
properties: {
paths: {
type: 'array',
items: { type: 'string' },
description: 'Array of reference image absolute paths'
}
},
required: ['paths']
}
},
{
name: 'remove_reference_image',
description: 'Remove reference image(s)',
inputSchema: {
type: 'object',
properties: {
paths: {
type: 'array',
items: { type: 'string' },
description: 'Array of reference image paths to remove (optional, removes current if empty)'
}
}
}
},
{
name: 'switch_reference_image',
description: 'Switch to specific reference image',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: 'Reference image absolute path'
},
sceneUUID: {
type: 'string',
description: 'Specific scene UUID (optional)'
}
},
required: ['path']
}
},
{
name: 'set_reference_image_data',
description: 'Set reference image transform and display properties',
inputSchema: {
type: 'object',
properties: {
key: {
type: 'string',
description: 'Property key',
enum: ['path', 'x', 'y', 'sx', 'sy', 'opacity']
},
value: {
description: 'Property value (path: string, x/y/sx/sy: number, opacity: number 0-1)'
}
},
required: ['key', 'value']
}
},
{
name: 'query_reference_image_config',
description: 'Query reference image configuration',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'query_current_reference_image',
description: 'Query current reference image data',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'refresh_reference_image',
description: 'Refresh reference image display',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'set_reference_image_position',
description: 'Set reference image position',
inputSchema: {
type: 'object',
properties: {
x: {
type: 'number',
description: 'X offset'
},
y: {
type: 'number',
description: 'Y offset'
}
},
required: ['x', 'y']
}
},
{
name: 'set_reference_image_scale',
description: 'Set reference image scale',
inputSchema: {
type: 'object',
properties: {
sx: {
type: 'number',
description: 'X scale',
minimum: 0.1,
maximum: 10
},
sy: {
type: 'number',
description: 'Y scale',
minimum: 0.1,
maximum: 10
}
},
required: ['sx', 'sy']
}
},
{
name: 'set_reference_image_opacity',
description: 'Set reference image opacity',
inputSchema: {
type: 'object',
properties: {
opacity: {
type: 'number',
description: 'Opacity (0.0 to 1.0)',
minimum: 0,
maximum: 1
}
},
required: ['opacity']
}
},
{
name: 'list_reference_images',
description: 'List all available reference images',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'clear_all_reference_images',
description: 'Clear all reference images',
inputSchema: {
type: 'object',
properties: {}
}
}
];
}
async execute(toolName: string, args: any): Promise<ToolResponse> {
switch (toolName) {
case 'add_reference_image':
return await this.addReferenceImage(args.paths);
case 'remove_reference_image':
return await this.removeReferenceImage(args.paths);
case 'switch_reference_image':
return await this.switchReferenceImage(args.path, args.sceneUUID);
case 'set_reference_image_data':
return await this.setReferenceImageData(args.key, args.value);
case 'query_reference_image_config':
return await this.queryReferenceImageConfig();
case 'query_current_reference_image':
return await this.queryCurrentReferenceImage();
case 'refresh_reference_image':
return await this.refreshReferenceImage();
case 'set_reference_image_position':
return await this.setReferenceImagePosition(args.x, args.y);
case 'set_reference_image_scale':
return await this.setReferenceImageScale(args.sx, args.sy);
case 'set_reference_image_opacity':
return await this.setReferenceImageOpacity(args.opacity);
case 'list_reference_images':
return await this.listReferenceImages();
case 'clear_all_reference_images':
return await this.clearAllReferenceImages();
default:
throw new Error(`Unknown tool: ${toolName}`);
}
}
private async addReferenceImage(paths: string[]): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('reference-image', 'add-image', paths).then(() => {
resolve({
success: true,
data: {
addedPaths: paths,
count: paths.length,
message: `Added ${paths.length} reference image(s)`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async removeReferenceImage(paths?: string[]): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('reference-image', 'remove-image', paths).then(() => {
const message = paths && paths.length > 0 ?
`Removed ${paths.length} reference image(s)` :
'Removed current reference image';
resolve({
success: true,
message: message
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async switchReferenceImage(path: string, sceneUUID?: string): Promise<ToolResponse> {
return new Promise((resolve) => {
const args = sceneUUID ? [path, sceneUUID] : [path];
Editor.Message.request('reference-image', 'switch-image', ...args).then(() => {
resolve({
success: true,
data: {
path: path,
sceneUUID: sceneUUID,
message: `Switched to reference image: ${path}`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async setReferenceImageData(key: string, value: any): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('reference-image', 'set-image-data', key, value).then(() => {
resolve({
success: true,
data: {
key: key,
value: value,
message: `Reference image ${key} set to ${value}`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryReferenceImageConfig(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('reference-image', 'query-config').then((config: any) => {
resolve({
success: true,
data: config
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryCurrentReferenceImage(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('reference-image', 'query-current').then((current: any) => {
resolve({
success: true,
data: current
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async refreshReferenceImage(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('reference-image', 'refresh').then(() => {
resolve({
success: true,
message: 'Reference image refreshed'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async setReferenceImagePosition(x: number, y: number): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
await Editor.Message.request('reference-image', 'set-image-data', 'x', x);
await Editor.Message.request('reference-image', 'set-image-data', 'y', y);
resolve({
success: true,
data: {
x: x,
y: y,
message: `Reference image position set to (${x}, ${y})`
}
});
} catch (err: any) {
resolve({ success: false, error: err.message });
}
});
}
private async setReferenceImageScale(sx: number, sy: number): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
await Editor.Message.request('reference-image', 'set-image-data', 'sx', sx);
await Editor.Message.request('reference-image', 'set-image-data', 'sy', sy);
resolve({
success: true,
data: {
sx: sx,
sy: sy,
message: `Reference image scale set to (${sx}, ${sy})`
}
});
} catch (err: any) {
resolve({ success: false, error: err.message });
}
});
}
private async setReferenceImageOpacity(opacity: number): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('reference-image', 'set-image-data', 'opacity', opacity).then(() => {
resolve({
success: true,
data: {
opacity: opacity,
message: `Reference image opacity set to ${opacity}`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async listReferenceImages(): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
const config = await Editor.Message.request('reference-image', 'query-config');
const current = await Editor.Message.request('reference-image', 'query-current');
resolve({
success: true,
data: {
config: config,
current: current,
message: 'Reference image information retrieved'
}
});
} catch (err: any) {
resolve({ success: false, error: err.message });
}
});
}
private async clearAllReferenceImages(): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
// Remove all reference images by calling remove-image without paths
await Editor.Message.request('reference-image', 'remove-image');
resolve({
success: true,
message: 'All reference images cleared'
});
} catch (err: any) {
resolve({ success: false, error: err.message });
}
});
}
}

View File

@@ -0,0 +1,776 @@
import { ToolDefinition, ToolResponse, ToolExecutor } from '../types';
export class SceneAdvancedTools implements ToolExecutor {
getTools(): ToolDefinition[] {
return [
{
name: 'reset_node_property',
description: 'Reset node property to default value',
inputSchema: {
type: 'object',
properties: {
uuid: {
type: 'string',
description: 'Node UUID'
},
path: {
type: 'string',
description: 'Property path (e.g., position, rotation, scale)'
}
},
required: ['uuid', 'path']
}
},
{
name: 'move_array_element',
description: 'Move array element position',
inputSchema: {
type: 'object',
properties: {
uuid: {
type: 'string',
description: 'Node UUID'
},
path: {
type: 'string',
description: 'Array property path (e.g., __comps__)'
},
target: {
type: 'number',
description: 'Target item original index'
},
offset: {
type: 'number',
description: 'Offset amount (positive or negative)'
}
},
required: ['uuid', 'path', 'target', 'offset']
}
},
{
name: 'remove_array_element',
description: 'Remove array element at specific index',
inputSchema: {
type: 'object',
properties: {
uuid: {
type: 'string',
description: 'Node UUID'
},
path: {
type: 'string',
description: 'Array property path'
},
index: {
type: 'number',
description: 'Target item index to remove'
}
},
required: ['uuid', 'path', 'index']
}
},
{
name: 'copy_node',
description: 'Copy node for later paste operation',
inputSchema: {
type: 'object',
properties: {
uuids: {
oneOf: [
{ type: 'string' },
{ type: 'array', items: { type: 'string' } }
],
description: 'Node UUID or array of UUIDs to copy'
}
},
required: ['uuids']
}
},
{
name: 'paste_node',
description: 'Paste previously copied nodes',
inputSchema: {
type: 'object',
properties: {
target: {
type: 'string',
description: 'Target parent node UUID'
},
uuids: {
oneOf: [
{ type: 'string' },
{ type: 'array', items: { type: 'string' } }
],
description: 'Node UUIDs to paste'
},
keepWorldTransform: {
type: 'boolean',
description: 'Keep world transform coordinates',
default: false
}
},
required: ['target', 'uuids']
}
},
{
name: 'cut_node',
description: 'Cut node (copy + mark for move)',
inputSchema: {
type: 'object',
properties: {
uuids: {
oneOf: [
{ type: 'string' },
{ type: 'array', items: { type: 'string' } }
],
description: 'Node UUID or array of UUIDs to cut'
}
},
required: ['uuids']
}
},
{
name: 'reset_node_transform',
description: 'Reset node position, rotation and scale',
inputSchema: {
type: 'object',
properties: {
uuid: {
type: 'string',
description: 'Node UUID'
}
},
required: ['uuid']
}
},
{
name: 'reset_component',
description: 'Reset component to default values',
inputSchema: {
type: 'object',
properties: {
uuid: {
type: 'string',
description: 'Component UUID'
}
},
required: ['uuid']
}
},
{
name: 'restore_prefab',
description: 'Restore prefab instance from asset',
inputSchema: {
type: 'object',
properties: {
nodeUuid: {
type: 'string',
description: 'Node UUID'
},
assetUuid: {
type: 'string',
description: 'Prefab asset UUID'
}
},
required: ['nodeUuid', 'assetUuid']
}
},
{
name: 'execute_component_method',
description: 'Execute method on component',
inputSchema: {
type: 'object',
properties: {
uuid: {
type: 'string',
description: 'Component UUID'
},
name: {
type: 'string',
description: 'Method name'
},
args: {
type: 'array',
description: 'Method arguments',
default: []
}
},
required: ['uuid', 'name']
}
},
{
name: 'execute_scene_script',
description: 'Execute scene script method',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Plugin name'
},
method: {
type: 'string',
description: 'Method name'
},
args: {
type: 'array',
description: 'Method arguments',
default: []
}
},
required: ['name', 'method']
}
},
{
name: 'scene_snapshot',
description: 'Create scene state snapshot',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'scene_snapshot_abort',
description: 'Abort scene snapshot creation',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'begin_undo_recording',
description: 'Begin recording undo data',
inputSchema: {
type: 'object',
properties: {
nodeUuid: {
type: 'string',
description: 'Node UUID to record'
}
},
required: ['nodeUuid']
}
},
{
name: 'end_undo_recording',
description: 'End recording undo data',
inputSchema: {
type: 'object',
properties: {
undoId: {
type: 'string',
description: 'Undo recording ID from begin_undo_recording'
}
},
required: ['undoId']
}
},
{
name: 'cancel_undo_recording',
description: 'Cancel undo recording',
inputSchema: {
type: 'object',
properties: {
undoId: {
type: 'string',
description: 'Undo recording ID to cancel'
}
},
required: ['undoId']
}
},
{
name: 'soft_reload_scene',
description: 'Soft reload current scene',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'query_scene_ready',
description: 'Check if scene is ready',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'query_scene_dirty',
description: 'Check if scene has unsaved changes',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'query_scene_classes',
description: 'Query all registered classes',
inputSchema: {
type: 'object',
properties: {
extends: {
type: 'string',
description: 'Filter classes that extend this base class'
}
}
}
},
{
name: 'query_scene_components',
description: 'Query available scene components',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'query_component_has_script',
description: 'Check if component has script',
inputSchema: {
type: 'object',
properties: {
className: {
type: 'string',
description: 'Script class name to check'
}
},
required: ['className']
}
},
{
name: 'query_nodes_by_asset_uuid',
description: 'Find nodes that use specific asset UUID',
inputSchema: {
type: 'object',
properties: {
assetUuid: {
type: 'string',
description: 'Asset UUID to search for'
}
},
required: ['assetUuid']
}
}
];
}
async execute(toolName: string, args: any): Promise<ToolResponse> {
switch (toolName) {
case 'reset_node_property':
return await this.resetNodeProperty(args.uuid, args.path);
case 'move_array_element':
return await this.moveArrayElement(args.uuid, args.path, args.target, args.offset);
case 'remove_array_element':
return await this.removeArrayElement(args.uuid, args.path, args.index);
case 'copy_node':
return await this.copyNode(args.uuids);
case 'paste_node':
return await this.pasteNode(args.target, args.uuids, args.keepWorldTransform);
case 'cut_node':
return await this.cutNode(args.uuids);
case 'reset_node_transform':
return await this.resetNodeTransform(args.uuid);
case 'reset_component':
return await this.resetComponent(args.uuid);
case 'restore_prefab':
return await this.restorePrefab(args.nodeUuid, args.assetUuid);
case 'execute_component_method':
return await this.executeComponentMethod(args.uuid, args.name, args.args);
case 'execute_scene_script':
return await this.executeSceneScript(args.name, args.method, args.args);
case 'scene_snapshot':
return await this.sceneSnapshot();
case 'scene_snapshot_abort':
return await this.sceneSnapshotAbort();
case 'begin_undo_recording':
return await this.beginUndoRecording(args.nodeUuid);
case 'end_undo_recording':
return await this.endUndoRecording(args.undoId);
case 'cancel_undo_recording':
return await this.cancelUndoRecording(args.undoId);
case 'soft_reload_scene':
return await this.softReloadScene();
case 'query_scene_ready':
return await this.querySceneReady();
case 'query_scene_dirty':
return await this.querySceneDirty();
case 'query_scene_classes':
return await this.querySceneClasses(args.extends);
case 'query_scene_components':
return await this.querySceneComponents();
case 'query_component_has_script':
return await this.queryComponentHasScript(args.className);
case 'query_nodes_by_asset_uuid':
return await this.queryNodesByAssetUuid(args.assetUuid);
default:
throw new Error(`Unknown tool: ${toolName}`);
}
}
private async resetNodeProperty(uuid: string, path: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'reset-property', {
uuid,
path,
dump: { value: null }
}).then(() => {
resolve({
success: true,
message: `Property '${path}' reset to default value`
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async moveArrayElement(uuid: string, path: string, target: number, offset: number): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'move-array-element', {
uuid,
path,
target,
offset
}).then(() => {
resolve({
success: true,
message: `Array element at index ${target} moved by ${offset}`
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async removeArrayElement(uuid: string, path: string, index: number): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'remove-array-element', {
uuid,
path,
index
}).then(() => {
resolve({
success: true,
message: `Array element at index ${index} removed`
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async copyNode(uuids: string | string[]): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'copy-node', uuids).then((result: string | string[]) => {
resolve({
success: true,
data: {
copiedUuids: result,
message: 'Node(s) copied successfully'
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async pasteNode(target: string, uuids: string | string[], keepWorldTransform: boolean = false): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'paste-node', {
target,
uuids,
keepWorldTransform
}).then((result: string | string[]) => {
resolve({
success: true,
data: {
newUuids: result,
message: 'Node(s) pasted successfully'
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async cutNode(uuids: string | string[]): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'cut-node', uuids).then((result: any) => {
resolve({
success: true,
data: {
cutUuids: result,
message: 'Node(s) cut successfully'
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async resetNodeTransform(uuid: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'reset-node', { uuid }).then(() => {
resolve({
success: true,
message: 'Node transform reset to default'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async resetComponent(uuid: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'reset-component', { uuid }).then(() => {
resolve({
success: true,
message: 'Component reset to default values'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async restorePrefab(nodeUuid: string, assetUuid: string): Promise<ToolResponse> {
return new Promise((resolve) => {
(Editor.Message.request as any)('scene', 'restore-prefab', nodeUuid, assetUuid).then(() => {
resolve({
success: true,
message: 'Prefab restored successfully'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async executeComponentMethod(uuid: string, name: string, args: any[] = []): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'execute-component-method', {
uuid,
name,
args
}).then((result: any) => {
resolve({
success: true,
data: {
result: result,
message: `Method '${name}' executed successfully`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async executeSceneScript(name: string, method: string, args: any[] = []): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'execute-scene-script', {
name,
method,
args
}).then((result: any) => {
resolve({
success: true,
data: result
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async sceneSnapshot(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'snapshot').then(() => {
resolve({
success: true,
message: 'Scene snapshot created'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async sceneSnapshotAbort(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'snapshot-abort').then(() => {
resolve({
success: true,
message: 'Scene snapshot aborted'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async beginUndoRecording(nodeUuid: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'begin-recording', nodeUuid).then((undoId: string) => {
resolve({
success: true,
data: {
undoId: undoId,
message: 'Undo recording started'
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async endUndoRecording(undoId: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'end-recording', undoId).then(() => {
resolve({
success: true,
message: 'Undo recording ended'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async cancelUndoRecording(undoId: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'cancel-recording', undoId).then(() => {
resolve({
success: true,
message: 'Undo recording cancelled'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async softReloadScene(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'soft-reload').then(() => {
resolve({
success: true,
message: 'Scene soft reloaded successfully'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async querySceneReady(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-is-ready').then((ready: boolean) => {
resolve({
success: true,
data: {
ready: ready,
message: ready ? 'Scene is ready' : 'Scene is not ready'
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async querySceneDirty(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-dirty').then((dirty: boolean) => {
resolve({
success: true,
data: {
dirty: dirty,
message: dirty ? 'Scene has unsaved changes' : 'Scene is clean'
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async querySceneClasses(extendsClass?: string): Promise<ToolResponse> {
return new Promise((resolve) => {
const options: any = {};
if (extendsClass) {
options.extends = extendsClass;
}
Editor.Message.request('scene', 'query-classes', options).then((classes: any[]) => {
resolve({
success: true,
data: {
classes: classes,
count: classes.length,
extendsFilter: extendsClass
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async querySceneComponents(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-components').then((components: any[]) => {
resolve({
success: true,
data: {
components: components,
count: components.length
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryComponentHasScript(className: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-component-has-script', className).then((hasScript: boolean) => {
resolve({
success: true,
data: {
className: className,
hasScript: hasScript,
message: hasScript ? `Component '${className}' has script` : `Component '${className}' does not have script`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryNodesByAssetUuid(assetUuid: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-nodes-by-asset-uuid', assetUuid).then((nodeUuids: string[]) => {
resolve({
success: true,
data: {
assetUuid: assetUuid,
nodeUuids: nodeUuids,
count: nodeUuids.length,
message: `Found ${nodeUuids.length} nodes using asset`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
}

View File

@@ -158,6 +158,7 @@ export class SceneTools implements ToolExecutor {
private async getSceneList(): Promise<ToolResponse> { private async getSceneList(): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
// Note: query-assets API corrected with proper parameters
Editor.Message.request('asset-db', 'query-assets', { Editor.Message.request('asset-db', 'query-assets', {
pattern: 'db://assets/**/*.scene' pattern: 'db://assets/**/*.scene'
}).then((results: any[]) => { }).then((results: any[]) => {

View File

@@ -0,0 +1,628 @@
import { ToolDefinition, ToolResponse, ToolExecutor } from '../types';
export class SceneViewTools implements ToolExecutor {
getTools(): ToolDefinition[] {
return [
{
name: 'change_gizmo_tool',
description: 'Change Gizmo tool',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Tool name',
enum: ['position', 'rotation', 'scale', 'rect']
}
},
required: ['name']
}
},
{
name: 'query_gizmo_tool_name',
description: 'Get current Gizmo tool name',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'change_gizmo_pivot',
description: 'Change transform pivot point',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Pivot point',
enum: ['pivot', 'center']
}
},
required: ['name']
}
},
{
name: 'query_gizmo_pivot',
description: 'Get current Gizmo pivot point',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'query_gizmo_view_mode',
description: 'Query view mode (view/select)',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'change_gizmo_coordinate',
description: 'Change coordinate system',
inputSchema: {
type: 'object',
properties: {
type: {
type: 'string',
description: 'Coordinate system',
enum: ['local', 'global']
}
},
required: ['type']
}
},
{
name: 'query_gizmo_coordinate',
description: 'Get current coordinate system',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'change_view_mode_2d_3d',
description: 'Change 2D/3D view mode',
inputSchema: {
type: 'object',
properties: {
is2D: {
type: 'boolean',
description: '2D/3D view mode (true for 2D, false for 3D)'
}
},
required: ['is2D']
}
},
{
name: 'query_view_mode_2d_3d',
description: 'Get current view mode',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'set_grid_visible',
description: 'Show/hide grid',
inputSchema: {
type: 'object',
properties: {
visible: {
type: 'boolean',
description: 'Grid visibility'
}
},
required: ['visible']
}
},
{
name: 'query_grid_visible',
description: 'Query grid visibility status',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'set_icon_gizmo_3d',
description: 'Set IconGizmo to 3D or 2D mode',
inputSchema: {
type: 'object',
properties: {
is3D: {
type: 'boolean',
description: '3D/2D IconGizmo (true for 3D, false for 2D)'
}
},
required: ['is3D']
}
},
{
name: 'query_icon_gizmo_3d',
description: 'Query IconGizmo mode',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'set_icon_gizmo_size',
description: 'Set IconGizmo size',
inputSchema: {
type: 'object',
properties: {
size: {
type: 'number',
description: 'IconGizmo size',
minimum: 10,
maximum: 100
}
},
required: ['size']
}
},
{
name: 'query_icon_gizmo_size',
description: 'Query IconGizmo size',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'focus_camera_on_nodes',
description: 'Focus scene camera on nodes',
inputSchema: {
type: 'object',
properties: {
uuids: {
oneOf: [
{ type: 'array', items: { type: 'string' } },
{ type: 'null' }
],
description: 'Node UUIDs to focus on (null for all)'
}
},
required: ['uuids']
}
},
{
name: 'align_camera_with_view',
description: 'Apply scene camera position and angle to selected node',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'align_view_with_node',
description: 'Apply selected node position and angle to current view',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'get_scene_view_status',
description: 'Get comprehensive scene view status',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'reset_scene_view',
description: 'Reset scene view to default settings',
inputSchema: {
type: 'object',
properties: {}
}
}
];
}
async execute(toolName: string, args: any): Promise<ToolResponse> {
switch (toolName) {
case 'change_gizmo_tool':
return await this.changeGizmoTool(args.name);
case 'query_gizmo_tool_name':
return await this.queryGizmoToolName();
case 'change_gizmo_pivot':
return await this.changeGizmoPivot(args.name);
case 'query_gizmo_pivot':
return await this.queryGizmoPivot();
case 'query_gizmo_view_mode':
return await this.queryGizmoViewMode();
case 'change_gizmo_coordinate':
return await this.changeGizmoCoordinate(args.type);
case 'query_gizmo_coordinate':
return await this.queryGizmoCoordinate();
case 'change_view_mode_2d_3d':
return await this.changeViewMode2D3D(args.is2D);
case 'query_view_mode_2d_3d':
return await this.queryViewMode2D3D();
case 'set_grid_visible':
return await this.setGridVisible(args.visible);
case 'query_grid_visible':
return await this.queryGridVisible();
case 'set_icon_gizmo_3d':
return await this.setIconGizmo3D(args.is3D);
case 'query_icon_gizmo_3d':
return await this.queryIconGizmo3D();
case 'set_icon_gizmo_size':
return await this.setIconGizmoSize(args.size);
case 'query_icon_gizmo_size':
return await this.queryIconGizmoSize();
case 'focus_camera_on_nodes':
return await this.focusCameraOnNodes(args.uuids);
case 'align_camera_with_view':
return await this.alignCameraWithView();
case 'align_view_with_node':
return await this.alignViewWithNode();
case 'get_scene_view_status':
return await this.getSceneViewStatus();
case 'reset_scene_view':
return await this.resetSceneView();
default:
throw new Error(`Unknown tool: ${toolName}`);
}
}
private async changeGizmoTool(name: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'change-gizmo-tool', name).then(() => {
resolve({
success: true,
message: `Gizmo tool changed to '${name}'`
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryGizmoToolName(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-gizmo-tool-name').then((toolName: string) => {
resolve({
success: true,
data: {
currentTool: toolName,
message: `Current Gizmo tool: ${toolName}`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async changeGizmoPivot(name: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'change-gizmo-pivot', name).then(() => {
resolve({
success: true,
message: `Gizmo pivot changed to '${name}'`
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryGizmoPivot(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-gizmo-pivot').then((pivotName: string) => {
resolve({
success: true,
data: {
currentPivot: pivotName,
message: `Current Gizmo pivot: ${pivotName}`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryGizmoViewMode(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-gizmo-view-mode').then((viewMode: string) => {
resolve({
success: true,
data: {
viewMode: viewMode,
message: `Current view mode: ${viewMode}`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async changeGizmoCoordinate(type: string): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'change-gizmo-coordinate', type).then(() => {
resolve({
success: true,
message: `Coordinate system changed to '${type}'`
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryGizmoCoordinate(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-gizmo-coordinate').then((coordinate: string) => {
resolve({
success: true,
data: {
coordinate: coordinate,
message: `Current coordinate system: ${coordinate}`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async changeViewMode2D3D(is2D: boolean): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'change-is2D', is2D).then(() => {
resolve({
success: true,
message: `View mode changed to ${is2D ? '2D' : '3D'}`
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryViewMode2D3D(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-is2D').then((is2D: boolean) => {
resolve({
success: true,
data: {
is2D: is2D,
viewMode: is2D ? '2D' : '3D',
message: `Current view mode: ${is2D ? '2D' : '3D'}`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async setGridVisible(visible: boolean): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'set-grid-visible', visible).then(() => {
resolve({
success: true,
message: `Grid ${visible ? 'shown' : 'hidden'}`
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryGridVisible(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-is-grid-visible').then((visible: boolean) => {
resolve({
success: true,
data: {
visible: visible,
message: `Grid is ${visible ? 'visible' : 'hidden'}`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async setIconGizmo3D(is3D: boolean): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'set-icon-gizmo-3d', is3D).then(() => {
resolve({
success: true,
message: `IconGizmo set to ${is3D ? '3D' : '2D'} mode`
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryIconGizmo3D(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-is-icon-gizmo-3d').then((is3D: boolean) => {
resolve({
success: true,
data: {
is3D: is3D,
mode: is3D ? '3D' : '2D',
message: `IconGizmo is in ${is3D ? '3D' : '2D'} mode`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async setIconGizmoSize(size: number): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'set-icon-gizmo-size', size).then(() => {
resolve({
success: true,
message: `IconGizmo size set to ${size}`
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryIconGizmoSize(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'query-icon-gizmo-size').then((size: number) => {
resolve({
success: true,
data: {
size: size,
message: `IconGizmo size: ${size}`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async focusCameraOnNodes(uuids: string[] | null): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'focus-camera', uuids || []).then(() => {
const message = uuids === null ?
'Camera focused on all nodes' :
`Camera focused on ${uuids.length} node(s)`;
resolve({
success: true,
message: message
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async alignCameraWithView(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'align-with-view').then(() => {
resolve({
success: true,
message: 'Scene camera aligned with current view'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async alignViewWithNode(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('scene', 'align-with-view-node').then(() => {
resolve({
success: true,
message: 'View aligned with selected node'
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async getSceneViewStatus(): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
// Gather all view status information
const [
gizmoTool,
gizmoPivot,
gizmoCoordinate,
viewMode2D3D,
gridVisible,
iconGizmo3D,
iconGizmoSize
] = await Promise.allSettled([
this.queryGizmoToolName(),
this.queryGizmoPivot(),
this.queryGizmoCoordinate(),
this.queryViewMode2D3D(),
this.queryGridVisible(),
this.queryIconGizmo3D(),
this.queryIconGizmoSize()
]);
const status: any = {
timestamp: new Date().toISOString()
};
// Extract data from fulfilled promises
if (gizmoTool.status === 'fulfilled' && gizmoTool.value.success) {
status.gizmoTool = gizmoTool.value.data.currentTool;
}
if (gizmoPivot.status === 'fulfilled' && gizmoPivot.value.success) {
status.gizmoPivot = gizmoPivot.value.data.currentPivot;
}
if (gizmoCoordinate.status === 'fulfilled' && gizmoCoordinate.value.success) {
status.coordinate = gizmoCoordinate.value.data.coordinate;
}
if (viewMode2D3D.status === 'fulfilled' && viewMode2D3D.value.success) {
status.is2D = viewMode2D3D.value.data.is2D;
status.viewMode = viewMode2D3D.value.data.viewMode;
}
if (gridVisible.status === 'fulfilled' && gridVisible.value.success) {
status.gridVisible = gridVisible.value.data.visible;
}
if (iconGizmo3D.status === 'fulfilled' && iconGizmo3D.value.success) {
status.iconGizmo3D = iconGizmo3D.value.data.is3D;
}
if (iconGizmoSize.status === 'fulfilled' && iconGizmoSize.value.success) {
status.iconGizmoSize = iconGizmoSize.value.data.size;
}
resolve({
success: true,
data: status
});
} catch (err: any) {
resolve({
success: false,
error: `Failed to get scene view status: ${err.message}`
});
}
});
}
private async resetSceneView(): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try {
// Reset scene view to default settings
const resetActions = [
this.changeGizmoTool('position'),
this.changeGizmoPivot('pivot'),
this.changeGizmoCoordinate('local'),
this.changeViewMode2D3D(false), // 3D mode
this.setGridVisible(true),
this.setIconGizmo3D(true),
this.setIconGizmoSize(60)
];
await Promise.all(resetActions);
resolve({
success: true,
message: 'Scene view reset to default settings'
});
} catch (err: any) {
resolve({
success: false,
error: `Failed to reset scene view: ${err.message}`
});
}
});
}
}

View File

@@ -4,73 +4,54 @@ export class ServerTools implements ToolExecutor {
getTools(): ToolDefinition[] { getTools(): ToolDefinition[] {
return [ return [
{ {
name: 'get_server_info', name: 'query_server_ip_list',
description: 'Get server information', description: 'Query server IP list',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: {} properties: {}
} }
}, },
{ {
name: 'broadcast_custom_message', name: 'query_sorted_server_ip_list',
description: 'Broadcast a custom message', description: 'Get sorted server IP list',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'query_server_port',
description: 'Query editor server current port',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'get_server_status',
description: 'Get comprehensive server status information',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'check_server_connectivity',
description: 'Check server connectivity and network status',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: { properties: {
message: { timeout: {
type: 'string', type: 'number',
description: 'Message name' description: 'Timeout in milliseconds',
}, default: 5000
data: { }
description: 'Message data (optional)'
} }
},
required: ['message']
} }
}, },
{ {
name: 'get_editor_version', name: 'get_network_interfaces',
description: 'Get editor version information', description: 'Get available network interfaces',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'get_project_name',
description: 'Get current project name',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'get_project_path',
description: 'Get current project path',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'get_project_uuid',
description: 'Get current project UUID',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'restart_editor',
description: 'Request to restart the editor',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'quit_editor',
description: 'Request to quit the editor',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: {} properties: {}
@@ -81,167 +62,199 @@ export class ServerTools implements ToolExecutor {
async execute(toolName: string, args: any): Promise<ToolResponse> { async execute(toolName: string, args: any): Promise<ToolResponse> {
switch (toolName) { switch (toolName) {
case 'get_server_info': case 'query_server_ip_list':
return await this.getServerInfo(); return await this.queryServerIPList();
case 'broadcast_custom_message': case 'query_sorted_server_ip_list':
return await this.broadcastCustomMessage(args.message, args.data); return await this.querySortedServerIPList();
case 'get_editor_version': case 'query_server_port':
return await this.getEditorVersion(); return await this.queryServerPort();
case 'get_project_name': case 'get_server_status':
return await this.getProjectName(); return await this.getServerStatus();
case 'get_project_path': case 'check_server_connectivity':
return await this.getProjectPath(); return await this.checkServerConnectivity(args.timeout);
case 'get_project_uuid': case 'get_network_interfaces':
return await this.getProjectUuid(); return await this.getNetworkInterfaces();
case 'restart_editor':
return await this.restartEditor();
case 'quit_editor':
return await this.quitEditor();
default: default:
throw new Error(`Unknown tool: ${toolName}`); throw new Error(`Unknown tool: ${toolName}`);
} }
} }
private async getServerInfo(): Promise<ToolResponse> { private async queryServerIPList(): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
try { Editor.Message.request('server', 'query-ip-list').then((ipList: string[]) => {
const info = {
editorVersion: (Editor as any).versions?.editor || 'Unknown',
cocosVersion: (Editor as any).versions?.cocos || 'Unknown',
nodeVersion: process.version,
platform: process.platform,
arch: process.arch,
projectName: Editor.Project.name,
projectPath: Editor.Project.path,
projectUuid: Editor.Project.uuid
};
resolve({ resolve({
success: true, success: true,
data: { data: {
server: info, ipList: ipList,
message: 'Server information retrieved successfully' count: ipList.length,
message: 'IP list retrieved successfully'
} }
}); });
} catch (err: any) { }).catch((err: Error) => {
resolve({ success: false, error: err.message }); resolve({ success: false, error: err.message });
} });
}); });
} }
private async broadcastCustomMessage(message: string, data?: any): Promise<ToolResponse> { private async querySortedServerIPList(): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise((resolve) => {
Editor.Message.request('server', 'query-sort-ip-list').then((sortedIPList: string[]) => {
resolve({
success: true,
data: {
sortedIPList: sortedIPList,
count: sortedIPList.length,
message: 'Sorted IP list retrieved successfully'
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async queryServerPort(): Promise<ToolResponse> {
return new Promise((resolve) => {
Editor.Message.request('server', 'query-port').then((port: number) => {
resolve({
success: true,
data: {
port: port,
message: `Editor server is running on port ${port}`
}
});
}).catch((err: Error) => {
resolve({ success: false, error: err.message });
});
});
}
private async getServerStatus(): Promise<ToolResponse> {
return new Promise(async (resolve) => {
try { try {
if (data !== undefined) { // Gather comprehensive server information
Editor.Message.broadcast(message, data); const [ipListResult, portResult] = await Promise.allSettled([
this.queryServerIPList(),
this.queryServerPort()
]);
const status: any = {
timestamp: new Date().toISOString(),
serverRunning: true
};
if (ipListResult.status === 'fulfilled' && ipListResult.value.success) {
status.availableIPs = ipListResult.value.data.ipList;
status.ipCount = ipListResult.value.data.count;
} else { } else {
Editor.Message.broadcast(message); status.availableIPs = [];
status.ipCount = 0;
status.ipError = ipListResult.status === 'rejected' ? ipListResult.reason : ipListResult.value.error;
} }
if (portResult.status === 'fulfilled' && portResult.value.success) {
status.port = portResult.value.data.port;
} else {
status.port = null;
status.portError = portResult.status === 'rejected' ? portResult.reason : portResult.value.error;
}
// Add additional server info
status.mcpServerPort = 3000; // Our MCP server port
status.editorVersion = (Editor as any).versions?.cocos || 'Unknown';
status.platform = process.platform;
status.nodeVersion = process.version;
resolve({ resolve({
success: true, success: true,
data: { data: status
message: message,
data: data,
result: 'Message broadcasted successfully'
}
}); });
} catch (err: any) { } catch (err: any) {
resolve({ success: false, error: err.message }); resolve({
success: false,
error: `Failed to get server status: ${err.message}`
});
} }
}); });
} }
private async getEditorVersion(): Promise<ToolResponse> { private async checkServerConnectivity(timeout: number = 5000): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise(async (resolve) => {
const startTime = Date.now();
try { try {
const version = { // Test basic Editor API connectivity
editor: (Editor as any).versions?.editor || 'Unknown', const testPromise = Editor.Message.request('server', 'query-port');
cocos: (Editor as any).versions?.cocos || 'Unknown', const timeoutPromise = new Promise((_, reject) => {
node: process.version setTimeout(() => reject(new Error('Connection timeout')), timeout);
}; });
await Promise.race([testPromise, timeoutPromise]);
const responseTime = Date.now() - startTime;
resolve({ resolve({
success: true, success: true,
data: { data: {
version: version, connected: true,
message: 'Editor version retrieved successfully' responseTime: responseTime,
timeout: timeout,
message: `Server connectivity confirmed in ${responseTime}ms`
} }
}); });
} catch (err: any) { } catch (err: any) {
resolve({ success: false, error: err.message }); const responseTime = Date.now() - startTime;
resolve({
success: false,
data: {
connected: false,
responseTime: responseTime,
timeout: timeout,
error: err.message
}
});
} }
}); });
} }
private async getProjectName(): Promise<ToolResponse> { private async getNetworkInterfaces(): Promise<ToolResponse> {
return new Promise((resolve) => { return new Promise(async (resolve) => {
try { try {
const name = Editor.Project.name; // Get network interfaces using Node.js os module
resolve({ const os = require('os');
success: true, const interfaces = os.networkInterfaces();
data: {
const networkInfo = Object.entries(interfaces).map(([name, addresses]: [string, any]) => ({
name: name, name: name,
message: 'Project name retrieved successfully' addresses: addresses.map((addr: any) => ({
} address: addr.address,
}); family: addr.family,
} catch (err: any) { internal: addr.internal,
resolve({ success: false, error: err.message }); cidr: addr.cidr
} }))
}); }));
}
// Also try to get server IPs for comparison
const serverIPResult = await this.queryServerIPList();
private async getProjectPath(): Promise<ToolResponse> {
return new Promise((resolve) => {
try {
const path = Editor.Project.path;
resolve({ resolve({
success: true, success: true,
data: { data: {
path: path, networkInterfaces: networkInfo,
message: 'Project path retrieved successfully' serverAvailableIPs: serverIPResult.success ? serverIPResult.data.ipList : [],
message: 'Network interfaces retrieved successfully'
} }
}); });
} catch (err: any) {
resolve({ success: false, error: err.message });
}
});
}
private async getProjectUuid(): Promise<ToolResponse> {
return new Promise((resolve) => {
try {
const uuid = Editor.Project.uuid;
resolve({
success: true,
data: {
uuid: uuid,
message: 'Project UUID retrieved successfully'
}
});
} catch (err: any) { } catch (err: any) {
resolve({ success: false, error: err.message });
}
});
}
private async restartEditor(): Promise<ToolResponse> {
return new Promise((resolve) => {
resolve({ resolve({
success: false, success: false,
error: 'Editor restart is not supported through MCP API', error: `Failed to get network interfaces: ${err.message}`
instruction: 'Please restart the editor manually or use the editor menu: File > Restart Editor'
});
}); });
} }
private async quitEditor(): Promise<ToolResponse> {
return new Promise((resolve) => {
resolve({
success: false,
error: 'Editor quit is not supported through MCP API',
instruction: 'Please quit the editor manually or use the editor menu: File > Quit'
});
}); });
} }
} }