This is page 3 of 5. Use http://codebase.md/daxianlee/cocos-mcp-server?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .gitignore
├── @types
│ └── schema
│ └── package
│ ├── base
│ │ └── panels.json
│ ├── contributions
│ │ └── index.json
│ └── index.json
├── base.tsconfig.json
├── dist
│ ├── examples
│ │ └── prefab-instantiation-example.js
│ ├── main.js
│ ├── mcp-server.js
│ ├── panels
│ │ ├── default
│ │ │ └── index.js
│ │ └── tool-manager
│ │ └── index.js
│ ├── scene.js
│ ├── settings.js
│ ├── test
│ │ ├── manual-test.js
│ │ ├── mcp-tool-tester.js
│ │ ├── prefab-tools-test.js
│ │ └── tool-tester.js
│ ├── tools
│ │ ├── asset-advanced-tools.js
│ │ ├── broadcast-tools.js
│ │ ├── component-tools.js
│ │ ├── debug-tools.js
│ │ ├── node-tools.js
│ │ ├── prefab-tools.js
│ │ ├── preferences-tools.js
│ │ ├── project-tools.js
│ │ ├── reference-image-tools.js
│ │ ├── scene-advanced-tools.js
│ │ ├── scene-tools.js
│ │ ├── scene-view-tools.js
│ │ ├── server-tools.js
│ │ ├── tool-manager.js
│ │ └── validation-tools.js
│ └── types
│ └── index.js
├── FEATURE_GUIDE_CN.md
├── FEATURE_GUIDE_EN.md
├── i18n
│ ├── en.js
│ └── zh.js
├── image
│ ├── iamge2.png
│ └── image-20250717174157957.png
├── package-lock.json
├── package.json
├── README.EN.md
├── README.md
├── scripts
│ └── preinstall.js
├── source
│ ├── main.ts
│ ├── mcp-server.ts
│ ├── panels
│ │ ├── default
│ │ │ └── index.ts
│ │ └── tool-manager
│ │ └── index.ts
│ ├── scene.ts
│ ├── settings.ts
│ ├── test
│ │ ├── manual-test.ts
│ │ ├── mcp-tool-tester.ts
│ │ ├── prefab-tools-test.ts
│ │ └── tool-tester.ts
│ ├── tools
│ │ ├── asset-advanced-tools.ts
│ │ ├── broadcast-tools.ts
│ │ ├── component-tools.ts
│ │ ├── debug-tools.ts
│ │ ├── node-tools.ts
│ │ ├── prefab-tools.ts
│ │ ├── preferences-tools.ts
│ │ ├── project-tools.ts
│ │ ├── reference-image-tools.ts
│ │ ├── scene-advanced-tools.ts
│ │ ├── scene-tools.ts
│ │ ├── scene-view-tools.ts
│ │ ├── server-tools.ts
│ │ ├── tool-manager.ts
│ │ └── validation-tools.ts
│ └── types
│ └── index.ts
├── static
│ ├── icon.png
│ ├── style
│ │ └── default
│ │ └── index.css
│ └── template
│ ├── default
│ │ ├── index.html
│ │ └── tool-manager.html
│ └── vue
│ └── mcp-server-app.html
├── TestScript.js
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/FEATURE_GUIDE_EN.md:
--------------------------------------------------------------------------------
```markdown
1 | # Cocos Creator MCP Server Feature Guide
2 |
3 | ## Overview
4 |
5 | The Cocos Creator MCP Server is a comprehensive Model Context Protocol (MCP) server plugin designed for Cocos Creator 3.8+, enabling AI assistants to interact with the Cocos Creator editor through standardized protocols.
6 |
7 | This document provides detailed information about all available MCP tools and their usage.
8 |
9 | ## Tool Categories
10 |
11 | The MCP server provides **158 tools** organized into 13 main categories by functionality:
12 |
13 | 1. [Scene Tools](#1-scene-tools)
14 | 2. [Node Tools](#2-node-tools)
15 | 3. [Component Management Tools](#3-component-management-tools)
16 | 4. [Prefab Tools](#4-prefab-tools)
17 | 5. [Project Control Tools](#5-project-control-tools)
18 | 6. [Debug Tools](#6-debug-tools)
19 | 7. [Preferences Tools](#7-preferences-tools)
20 | 8. [Server Tools](#8-server-tools)
21 | 9. [Broadcast Tools](#9-broadcast-tools)
22 | 10. [Asset Advanced Tools](#10-asset-advanced-tools)
23 | 11. [Reference Image Tools](#11-reference-image-tools)
24 | 12. [Scene Advanced Tools](#12-scene-advanced-tools)
25 | 13. [Scene View Tools](#13-scene-view-tools)
26 |
27 | ---
28 |
29 | ## 1. Scene Tools
30 |
31 | ### 1.1 scene_get_current_scene
32 | Get current scene information
33 |
34 | **Parameters**: None
35 |
36 | **Returns**: Current scene name, UUID, type, active status, and node count
37 |
38 | **Example**:
39 | ```json
40 | {
41 | "tool": "scene_get_current_scene",
42 | "arguments": {}
43 | }
44 | ```
45 |
46 | ### 1.2 scene_get_scene_list
47 | Get all scenes in the project
48 |
49 | **Parameters**: None
50 |
51 | **Returns**: List of all scenes in the project, including names, paths, and UUIDs
52 |
53 | **Example**:
54 | ```json
55 | {
56 | "tool": "scene_get_scene_list",
57 | "arguments": {}
58 | }
59 | ```
60 |
61 | ### 1.3 scene_open_scene
62 | Open a scene by path
63 |
64 | **Parameters**:
65 | - `scenePath` (string, required): Scene file path
66 |
67 | **Example**:
68 | ```json
69 | {
70 | "tool": "scene_open_scene",
71 | "arguments": {
72 | "scenePath": "db://assets/scenes/GameScene.scene"
73 | }
74 | }
75 | ```
76 |
77 | ### 1.4 scene_save_scene
78 | Save current scene
79 |
80 | **Parameters**: None
81 |
82 | **Example**:
83 | ```json
84 | {
85 | "tool": "scene_save_scene",
86 | "arguments": {}
87 | }
88 | ```
89 |
90 | ### 1.5 scene_create_scene
91 | Create a new scene asset
92 |
93 | **Parameters**:
94 | - `sceneName` (string, required): Name of the new scene
95 | - `savePath` (string, required): Path to save the scene
96 |
97 | **Example**:
98 | ```json
99 | {
100 | "tool": "scene_create_scene",
101 | "arguments": {
102 | "sceneName": "NewLevel",
103 | "savePath": "db://assets/scenes/NewLevel.scene"
104 | }
105 | }
106 | ```
107 |
108 | ### 1.6 scene_save_scene_as
109 | Save scene as a new file
110 |
111 | **Parameters**:
112 | - `path` (string, required): Path to save the scene
113 |
114 | **Example**:
115 | ```json
116 | {
117 | "tool": "scene_save_scene_as",
118 | "arguments": {
119 | "path": "db://assets/scenes/GameScene_Copy.scene"
120 | }
121 | }
122 | ```
123 |
124 | ### 1.7 scene_close_scene
125 | Close current scene
126 |
127 | **Parameters**: None
128 |
129 | **Example**:
130 | ```json
131 | {
132 | "tool": "scene_close_scene",
133 | "arguments": {}
134 | }
135 | ```
136 |
137 | ### 1.8 scene_get_scene_hierarchy
138 | Get the complete hierarchy of current scene
139 |
140 | **Parameters**:
141 | - `includeComponents` (boolean, optional): Whether to include component information, defaults to false
142 |
143 | **Example**:
144 | ```json
145 | {
146 | "tool": "scene_get_scene_hierarchy",
147 | "arguments": {
148 | "includeComponents": true
149 | }
150 | }
151 | ```
152 |
153 | ---
154 |
155 | ## 2. Node Tools
156 |
157 | ### 2.1 node_create_node
158 | Create a new node in the scene
159 |
160 | **Parameters**:
161 | - `name` (string, required): Node name
162 | - `parentUuid` (string, **strongly recommended**): Parent node UUID. **Important**: It is strongly recommended to always provide this parameter. Use `get_current_scene` or `get_all_nodes` to find parent node UUIDs. If not provided, the node will be created at the scene root.
163 | - `nodeType` (string, optional): Node type, options: `Node`, `2DNode`, `3DNode`, defaults to `Node`
164 | - `siblingIndex` (number, optional): Sibling index, -1 means append at end, defaults to -1
165 |
166 | **Important Note**: To ensure the node is created at the expected location, always provide the `parentUuid` parameter. You can obtain parent node UUIDs by:
167 | - Using `scene_get_current_scene` to get the scene root node UUID
168 | - Using `node_get_all_nodes` to view all nodes and their UUIDs
169 | - Using `node_find_node_by_name` to find specific node UUIDs
170 |
171 | **Example**:
172 | ```json
173 | {
174 | "tool": "node_create_node",
175 | "arguments": {
176 | "name": "PlayerNode",
177 | "nodeType": "2DNode",
178 | "parentUuid": "parent-uuid-here"
179 | }
180 | }
181 | ```
182 |
183 | ### 2.2 node_get_node_info
184 | Get node information by UUID
185 |
186 | **Parameters**:
187 | - `uuid` (string, required): Node UUID
188 |
189 | **Example**:
190 | ```json
191 | {
192 | "tool": "node_get_node_info",
193 | "arguments": {
194 | "uuid": "node-uuid-here"
195 | }
196 | }
197 | ```
198 |
199 | ### 2.3 node_find_nodes
200 | Find nodes by name pattern
201 |
202 | **Parameters**:
203 | - `pattern` (string, required): Name pattern to search
204 | - `exactMatch` (boolean, optional): Whether to match exactly, defaults to false
205 |
206 | **Example**:
207 | ```json
208 | {
209 | "tool": "node_find_nodes",
210 | "arguments": {
211 | "pattern": "Enemy",
212 | "exactMatch": false
213 | }
214 | }
215 | ```
216 |
217 | ### 2.4 node_find_node_by_name
218 | Find the first node by exact name
219 |
220 | **Parameters**:
221 | - `name` (string, required): Node name to find
222 |
223 | **Example**:
224 | ```json
225 | {
226 | "tool": "node_find_node_by_name",
227 | "arguments": {
228 | "name": "Player"
229 | }
230 | }
231 | ```
232 |
233 | ### 2.5 node_get_all_nodes
234 | Get all nodes in the scene with their UUIDs
235 |
236 | **Parameters**: None
237 |
238 | **Example**:
239 | ```json
240 | {
241 | "tool": "node_get_all_nodes",
242 | "arguments": {}
243 | }
244 | ```
245 |
246 | ### 2.6 node_set_node_property
247 | Set node property value
248 |
249 | **Parameters**:
250 | - `uuid` (string, required): Node UUID
251 | - `property` (string, required): Property name (e.g., position, rotation, scale, active)
252 | - `value` (any, required): Property value
253 |
254 | **Example**:
255 | ```json
256 | {
257 | "tool": "node_set_node_property",
258 | "arguments": {
259 | "uuid": "node-uuid-here",
260 | "property": "position",
261 | "value": {"x": 100, "y": 200, "z": 0}
262 | }
263 | }
264 | ```
265 |
266 | ### 2.7 node_delete_node
267 | Delete a node from the scene
268 |
269 | **Parameters**:
270 | - `uuid` (string, required): UUID of the node to delete
271 |
272 | **Example**:
273 | ```json
274 | {
275 | "tool": "node_delete_node",
276 | "arguments": {
277 | "uuid": "node-uuid-here"
278 | }
279 | }
280 | ```
281 |
282 | ### 2.8 node_move_node
283 | Move a node to a new parent
284 |
285 | **Parameters**:
286 | - `nodeUuid` (string, required): UUID of the node to move
287 | - `newParentUuid` (string, required): New parent node UUID
288 | - `siblingIndex` (number, optional): Sibling index in the new parent, defaults to -1
289 |
290 | **Example**:
291 | ```json
292 | {
293 | "tool": "node_move_node",
294 | "arguments": {
295 | "nodeUuid": "node-uuid-here",
296 | "newParentUuid": "parent-uuid-here",
297 | "siblingIndex": 0
298 | }
299 | }
300 | ```
301 |
302 | ### 2.9 node_duplicate_node
303 | Duplicate a node
304 |
305 | **Parameters**:
306 | - `uuid` (string, required): UUID of the node to duplicate
307 | - `includeChildren` (boolean, optional): Whether to include child nodes, defaults to true
308 |
309 | **Example**:
310 | ```json
311 | {
312 | "tool": "node_duplicate_node",
313 | "arguments": {
314 | "uuid": "node-uuid-here",
315 | "includeChildren": true
316 | }
317 | }
318 | ```
319 |
320 | ---
321 |
322 | ## 3. Component Management Tools
323 |
324 | ### 3.1 component_add_component
325 | Add a component to a specific node
326 |
327 | **Parameters**:
328 | - `nodeUuid` (string, **required**): Target node UUID. **Important**: 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.
329 | - `componentType` (string, required): Component type (e.g., cc.Sprite, cc.Label, cc.Button)
330 |
331 | **Important Note**: Before adding a component, ensure:
332 | 1. First use `node_get_all_nodes` or `node_find_node_by_name` to find the target node's UUID
333 | 2. Verify the node exists and the UUID is correct
334 | 3. Choose the appropriate component type
335 |
336 | **Example**:
337 | ```json
338 | {
339 | "tool": "component_add_component",
340 | "arguments": {
341 | "nodeUuid": "node-uuid-here",
342 | "componentType": "cc.Sprite"
343 | }
344 | }
345 | ```
346 |
347 | ### 3.2 component_remove_component
348 | Remove a component from a node
349 |
350 | **Parameters**:
351 | - `nodeUuid` (string, required): Node UUID
352 | - `componentType` (string, required): Component type to remove
353 |
354 | **Example**:
355 | ```json
356 | {
357 | "tool": "component_remove_component",
358 | "arguments": {
359 | "nodeUuid": "node-uuid-here",
360 | "componentType": "cc.Sprite"
361 | }
362 | }
363 | ```
364 |
365 | ### 3.3 component_get_components
366 | Get all components of a node
367 |
368 | **Parameters**:
369 | - `nodeUuid` (string, required): Node UUID
370 |
371 | **Example**:
372 | ```json
373 | {
374 | "tool": "component_get_components",
375 | "arguments": {
376 | "nodeUuid": "node-uuid-here"
377 | }
378 | }
379 | ```
380 |
381 | ### 3.4 component_get_component_info
382 | Get specific component information
383 |
384 | **Parameters**:
385 | - `nodeUuid` (string, required): Node UUID
386 | - `componentType` (string, required): Component type to get info for
387 |
388 | **Example**:
389 | ```json
390 | {
391 | "tool": "component_get_component_info",
392 | "arguments": {
393 | "nodeUuid": "node-uuid-here",
394 | "componentType": "cc.Sprite"
395 | }
396 | }
397 | ```
398 |
399 | ### 3.5 component_set_component_property
400 | Set component property value
401 |
402 | **Parameters**:
403 | - `nodeUuid` (string, required): Node UUID
404 | - `componentType` (string, required): Component type
405 | - `property` (string, required): Property name
406 | - `value` (any, required): Property value
407 |
408 | **Example**:
409 | ```json
410 | {
411 | "tool": "component_set_component_property",
412 | "arguments": {
413 | "nodeUuid": "node-uuid-here",
414 | "componentType": "cc.Sprite",
415 | "property": "spriteFrame",
416 | "value": "sprite-frame-uuid"
417 | }
418 | }
419 | ```
420 |
421 | ### 3.6 component_attach_script
422 | Attach a script component to a node
423 |
424 | **Parameters**:
425 | - `nodeUuid` (string, required): Node UUID
426 | - `scriptPath` (string, required): Script asset path
427 |
428 | **Example**:
429 | ```json
430 | {
431 | "tool": "component_attach_script",
432 | "arguments": {
433 | "nodeUuid": "node-uuid-here",
434 | "scriptPath": "db://assets/scripts/PlayerController.ts"
435 | }
436 | }
437 | ```
438 |
439 | ### 3.7 component_get_available_components
440 | Get list of available component types
441 |
442 | **Parameters**:
443 | - `category` (string, optional): Component category filter, options: `all`, `renderer`, `ui`, `physics`, `animation`, `audio`, defaults to `all`
444 |
445 | **Example**:
446 | ```json
447 | {
448 | "tool": "component_get_available_components",
449 | "arguments": {
450 | "category": "ui"
451 | }
452 | }
453 | ```
454 |
455 | ---
456 |
457 | ## 4. Prefab Tools
458 |
459 | **⚠️ Known Issue**: When using standard Cocos Creator API for prefab instantiation, complex prefabs with child nodes may not be properly restored. While prefab creation functionality can correctly save all child node information, the instantiation process through `create-node` with `assetUuid` has limitations that may result in missing child nodes in the instantiated prefab.
460 |
461 | ### 4.1 prefab_get_prefab_list
462 | Get all prefabs in the project
463 |
464 | **Parameters**:
465 | - `folder` (string, optional): Search folder path, defaults to `db://assets`
466 |
467 | **Example**:
468 | ```json
469 | {
470 | "tool": "prefab_get_prefab_list",
471 | "arguments": {
472 | "folder": "db://assets/prefabs"
473 | }
474 | }
475 | ```
476 |
477 | ### 4.2 prefab_load_prefab
478 | Load a prefab by path
479 |
480 | **Parameters**:
481 | - `prefabPath` (string, required): Prefab asset path
482 |
483 | **Example**:
484 | ```json
485 | {
486 | "tool": "prefab_load_prefab",
487 | "arguments": {
488 | "prefabPath": "db://assets/prefabs/Enemy.prefab"
489 | }
490 | }
491 | ```
492 |
493 | ### 4.3 prefab_instantiate_prefab
494 | Instantiate a prefab in the scene
495 |
496 | **Parameters**:
497 | - `prefabPath` (string, required): Prefab asset path
498 | - `parentUuid` (string, optional): Parent node UUID
499 | - `position` (object, optional): Initial position with x, y, z properties
500 |
501 | **Example**:
502 | ```json
503 | {
504 | "tool": "prefab_instantiate_prefab",
505 | "arguments": {
506 | "prefabPath": "db://assets/prefabs/Enemy.prefab",
507 | "parentUuid": "parent-uuid-here",
508 | "position": {"x": 100, "y": 200, "z": 0}
509 | }
510 | }
511 | ```
512 |
513 | **⚠️ Functionality Limitation**: Complex prefabs with child nodes may not instantiate correctly. Due to Cocos Creator API limitations in the standard `create-node` method using `assetUuid`, only the root node may be created, and child nodes may be lost. This is a known issue with the current implementation.
514 |
515 | ### 4.4 prefab_create_prefab
516 | Create a prefab from a node
517 |
518 | **Parameters**:
519 | - `nodeUuid` (string, required): Source node UUID
520 | - `savePath` (string, required): Path to save the prefab
521 | - `prefabName` (string, required): Prefab name
522 |
523 | **Example**:
524 | ```json
525 | {
526 | "tool": "prefab_create_prefab",
527 | "arguments": {
528 | "nodeUuid": "node-uuid-here",
529 | "savePath": "db://assets/prefabs/",
530 | "prefabName": "MyPrefab"
531 | }
532 | }
533 | ```
534 |
535 | ### 4.5 prefab_create_prefab_from_node
536 | Create a prefab from a node (alias for create_prefab)
537 |
538 | **Parameters**:
539 | - `nodeUuid` (string, required): Source node UUID
540 | - `prefabPath` (string, required): Path to save the prefab
541 |
542 | **Example**:
543 | ```json
544 | {
545 | "tool": "prefab_create_prefab_from_node",
546 | "arguments": {
547 | "nodeUuid": "node-uuid-here",
548 | "prefabPath": "db://assets/prefabs/MyPrefab.prefab"
549 | }
550 | }
551 | ```
552 |
553 | ### 4.6 prefab_update_prefab
554 | Update an existing prefab
555 |
556 | **Parameters**:
557 | - `prefabPath` (string, required): Prefab asset path
558 | - `nodeUuid` (string, required): Node UUID containing changes
559 |
560 | **Example**:
561 | ```json
562 | {
563 | "tool": "prefab_update_prefab",
564 | "arguments": {
565 | "prefabPath": "db://assets/prefabs/Enemy.prefab",
566 | "nodeUuid": "node-uuid-here"
567 | }
568 | }
569 | ```
570 |
571 | ### 4.7 prefab_revert_prefab
572 | Revert a prefab instance to its original state
573 |
574 | **Parameters**:
575 | - `nodeUuid` (string, required): Prefab instance node UUID
576 |
577 | **Example**:
578 | ```json
579 | {
580 | "tool": "prefab_revert_prefab",
581 | "arguments": {
582 | "nodeUuid": "prefab-instance-uuid-here"
583 | }
584 | }
585 | ```
586 |
587 | ### 4.8 prefab_get_prefab_info
588 | Get detailed prefab information
589 |
590 | **Parameters**:
591 | - `prefabPath` (string, required): Prefab asset path
592 |
593 | **Example**:
594 | ```json
595 | {
596 | "tool": "prefab_get_prefab_info",
597 | "arguments": {
598 | "prefabPath": "db://assets/prefabs/Enemy.prefab"
599 | }
600 | }
601 | ```
602 |
603 | ---
604 |
605 | ## 5. Project Control Tools
606 |
607 | ### 5.1 project_run_project
608 | Run the project in preview mode
609 |
610 | **Parameters**:
611 | - `platform` (string, optional): Target platform, options: `browser`, `simulator`, `preview`, defaults to `browser`
612 |
613 | **Example**:
614 | ```json
615 | {
616 | "tool": "project_run_project",
617 | "arguments": {
618 | "platform": "browser"
619 | }
620 | }
621 | ```
622 |
623 | ### 5.2 project_build_project
624 | Build the project
625 |
626 | **Parameters**:
627 | - `platform` (string, required): Build platform, options: `web-mobile`, `web-desktop`, `ios`, `android`, `windows`, `mac`
628 | - `debug` (boolean, optional): Whether to build in debug mode, defaults to true
629 |
630 | **Example**:
631 | ```json
632 | {
633 | "tool": "project_build_project",
634 | "arguments": {
635 | "platform": "web-mobile",
636 | "debug": false
637 | }
638 | }
639 | ```
640 |
641 | ### 5.3 project_get_project_info
642 | Get project information
643 |
644 | **Parameters**: None
645 |
646 | **Example**:
647 | ```json
648 | {
649 | "tool": "project_get_project_info",
650 | "arguments": {}
651 | }
652 | ```
653 |
654 | ### 5.4 project_get_project_settings
655 | Get project settings
656 |
657 | **Parameters**:
658 | - `category` (string, optional): Settings category, options: `general`, `physics`, `render`, `assets`, defaults to `general`
659 |
660 | **Example**:
661 | ```json
662 | {
663 | "tool": "project_get_project_settings",
664 | "arguments": {
665 | "category": "physics"
666 | }
667 | }
668 | ```
669 |
670 | ### 5.5 project_refresh_assets
671 | Refresh the asset database
672 |
673 | **Parameters**:
674 | - `folder` (string, optional): Specific folder to refresh
675 |
676 | **Example**:
677 | ```json
678 | {
679 | "tool": "project_refresh_assets",
680 | "arguments": {
681 | "folder": "db://assets/textures"
682 | }
683 | }
684 | ```
685 |
686 | ### 5.6 project_import_asset
687 | Import an asset file
688 |
689 | **Parameters**:
690 | - `sourcePath` (string, required): Source file path
691 | - `targetFolder` (string, required): Target folder in assets
692 |
693 | **Example**:
694 | ```json
695 | {
696 | "tool": "project_import_asset",
697 | "arguments": {
698 | "sourcePath": "/path/to/image.png",
699 | "targetFolder": "db://assets/textures"
700 | }
701 | }
702 | ```
703 |
704 | ### 5.7 project_get_asset_info
705 | Get asset information
706 |
707 | **Parameters**:
708 | - `assetPath` (string, required): Asset path
709 |
710 | **Example**:
711 | ```json
712 | {
713 | "tool": "project_get_asset_info",
714 | "arguments": {
715 | "assetPath": "db://assets/textures/player.png"
716 | }
717 | }
718 | ```
719 |
720 | ### 5.8 project_get_assets
721 | Get assets by type
722 |
723 | **Parameters**:
724 | - `type` (string, optional): Asset type filter, options: `all`, `scene`, `prefab`, `script`, `texture`, `material`, `mesh`, `audio`, `animation`, defaults to `all`
725 | - `folder` (string, optional): Search folder, defaults to `db://assets`
726 |
727 | **Example**:
728 | ```json
729 | {
730 | "tool": "project_get_assets",
731 | "arguments": {
732 | "type": "texture",
733 | "folder": "db://assets/textures"
734 | }
735 | }
736 | ```
737 |
738 | ### 5.9 project_get_build_settings
739 | Get build settings
740 |
741 | **Parameters**: None
742 |
743 | **Example**:
744 | ```json
745 | {
746 | "tool": "project_get_build_settings",
747 | "arguments": {}
748 | }
749 | ```
750 |
751 | ### 5.10 project_open_build_panel
752 | Open the build panel in the editor
753 |
754 | **Parameters**: None
755 |
756 | **Example**:
757 | ```json
758 | {
759 | "tool": "project_open_build_panel",
760 | "arguments": {}
761 | }
762 | ```
763 |
764 | ### 5.11 project_check_builder_status
765 | Check if the builder worker process is ready
766 |
767 | **Parameters**: None
768 |
769 | **Example**:
770 | ```json
771 | {
772 | "tool": "project_check_builder_status",
773 | "arguments": {}
774 | }
775 | ```
776 |
777 | ### 5.12 project_start_preview_server
778 | Start the preview server
779 |
780 | **Parameters**:
781 | - `port` (number, optional): Preview server port, defaults to 7456
782 |
783 | **Example**:
784 | ```json
785 | {
786 | "tool": "project_start_preview_server",
787 | "arguments": {
788 | "port": 8080
789 | }
790 | }
791 | ```
792 |
793 | ### 5.13 project_stop_preview_server
794 | Stop the preview server
795 |
796 | **Parameters**: None
797 |
798 | **Example**:
799 | ```json
800 | {
801 | "tool": "project_stop_preview_server",
802 | "arguments": {}
803 | }
804 | ```
805 |
806 | ### 5.14 project_create_asset
807 | Create a new asset file or folder
808 |
809 | **Parameters**:
810 | - `url` (string, required): Asset URL
811 | - `content` (string, optional): File content, null means create folder
812 | - `overwrite` (boolean, optional): Whether to overwrite existing file, defaults to false
813 |
814 | **Example**:
815 | ```json
816 | {
817 | "tool": "project_create_asset",
818 | "arguments": {
819 | "url": "db://assets/scripts/NewScript.ts",
820 | "content": "// New TypeScript script\n",
821 | "overwrite": false
822 | }
823 | }
824 | ```
825 |
826 | ### 5.15 project_copy_asset
827 | Copy an asset to another location
828 |
829 | **Parameters**:
830 | - `source` (string, required): Source asset URL
831 | - `target` (string, required): Target location URL
832 | - `overwrite` (boolean, optional): Whether to overwrite existing file, defaults to false
833 |
834 | **Example**:
835 | ```json
836 | {
837 | "tool": "project_copy_asset",
838 | "arguments": {
839 | "source": "db://assets/textures/player.png",
840 | "target": "db://assets/textures/backup/player.png",
841 | "overwrite": false
842 | }
843 | }
844 | ```
845 |
846 | ### 5.16 project_move_asset
847 | Move an asset to another location
848 |
849 | **Parameters**:
850 | - `source` (string, required): Source asset URL
851 | - `target` (string, required): Target location URL
852 | - `overwrite` (boolean, optional): Whether to overwrite existing file, defaults to false
853 |
854 | **Example**:
855 | ```json
856 | {
857 | "tool": "project_move_asset",
858 | "arguments": {
859 | "source": "db://assets/textures/old_player.png",
860 | "target": "db://assets/textures/player.png",
861 | "overwrite": true
862 | }
863 | }
864 | ```
865 |
866 | ### 5.17 project_delete_asset
867 | Delete an asset
868 |
869 | **Parameters**:
870 | - `url` (string, required): Asset URL to delete
871 |
872 | **Example**:
873 | ```json
874 | {
875 | "tool": "project_delete_asset",
876 | "arguments": {
877 | "url": "db://assets/textures/unused.png"
878 | }
879 | }
880 | ```
881 |
882 | ### 5.18 project_save_asset
883 | Save asset content
884 |
885 | **Parameters**:
886 | - `url` (string, required): Asset URL
887 | - `content` (string, required): Asset content
888 |
889 | **Example**:
890 | ```json
891 | {
892 | "tool": "project_save_asset",
893 | "arguments": {
894 | "url": "db://assets/scripts/GameManager.ts",
895 | "content": "// Updated script content\n"
896 | }
897 | }
898 | ```
899 |
900 | ### 5.19 project_reimport_asset
901 | Reimport an asset
902 |
903 | **Parameters**:
904 | - `url` (string, required): Asset URL to reimport
905 |
906 | **Example**:
907 | ```json
908 | {
909 | "tool": "project_reimport_asset",
910 | "arguments": {
911 | "url": "db://assets/textures/player.png"
912 | }
913 | }
914 | ```
915 |
916 | ### 5.20 project_query_asset_path
917 | Get asset disk path
918 |
919 | **Parameters**:
920 | - `url` (string, required): Asset URL
921 |
922 | **Example**:
923 | ```json
924 | {
925 | "tool": "project_query_asset_path",
926 | "arguments": {
927 | "url": "db://assets/textures/player.png"
928 | }
929 | }
930 | ```
931 |
932 | ### 5.21 project_query_asset_uuid
933 | Get asset UUID from URL
934 |
935 | **Parameters**:
936 | - `url` (string, required): Asset URL
937 |
938 | **Example**:
939 | ```json
940 | {
941 | "tool": "project_query_asset_uuid",
942 | "arguments": {
943 | "url": "db://assets/textures/player.png"
944 | }
945 | }
946 | ```
947 |
948 | ### 5.22 project_query_asset_url
949 | Get asset URL from UUID
950 |
951 | **Parameters**:
952 | - `uuid` (string, required): Asset UUID
953 |
954 | **Example**:
955 | ```json
956 | {
957 | "tool": "project_query_asset_url",
958 | "arguments": {
959 | "uuid": "asset-uuid-here"
960 | }
961 | }
962 | ```
963 |
964 | ---
965 |
966 | ## 6. Debug Tools
967 |
968 | ### 6.1 debug_get_console_logs
969 | Get editor console logs
970 |
971 | **Parameters**:
972 | - `limit` (number, optional): Number of latest logs to retrieve, defaults to 100
973 | - `filter` (string, optional): Filter logs by type, options: `all`, `log`, `warn`, `error`, `info`, defaults to `all`
974 |
975 | **Example**:
976 | ```json
977 | {
978 | "tool": "debug_get_console_logs",
979 | "arguments": {
980 | "limit": 50,
981 | "filter": "error"
982 | }
983 | }
984 | ```
985 |
986 | ### 6.2 debug_clear_console
987 | Clear the editor console
988 |
989 | **Parameters**: None
990 |
991 | **Example**:
992 | ```json
993 | {
994 | "tool": "debug_clear_console",
995 | "arguments": {}
996 | }
997 | ```
998 |
999 | ### 6.3 debug_execute_script
1000 | Execute JavaScript code in scene context
1001 |
1002 | **Parameters**:
1003 | - `script` (string, required): JavaScript code to execute
1004 |
1005 | **Example**:
1006 | ```json
1007 | {
1008 | "tool": "debug_execute_script",
1009 | "arguments": {
1010 | "script": "console.log('Hello from MCP!');"
1011 | }
1012 | }
1013 | ```
1014 |
1015 | ### 6.4 debug_get_node_tree
1016 | Get detailed node tree for debugging
1017 |
1018 | **Parameters**:
1019 | - `rootUuid` (string, optional): Root node UUID, if not provided uses scene root node
1020 | - `maxDepth` (number, optional): Maximum tree depth, defaults to 10
1021 |
1022 | **Example**:
1023 | ```json
1024 | {
1025 | "tool": "debug_get_node_tree",
1026 | "arguments": {
1027 | "rootUuid": "root-node-uuid",
1028 | "maxDepth": 5
1029 | }
1030 | }
1031 | ```
1032 |
1033 | ### 6.5 debug_get_performance_stats
1034 | Get performance statistics
1035 |
1036 | **Parameters**: None
1037 |
1038 | **Example**:
1039 | ```json
1040 | {
1041 | "tool": "debug_get_performance_stats",
1042 | "arguments": {}
1043 | }
1044 | ```
1045 |
1046 | ### 6.6 debug_validate_scene
1047 | Validate if the current scene has issues
1048 |
1049 | **Parameters**:
1050 | - `checkMissingAssets` (boolean, optional): Check for missing asset references, defaults to true
1051 | - `checkPerformance` (boolean, optional): Check for performance issues, defaults to true
1052 |
1053 | **Example**:
1054 | ```json
1055 | {
1056 | "tool": "debug_validate_scene",
1057 | "arguments": {
1058 | "checkMissingAssets": true,
1059 | "checkPerformance": true
1060 | }
1061 | }
1062 | ```
1063 |
1064 | ### 6.7 debug_get_editor_info
1065 | Get editor and environment information
1066 |
1067 | **Parameters**: None
1068 |
1069 | **Example**:
1070 | ```json
1071 | {
1072 | "tool": "debug_get_editor_info",
1073 | "arguments": {}
1074 | }
1075 | ```
1076 |
1077 | ### 6.8 debug_get_project_logs
1078 | Get project logs from temp/logs/project.log file
1079 |
1080 | **Parameters**:
1081 | - `lines` (number, optional): Number of lines to read from the end of the log file, default is 100, range: 1-10000
1082 | - `filterKeyword` (string, optional): Filter logs by specific keyword
1083 | - `logLevel` (string, optional): Filter by log level, options: `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE`, `ALL`, defaults to `ALL`
1084 |
1085 | **Example**:
1086 | ```json
1087 | {
1088 | "tool": "debug_get_project_logs",
1089 | "arguments": {
1090 | "lines": 200,
1091 | "filterKeyword": "prefab",
1092 | "logLevel": "INFO"
1093 | }
1094 | }
1095 | ```
1096 |
1097 | ### 6.9 debug_get_log_file_info
1098 | Get project log file information
1099 |
1100 | **Parameters**: None
1101 |
1102 | **Returns**: File size, last modified time, line count, and file path information
1103 |
1104 | **Example**:
1105 | ```json
1106 | {
1107 | "tool": "debug_get_log_file_info",
1108 | "arguments": {}
1109 | }
1110 | ```
1111 |
1112 | ### 6.10 debug_search_project_logs
1113 | Search for specific patterns or errors in project logs
1114 |
1115 | **Parameters**:
1116 | - `pattern` (string, required): Search pattern (supports regex)
1117 | - `maxResults` (number, optional): Maximum number of matching results, defaults to 20, range: 1-100
1118 | - `contextLines` (number, optional): Number of context lines to show around each match, defaults to 2, range: 0-10
1119 |
1120 | **Example**:
1121 | ```json
1122 | {
1123 | "tool": "debug_search_project_logs",
1124 | "arguments": {
1125 | "pattern": "error|failed|exception",
1126 | "maxResults": 10,
1127 | "contextLines": 3
1128 | }
1129 | }
1130 | ```
1131 |
1132 | ---
1133 |
1134 | ## 7. Preferences Tools
1135 |
1136 | ### 7.1 preferences_get_preferences
1137 | Get editor preferences
1138 |
1139 | **Parameters**:
1140 | - `key` (string, optional): Specific preference key to get
1141 |
1142 | **Example**:
1143 | ```json
1144 | {
1145 | "tool": "preferences_get_preferences",
1146 | "arguments": {
1147 | "key": "editor.theme"
1148 | }
1149 | }
1150 | ```
1151 |
1152 | ### 7.2 preferences_set_preferences
1153 | Set editor preferences
1154 |
1155 | **Parameters**:
1156 | - `key` (string, required): Preference key to set
1157 | - `value` (any, required): Preference value to set
1158 |
1159 | **Example**:
1160 | ```json
1161 | {
1162 | "tool": "preferences_set_preferences",
1163 | "arguments": {
1164 | "key": "editor.theme",
1165 | "value": "dark"
1166 | }
1167 | }
1168 | ```
1169 |
1170 | ### 7.3 preferences_get_global_preferences
1171 | Get global editor preferences
1172 |
1173 | **Parameters**:
1174 | - `key` (string, optional): Global preference key to get
1175 |
1176 | **Example**:
1177 | ```json
1178 | {
1179 | "tool": "preferences_get_global_preferences",
1180 | "arguments": {
1181 | "key": "global.autoSave"
1182 | }
1183 | }
1184 | ```
1185 |
1186 | ### 7.4 preferences_set_global_preferences
1187 | Set global editor preferences
1188 |
1189 | **Parameters**:
1190 | - `key` (string, required): Global preference key to set
1191 | - `value` (any, required): Global preference value to set
1192 |
1193 | **Example**:
1194 | ```json
1195 | {
1196 | "tool": "preferences_set_global_preferences",
1197 | "arguments": {
1198 | "key": "global.autoSave",
1199 | "value": true
1200 | }
1201 | }
1202 | ```
1203 |
1204 | ### 7.5 preferences_get_recent_projects
1205 | Get recently opened projects
1206 |
1207 | **Parameters**: None
1208 |
1209 | **Example**:
1210 | ```json
1211 | {
1212 | "tool": "preferences_get_recent_projects",
1213 | "arguments": {}
1214 | }
1215 | ```
1216 |
1217 | ### 7.6 preferences_clear_recent_projects
1218 | Clear the list of recently opened projects
1219 |
1220 | **Parameters**: None
1221 |
1222 | **Example**:
1223 | ```json
1224 | {
1225 | "tool": "preferences_clear_recent_projects",
1226 | "arguments": {}
1227 | }
1228 | ```
1229 |
1230 | ---
1231 |
1232 | ## 8. Server Tools
1233 |
1234 | ### 8.1 server_get_server_info
1235 | Get server information
1236 |
1237 | **Parameters**: None
1238 |
1239 | **Example**:
1240 | ```json
1241 | {
1242 | "tool": "server_get_server_info",
1243 | "arguments": {}
1244 | }
1245 | ```
1246 |
1247 | ### 8.2 server_broadcast_custom_message
1248 | Broadcast a custom message
1249 |
1250 | **Parameters**:
1251 | - `message` (string, required): Message name
1252 | - `data` (any, optional): Message data
1253 |
1254 | **Example**:
1255 | ```json
1256 | {
1257 | "tool": "server_broadcast_custom_message",
1258 | "arguments": {
1259 | "message": "custom_event",
1260 | "data": {"type": "test", "value": 123}
1261 | }
1262 | }
1263 | ```
1264 |
1265 | ### 8.3 server_get_editor_version
1266 | Get editor version information
1267 |
1268 | **Parameters**: None
1269 |
1270 | **Example**:
1271 | ```json
1272 | {
1273 | "tool": "server_get_editor_version",
1274 | "arguments": {}
1275 | }
1276 | ```
1277 |
1278 | ### 8.4 server_get_project_name
1279 | Get current project name
1280 |
1281 | **Parameters**: None
1282 |
1283 | **Example**:
1284 | ```json
1285 | {
1286 | "tool": "server_get_project_name",
1287 | "arguments": {}
1288 | }
1289 | ```
1290 |
1291 | ### 8.5 server_get_project_path
1292 | Get current project path
1293 |
1294 | **Parameters**: None
1295 |
1296 | **Example**:
1297 | ```json
1298 | {
1299 | "tool": "server_get_project_path",
1300 | "arguments": {}
1301 | }
1302 | ```
1303 |
1304 | ### 8.6 server_get_project_uuid
1305 | Get current project UUID
1306 |
1307 | **Parameters**: None
1308 |
1309 | **Example**:
1310 | ```json
1311 | {
1312 | "tool": "server_get_project_uuid",
1313 | "arguments": {}
1314 | }
1315 | ```
1316 |
1317 | ### 8.7 server_restart_editor
1318 | Request to restart the editor
1319 |
1320 | **Parameters**: None
1321 |
1322 | **Example**:
1323 | ```json
1324 | {
1325 | "tool": "server_restart_editor",
1326 | "arguments": {}
1327 | }
1328 | ```
1329 |
1330 | ### 8.8 server_quit_editor
1331 | Request to quit the editor
1332 |
1333 | **Parameters**: None
1334 |
1335 | **Example**:
1336 | ```json
1337 | {
1338 | "tool": "server_quit_editor",
1339 | "arguments": {}
1340 | }
1341 | ```
1342 |
1343 | ---
1344 |
1345 | ## 9. Broadcast Tools
1346 |
1347 | ### 9.1 broadcast_get_broadcast_log
1348 | Get recent broadcast message log
1349 |
1350 | **Parameters**:
1351 | - `limit` (number, optional): Number of latest messages to return, defaults to 50
1352 | - `messageType` (string, optional): Filter by message type
1353 |
1354 | **Example**:
1355 | ```json
1356 | {
1357 | "tool": "broadcast_get_broadcast_log",
1358 | "arguments": {
1359 | "limit": 100,
1360 | "messageType": "scene_change"
1361 | }
1362 | }
1363 | ```
1364 |
1365 | ### 9.2 broadcast_listen_broadcast
1366 | Start listening for specific broadcast messages
1367 |
1368 | **Parameters**:
1369 | - `messageType` (string, required): Message type to listen for
1370 |
1371 | **Example**:
1372 | ```json
1373 | {
1374 | "tool": "broadcast_listen_broadcast",
1375 | "arguments": {
1376 | "messageType": "node_created"
1377 | }
1378 | }
1379 | ```
1380 |
1381 | ### 9.3 broadcast_stop_listening
1382 | Stop listening for specific broadcast messages
1383 |
1384 | **Parameters**:
1385 | - `messageType` (string, required): Message type to stop listening for
1386 |
1387 | **Example**:
1388 | ```json
1389 | {
1390 | "tool": "broadcast_stop_listening",
1391 | "arguments": {
1392 | "messageType": "node_created"
1393 | }
1394 | }
1395 | ```
1396 |
1397 | ### 9.4 broadcast_clear_broadcast_log
1398 | Clear broadcast message log
1399 |
1400 | **Parameters**: None
1401 |
1402 | **Example**:
1403 | ```json
1404 | {
1405 | "tool": "broadcast_clear_broadcast_log",
1406 | "arguments": {}
1407 | }
1408 | ```
1409 |
1410 | ### 9.5 broadcast_get_active_listeners
1411 | Get list of active broadcast listeners
1412 |
1413 | **Parameters**: None
1414 |
1415 | **Example**:
1416 | ```json
1417 | {
1418 | "tool": "broadcast_get_active_listeners",
1419 | "arguments": {}
1420 | }
1421 | ```
1422 |
1423 | ---
1424 |
1425 | ## Usage Guidelines
1426 |
1427 | ### 1. Tool Call Format
1428 |
1429 | All tool calls use JSON-RPC 2.0 format:
1430 |
1431 | ```json
1432 | {
1433 | "jsonrpc": "2.0",
1434 | "method": "tools/call",
1435 | "params": {
1436 | "name": "tool_name",
1437 | "arguments": {
1438 | // Tool parameters
1439 | }
1440 | },
1441 | "id": 1
1442 | }
1443 | ```
1444 |
1445 | ### 2. Common UUID Retrieval Methods
1446 |
1447 | - Use `node_get_all_nodes` to get all node UUIDs
1448 | - Use `node_find_node_by_name` to find node UUIDs by name
1449 | - Use `scene_get_current_scene` to get scene UUID
1450 | - Use `prefab_get_prefab_list` to get prefab information
1451 |
1452 | ### 3. Asset Path Format
1453 |
1454 | Cocos Creator uses `db://` prefixed asset URL format:
1455 | - Scenes: `db://assets/scenes/GameScene.scene`
1456 | - Prefabs: `db://assets/prefabs/Player.prefab`
1457 | - Scripts: `db://assets/scripts/GameManager.ts`
1458 | - Textures: `db://assets/textures/player.png`
1459 |
1460 | ### 4. Error Handling
1461 |
1462 | If a tool call fails, an error message will be returned:
1463 |
1464 | ```json
1465 | {
1466 | "jsonrpc": "2.0",
1467 | "id": 1,
1468 | "error": {
1469 | "code": -32000,
1470 | "message": "Tool execution failed",
1471 | "data": {
1472 | "error": "Detailed error message"
1473 | }
1474 | }
1475 | }
1476 | ```
1477 |
1478 | ### 5. Best Practices
1479 |
1480 | 1. **Query First, Then Operate**: Before modifying nodes or components, first use query tools to get current state
1481 | 2. **Use UUIDs**: Prefer using UUIDs over names when referencing nodes and assets
1482 | 3. **Error Checking**: Always check the return value of tool calls to ensure operations succeed
1483 | 4. **Asset Management**: Before deleting or moving assets, ensure they are not referenced elsewhere
1484 | 5. **Performance Considerations**: Avoid frequent tool calls in loops, consider batch operations
1485 |
1486 | ---
1487 |
1488 | ## Technical Support
1489 |
1490 | If you encounter issues during use, you can:
1491 |
1492 | 1. Use `debug_get_console_logs` to view detailed error logs
1493 | 2. Use `debug_validate_scene` to check if the scene has issues
1494 | 3. Use `debug_get_editor_info` to get environment information
1495 | 4. Check the MCP server's running status and logs
1496 |
1497 | ---
1498 |
1499 | *This document is based on Cocos Creator MCP Server v1.3.0. Please refer to the latest version documentation for updates.*
```
--------------------------------------------------------------------------------
/source/tools/project-tools.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ToolDefinition, ToolResponse, ToolExecutor, ProjectInfo, AssetInfo } from '../types';
2 | import * as fs from 'fs';
3 | import * as path from 'path';
4 |
5 | export class ProjectTools implements ToolExecutor {
6 | getTools(): ToolDefinition[] {
7 | return [
8 | {
9 | name: 'run_project',
10 | description: 'Run the project in preview mode',
11 | inputSchema: {
12 | type: 'object',
13 | properties: {
14 | platform: {
15 | type: 'string',
16 | description: 'Target platform',
17 | enum: ['browser', 'simulator', 'preview'],
18 | default: 'browser'
19 | }
20 | }
21 | }
22 | },
23 | {
24 | name: 'build_project',
25 | description: 'Build the project',
26 | inputSchema: {
27 | type: 'object',
28 | properties: {
29 | platform: {
30 | type: 'string',
31 | description: 'Build platform',
32 | enum: ['web-mobile', 'web-desktop', 'ios', 'android', 'windows', 'mac']
33 | },
34 | debug: {
35 | type: 'boolean',
36 | description: 'Debug build',
37 | default: true
38 | }
39 | },
40 | required: ['platform']
41 | }
42 | },
43 | {
44 | name: 'get_project_info',
45 | description: 'Get project information',
46 | inputSchema: {
47 | type: 'object',
48 | properties: {}
49 | }
50 | },
51 | {
52 | name: 'get_project_settings',
53 | description: 'Get project settings',
54 | inputSchema: {
55 | type: 'object',
56 | properties: {
57 | category: {
58 | type: 'string',
59 | description: 'Settings category',
60 | enum: ['general', 'physics', 'render', 'assets'],
61 | default: 'general'
62 | }
63 | }
64 | }
65 | },
66 | {
67 | name: 'refresh_assets',
68 | description: 'Refresh asset database',
69 | inputSchema: {
70 | type: 'object',
71 | properties: {
72 | folder: {
73 | type: 'string',
74 | description: 'Specific folder to refresh (optional)'
75 | }
76 | }
77 | }
78 | },
79 | {
80 | name: 'import_asset',
81 | description: 'Import an asset file',
82 | inputSchema: {
83 | type: 'object',
84 | properties: {
85 | sourcePath: {
86 | type: 'string',
87 | description: 'Source file path'
88 | },
89 | targetFolder: {
90 | type: 'string',
91 | description: 'Target folder in assets'
92 | }
93 | },
94 | required: ['sourcePath', 'targetFolder']
95 | }
96 | },
97 | {
98 | name: 'get_asset_info',
99 | description: 'Get asset information',
100 | inputSchema: {
101 | type: 'object',
102 | properties: {
103 | assetPath: {
104 | type: 'string',
105 | description: 'Asset path (db://assets/...)'
106 | }
107 | },
108 | required: ['assetPath']
109 | }
110 | },
111 | {
112 | name: 'get_assets',
113 | description: 'Get assets by type',
114 | inputSchema: {
115 | type: 'object',
116 | properties: {
117 | type: {
118 | type: 'string',
119 | description: 'Asset type filter',
120 | enum: ['all', 'scene', 'prefab', 'script', 'texture', 'material', 'mesh', 'audio', 'animation'],
121 | default: 'all'
122 | },
123 | folder: {
124 | type: 'string',
125 | description: 'Folder to search in',
126 | default: 'db://assets'
127 | }
128 | }
129 | }
130 | },
131 | {
132 | name: 'get_build_settings',
133 | description: 'Get build settings - shows current limitations',
134 | inputSchema: {
135 | type: 'object',
136 | properties: {}
137 | }
138 | },
139 | {
140 | name: 'open_build_panel',
141 | description: 'Open the build panel in the editor',
142 | inputSchema: {
143 | type: 'object',
144 | properties: {}
145 | }
146 | },
147 | {
148 | name: 'check_builder_status',
149 | description: 'Check if builder worker is ready',
150 | inputSchema: {
151 | type: 'object',
152 | properties: {}
153 | }
154 | },
155 | {
156 | name: 'start_preview_server',
157 | description: 'Start preview server',
158 | inputSchema: {
159 | type: 'object',
160 | properties: {
161 | port: {
162 | type: 'number',
163 | description: 'Preview server port',
164 | default: 7456
165 | }
166 | }
167 | }
168 | },
169 | {
170 | name: 'stop_preview_server',
171 | description: 'Stop preview server',
172 | inputSchema: {
173 | type: 'object',
174 | properties: {}
175 | }
176 | },
177 | {
178 | name: 'create_asset',
179 | description: 'Create a new asset file or folder',
180 | inputSchema: {
181 | type: 'object',
182 | properties: {
183 | url: {
184 | type: 'string',
185 | description: 'Asset URL (e.g., db://assets/newfile.json)'
186 | },
187 | content: {
188 | type: 'string',
189 | description: 'File content (null for folder)',
190 | default: null
191 | },
192 | overwrite: {
193 | type: 'boolean',
194 | description: 'Overwrite existing file',
195 | default: false
196 | }
197 | },
198 | required: ['url']
199 | }
200 | },
201 | {
202 | name: 'copy_asset',
203 | description: 'Copy an asset to another location',
204 | inputSchema: {
205 | type: 'object',
206 | properties: {
207 | source: {
208 | type: 'string',
209 | description: 'Source asset URL'
210 | },
211 | target: {
212 | type: 'string',
213 | description: 'Target location URL'
214 | },
215 | overwrite: {
216 | type: 'boolean',
217 | description: 'Overwrite existing file',
218 | default: false
219 | }
220 | },
221 | required: ['source', 'target']
222 | }
223 | },
224 | {
225 | name: 'move_asset',
226 | description: 'Move an asset to another location',
227 | inputSchema: {
228 | type: 'object',
229 | properties: {
230 | source: {
231 | type: 'string',
232 | description: 'Source asset URL'
233 | },
234 | target: {
235 | type: 'string',
236 | description: 'Target location URL'
237 | },
238 | overwrite: {
239 | type: 'boolean',
240 | description: 'Overwrite existing file',
241 | default: false
242 | }
243 | },
244 | required: ['source', 'target']
245 | }
246 | },
247 | {
248 | name: 'delete_asset',
249 | description: 'Delete an asset',
250 | inputSchema: {
251 | type: 'object',
252 | properties: {
253 | url: {
254 | type: 'string',
255 | description: 'Asset URL to delete'
256 | }
257 | },
258 | required: ['url']
259 | }
260 | },
261 | {
262 | name: 'save_asset',
263 | description: 'Save asset content',
264 | inputSchema: {
265 | type: 'object',
266 | properties: {
267 | url: {
268 | type: 'string',
269 | description: 'Asset URL'
270 | },
271 | content: {
272 | type: 'string',
273 | description: 'Asset content'
274 | }
275 | },
276 | required: ['url', 'content']
277 | }
278 | },
279 | {
280 | name: 'reimport_asset',
281 | description: 'Reimport an asset',
282 | inputSchema: {
283 | type: 'object',
284 | properties: {
285 | url: {
286 | type: 'string',
287 | description: 'Asset URL to reimport'
288 | }
289 | },
290 | required: ['url']
291 | }
292 | },
293 | {
294 | name: 'query_asset_path',
295 | description: 'Get asset disk path',
296 | inputSchema: {
297 | type: 'object',
298 | properties: {
299 | url: {
300 | type: 'string',
301 | description: 'Asset URL'
302 | }
303 | },
304 | required: ['url']
305 | }
306 | },
307 | {
308 | name: 'query_asset_uuid',
309 | description: 'Get asset UUID from URL',
310 | inputSchema: {
311 | type: 'object',
312 | properties: {
313 | url: {
314 | type: 'string',
315 | description: 'Asset URL'
316 | }
317 | },
318 | required: ['url']
319 | }
320 | },
321 | {
322 | name: 'query_asset_url',
323 | description: 'Get asset URL from UUID',
324 | inputSchema: {
325 | type: 'object',
326 | properties: {
327 | uuid: {
328 | type: 'string',
329 | description: 'Asset UUID'
330 | }
331 | },
332 | required: ['uuid']
333 | }
334 | },
335 | {
336 | name: 'find_asset_by_name',
337 | description: 'Find assets by name (supports partial matching and multiple results)',
338 | inputSchema: {
339 | type: 'object',
340 | properties: {
341 | name: {
342 | type: 'string',
343 | description: 'Asset name to search for (supports partial matching)'
344 | },
345 | exactMatch: {
346 | type: 'boolean',
347 | description: 'Whether to use exact name matching',
348 | default: false
349 | },
350 | assetType: {
351 | type: 'string',
352 | description: 'Filter by asset type',
353 | enum: ['all', 'scene', 'prefab', 'script', 'texture', 'material', 'mesh', 'audio', 'animation', 'spriteFrame'],
354 | default: 'all'
355 | },
356 | folder: {
357 | type: 'string',
358 | description: 'Folder to search in',
359 | default: 'db://assets'
360 | },
361 | maxResults: {
362 | type: 'number',
363 | description: 'Maximum number of results to return',
364 | default: 20,
365 | minimum: 1,
366 | maximum: 100
367 | }
368 | },
369 | required: ['name']
370 | }
371 | },
372 | {
373 | name: 'get_asset_details',
374 | description: 'Get detailed asset information including spriteFrame sub-assets',
375 | inputSchema: {
376 | type: 'object',
377 | properties: {
378 | assetPath: {
379 | type: 'string',
380 | description: 'Asset path (db://assets/...)'
381 | },
382 | includeSubAssets: {
383 | type: 'boolean',
384 | description: 'Include sub-assets like spriteFrame, texture',
385 | default: true
386 | }
387 | },
388 | required: ['assetPath']
389 | }
390 | }
391 | ];
392 | }
393 |
394 | async execute(toolName: string, args: any): Promise<ToolResponse> {
395 | switch (toolName) {
396 | case 'run_project':
397 | return await this.runProject(args.platform);
398 | case 'build_project':
399 | return await this.buildProject(args);
400 | case 'get_project_info':
401 | return await this.getProjectInfo();
402 | case 'get_project_settings':
403 | return await this.getProjectSettings(args.category);
404 | case 'refresh_assets':
405 | return await this.refreshAssets(args.folder);
406 | case 'import_asset':
407 | return await this.importAsset(args.sourcePath, args.targetFolder);
408 | case 'get_asset_info':
409 | return await this.getAssetInfo(args.assetPath);
410 | case 'get_assets':
411 | return await this.getAssets(args.type, args.folder);
412 | case 'get_build_settings':
413 | return await this.getBuildSettings();
414 | case 'open_build_panel':
415 | return await this.openBuildPanel();
416 | case 'check_builder_status':
417 | return await this.checkBuilderStatus();
418 | case 'start_preview_server':
419 | return await this.startPreviewServer(args.port);
420 | case 'stop_preview_server':
421 | return await this.stopPreviewServer();
422 | case 'create_asset':
423 | return await this.createAsset(args.url, args.content, args.overwrite);
424 | case 'copy_asset':
425 | return await this.copyAsset(args.source, args.target, args.overwrite);
426 | case 'move_asset':
427 | return await this.moveAsset(args.source, args.target, args.overwrite);
428 | case 'delete_asset':
429 | return await this.deleteAsset(args.url);
430 | case 'save_asset':
431 | return await this.saveAsset(args.url, args.content);
432 | case 'reimport_asset':
433 | return await this.reimportAsset(args.url);
434 | case 'query_asset_path':
435 | return await this.queryAssetPath(args.url);
436 | case 'query_asset_uuid':
437 | return await this.queryAssetUuid(args.url);
438 | case 'query_asset_url':
439 | return await this.queryAssetUrl(args.uuid);
440 | case 'find_asset_by_name':
441 | return await this.findAssetByName(args);
442 | case 'get_asset_details':
443 | return await this.getAssetDetails(args.assetPath, args.includeSubAssets);
444 | default:
445 | throw new Error(`Unknown tool: ${toolName}`);
446 | }
447 | }
448 |
449 | private async runProject(platform: string = 'browser'): Promise<ToolResponse> {
450 | return new Promise((resolve) => {
451 | const previewConfig = {
452 | platform: platform,
453 | scenes: [] // Will use current scene
454 | };
455 |
456 | // Note: Preview module is not documented in official API
457 | // Using fallback approach - open build panel as alternative
458 | Editor.Message.request('builder', 'open').then(() => {
459 | resolve({
460 | success: true,
461 | message: `Build panel opened. Preview functionality requires manual setup.`
462 | });
463 | }).catch((err: Error) => {
464 | resolve({ success: false, error: err.message });
465 | });
466 | });
467 | }
468 |
469 | private async buildProject(args: any): Promise<ToolResponse> {
470 | return new Promise((resolve) => {
471 | const buildOptions = {
472 | platform: args.platform,
473 | debug: args.debug !== false,
474 | sourceMaps: args.debug !== false,
475 | buildPath: `build/${args.platform}`
476 | };
477 |
478 | // Note: Builder module only supports 'open' and 'query-worker-ready'
479 | // Building requires manual interaction through the build panel
480 | Editor.Message.request('builder', 'open').then(() => {
481 | resolve({
482 | success: true,
483 | message: `Build panel opened for ${args.platform}. Please configure and start build manually.`,
484 | data: {
485 | platform: args.platform,
486 | instruction: "Use the build panel to configure and start the build process"
487 | }
488 | });
489 | }).catch((err: Error) => {
490 | resolve({ success: false, error: err.message });
491 | });
492 | });
493 | }
494 |
495 | private async getProjectInfo(): Promise<ToolResponse> {
496 | return new Promise((resolve) => {
497 | const info: ProjectInfo = {
498 | name: Editor.Project.name,
499 | path: Editor.Project.path,
500 | uuid: Editor.Project.uuid,
501 | version: (Editor.Project as any).version || '1.0.0',
502 | cocosVersion: (Editor as any).versions?.cocos || 'Unknown'
503 | };
504 |
505 | // Note: 'query-info' API doesn't exist, using 'query-config' instead
506 | Editor.Message.request('project', 'query-config', 'project').then((additionalInfo: any) => {
507 | if (additionalInfo) {
508 | Object.assign(info, { config: additionalInfo });
509 | }
510 | resolve({ success: true, data: info });
511 | }).catch(() => {
512 | // Return basic info even if detailed query fails
513 | resolve({ success: true, data: info });
514 | });
515 | });
516 | }
517 |
518 | private async getProjectSettings(category: string = 'general'): Promise<ToolResponse> {
519 | return new Promise((resolve) => {
520 | // 使用正确的 project API 查询项目配置
521 | const configMap: Record<string, string> = {
522 | general: 'project',
523 | physics: 'physics',
524 | render: 'render',
525 | assets: 'asset-db'
526 | };
527 |
528 | const configName = configMap[category] || 'project';
529 |
530 | Editor.Message.request('project', 'query-config', configName).then((settings: any) => {
531 | resolve({
532 | success: true,
533 | data: {
534 | category: category,
535 | config: settings,
536 | message: `${category} settings retrieved successfully`
537 | }
538 | });
539 | }).catch((err: Error) => {
540 | resolve({ success: false, error: err.message });
541 | });
542 | });
543 | }
544 |
545 | private async refreshAssets(folder?: string): Promise<ToolResponse> {
546 | return new Promise((resolve) => {
547 | // 使用正确的 asset-db API 刷新资源
548 | const targetPath = folder || 'db://assets';
549 |
550 | Editor.Message.request('asset-db', 'refresh-asset', targetPath).then(() => {
551 | resolve({
552 | success: true,
553 | message: `Assets refreshed in: ${targetPath}`
554 | });
555 | }).catch((err: Error) => {
556 | resolve({ success: false, error: err.message });
557 | });
558 | });
559 | }
560 |
561 | private async importAsset(sourcePath: string, targetFolder: string): Promise<ToolResponse> {
562 | return new Promise((resolve) => {
563 | if (!fs.existsSync(sourcePath)) {
564 | resolve({ success: false, error: 'Source file not found' });
565 | return;
566 | }
567 |
568 | const fileName = path.basename(sourcePath);
569 | const targetPath = targetFolder.startsWith('db://') ?
570 | targetFolder : `db://assets/${targetFolder}`;
571 |
572 | Editor.Message.request('asset-db', 'import-asset', sourcePath, `${targetPath}/${fileName}`).then((result: any) => {
573 | resolve({
574 | success: true,
575 | data: {
576 | uuid: result.uuid,
577 | path: result.url,
578 | message: `Asset imported: ${fileName}`
579 | }
580 | });
581 | }).catch((err: Error) => {
582 | resolve({ success: false, error: err.message });
583 | });
584 | });
585 | }
586 |
587 | private async getAssetInfo(assetPath: string): Promise<ToolResponse> {
588 | return new Promise((resolve) => {
589 | Editor.Message.request('asset-db', 'query-asset-info', assetPath).then((assetInfo: any) => {
590 | if (!assetInfo) {
591 | throw new Error('Asset not found');
592 | }
593 |
594 | const info: AssetInfo = {
595 | name: assetInfo.name,
596 | uuid: assetInfo.uuid,
597 | path: assetInfo.url,
598 | type: assetInfo.type,
599 | size: assetInfo.size,
600 | isDirectory: assetInfo.isDirectory
601 | };
602 |
603 | if (assetInfo.meta) {
604 | info.meta = {
605 | ver: assetInfo.meta.ver,
606 | importer: assetInfo.meta.importer
607 | };
608 | }
609 |
610 | resolve({ success: true, data: info });
611 | }).catch((err: Error) => {
612 | resolve({ success: false, error: err.message });
613 | });
614 | });
615 | }
616 |
617 | private async getAssets(type: string = 'all', folder: string = 'db://assets'): Promise<ToolResponse> {
618 | return new Promise((resolve) => {
619 | let pattern = `${folder}/**/*`;
620 |
621 | // 添加类型过滤
622 | if (type !== 'all') {
623 | const typeExtensions: Record<string, string> = {
624 | 'scene': '.scene',
625 | 'prefab': '.prefab',
626 | 'script': '.{ts,js}',
627 | 'texture': '.{png,jpg,jpeg,gif,tga,bmp,psd}',
628 | 'material': '.mtl',
629 | 'mesh': '.{fbx,obj,dae}',
630 | 'audio': '.{mp3,ogg,wav,m4a}',
631 | 'animation': '.{anim,clip}'
632 | };
633 |
634 | const extension = typeExtensions[type];
635 | if (extension) {
636 | pattern = `${folder}/**/*${extension}`;
637 | }
638 | }
639 |
640 | // Note: query-assets API parameters corrected based on documentation
641 | Editor.Message.request('asset-db', 'query-assets', { pattern: pattern }).then((results: any[]) => {
642 | const assets = results.map(asset => ({
643 | name: asset.name,
644 | uuid: asset.uuid,
645 | path: asset.url,
646 | type: asset.type,
647 | size: asset.size || 0,
648 | isDirectory: asset.isDirectory || false
649 | }));
650 |
651 | resolve({
652 | success: true,
653 | data: {
654 | type: type,
655 | folder: folder,
656 | count: assets.length,
657 | assets: assets
658 | }
659 | });
660 | }).catch((err: Error) => {
661 | resolve({ success: false, error: err.message });
662 | });
663 | });
664 | }
665 |
666 | private async getBuildSettings(): Promise<ToolResponse> {
667 | return new Promise((resolve) => {
668 | // 检查构建器是否准备就绪
669 | Editor.Message.request('builder', 'query-worker-ready').then((ready: boolean) => {
670 | resolve({
671 | success: true,
672 | data: {
673 | builderReady: ready,
674 | message: 'Build settings are limited in MCP plugin environment',
675 | availableActions: [
676 | 'Open build panel with open_build_panel',
677 | 'Check builder status with check_builder_status',
678 | 'Start preview server with start_preview_server',
679 | 'Stop preview server with stop_preview_server'
680 | ],
681 | limitation: 'Full build configuration requires direct Editor UI access'
682 | }
683 | });
684 | }).catch((err: Error) => {
685 | resolve({ success: false, error: err.message });
686 | });
687 | });
688 | }
689 |
690 | private async openBuildPanel(): Promise<ToolResponse> {
691 | return new Promise((resolve) => {
692 | Editor.Message.request('builder', 'open').then(() => {
693 | resolve({
694 | success: true,
695 | message: 'Build panel opened successfully'
696 | });
697 | }).catch((err: Error) => {
698 | resolve({ success: false, error: err.message });
699 | });
700 | });
701 | }
702 |
703 | private async checkBuilderStatus(): Promise<ToolResponse> {
704 | return new Promise((resolve) => {
705 | Editor.Message.request('builder', 'query-worker-ready').then((ready: boolean) => {
706 | resolve({
707 | success: true,
708 | data: {
709 | ready: ready,
710 | status: ready ? 'Builder worker is ready' : 'Builder worker is not ready',
711 | message: 'Builder status checked successfully'
712 | }
713 | });
714 | }).catch((err: Error) => {
715 | resolve({ success: false, error: err.message });
716 | });
717 | });
718 | }
719 |
720 | private async startPreviewServer(port: number = 7456): Promise<ToolResponse> {
721 | return new Promise((resolve) => {
722 | resolve({
723 | success: false,
724 | error: 'Preview server control is not supported through MCP API',
725 | instruction: 'Please start the preview server manually using the editor menu: Project > Preview, or use the preview panel in the editor'
726 | });
727 | });
728 | }
729 |
730 | private async stopPreviewServer(): Promise<ToolResponse> {
731 | return new Promise((resolve) => {
732 | resolve({
733 | success: false,
734 | error: 'Preview server control is not supported through MCP API',
735 | instruction: 'Please stop the preview server manually using the preview panel in the editor'
736 | });
737 | });
738 | }
739 |
740 | private async createAsset(url: string, content: string | null = null, overwrite: boolean = false): Promise<ToolResponse> {
741 | return new Promise((resolve) => {
742 | const options = {
743 | overwrite: overwrite,
744 | rename: !overwrite
745 | };
746 |
747 | Editor.Message.request('asset-db', 'create-asset', url, content, options).then((result: any) => {
748 | if (result && result.uuid) {
749 | resolve({
750 | success: true,
751 | data: {
752 | uuid: result.uuid,
753 | url: result.url,
754 | message: content === null ? 'Folder created successfully' : 'File created successfully'
755 | }
756 | });
757 | } else {
758 | resolve({
759 | success: true,
760 | data: {
761 | url: url,
762 | message: content === null ? 'Folder created successfully' : 'File created successfully'
763 | }
764 | });
765 | }
766 | }).catch((err: Error) => {
767 | resolve({ success: false, error: err.message });
768 | });
769 | });
770 | }
771 |
772 | private async copyAsset(source: string, target: string, overwrite: boolean = false): Promise<ToolResponse> {
773 | return new Promise((resolve) => {
774 | const options = {
775 | overwrite: overwrite,
776 | rename: !overwrite
777 | };
778 |
779 | Editor.Message.request('asset-db', 'copy-asset', source, target, options).then((result: any) => {
780 | if (result && result.uuid) {
781 | resolve({
782 | success: true,
783 | data: {
784 | uuid: result.uuid,
785 | url: result.url,
786 | message: 'Asset copied successfully'
787 | }
788 | });
789 | } else {
790 | resolve({
791 | success: true,
792 | data: {
793 | source: source,
794 | target: target,
795 | message: 'Asset copied successfully'
796 | }
797 | });
798 | }
799 | }).catch((err: Error) => {
800 | resolve({ success: false, error: err.message });
801 | });
802 | });
803 | }
804 |
805 | private async moveAsset(source: string, target: string, overwrite: boolean = false): Promise<ToolResponse> {
806 | return new Promise((resolve) => {
807 | const options = {
808 | overwrite: overwrite,
809 | rename: !overwrite
810 | };
811 |
812 | Editor.Message.request('asset-db', 'move-asset', source, target, options).then((result: any) => {
813 | if (result && result.uuid) {
814 | resolve({
815 | success: true,
816 | data: {
817 | uuid: result.uuid,
818 | url: result.url,
819 | message: 'Asset moved successfully'
820 | }
821 | });
822 | } else {
823 | resolve({
824 | success: true,
825 | data: {
826 | source: source,
827 | target: target,
828 | message: 'Asset moved successfully'
829 | }
830 | });
831 | }
832 | }).catch((err: Error) => {
833 | resolve({ success: false, error: err.message });
834 | });
835 | });
836 | }
837 |
838 | private async deleteAsset(url: string): Promise<ToolResponse> {
839 | return new Promise((resolve) => {
840 | Editor.Message.request('asset-db', 'delete-asset', url).then((result: any) => {
841 | resolve({
842 | success: true,
843 | data: {
844 | url: url,
845 | message: 'Asset deleted successfully'
846 | }
847 | });
848 | }).catch((err: Error) => {
849 | resolve({ success: false, error: err.message });
850 | });
851 | });
852 | }
853 |
854 | private async saveAsset(url: string, content: string): Promise<ToolResponse> {
855 | return new Promise((resolve) => {
856 | Editor.Message.request('asset-db', 'save-asset', url, content).then((result: any) => {
857 | if (result && result.uuid) {
858 | resolve({
859 | success: true,
860 | data: {
861 | uuid: result.uuid,
862 | url: result.url,
863 | message: 'Asset saved successfully'
864 | }
865 | });
866 | } else {
867 | resolve({
868 | success: true,
869 | data: {
870 | url: url,
871 | message: 'Asset saved successfully'
872 | }
873 | });
874 | }
875 | }).catch((err: Error) => {
876 | resolve({ success: false, error: err.message });
877 | });
878 | });
879 | }
880 |
881 | private async reimportAsset(url: string): Promise<ToolResponse> {
882 | return new Promise((resolve) => {
883 | Editor.Message.request('asset-db', 'reimport-asset', url).then(() => {
884 | resolve({
885 | success: true,
886 | data: {
887 | url: url,
888 | message: 'Asset reimported successfully'
889 | }
890 | });
891 | }).catch((err: Error) => {
892 | resolve({ success: false, error: err.message });
893 | });
894 | });
895 | }
896 |
897 | private async queryAssetPath(url: string): Promise<ToolResponse> {
898 | return new Promise((resolve) => {
899 | Editor.Message.request('asset-db', 'query-path', url).then((path: string | null) => {
900 | if (path) {
901 | resolve({
902 | success: true,
903 | data: {
904 | url: url,
905 | path: path,
906 | message: 'Asset path retrieved successfully'
907 | }
908 | });
909 | } else {
910 | resolve({ success: false, error: 'Asset path not found' });
911 | }
912 | }).catch((err: Error) => {
913 | resolve({ success: false, error: err.message });
914 | });
915 | });
916 | }
917 |
918 | private async queryAssetUuid(url: string): Promise<ToolResponse> {
919 | return new Promise((resolve) => {
920 | Editor.Message.request('asset-db', 'query-uuid', url).then((uuid: string | null) => {
921 | if (uuid) {
922 | resolve({
923 | success: true,
924 | data: {
925 | url: url,
926 | uuid: uuid,
927 | message: 'Asset UUID retrieved successfully'
928 | }
929 | });
930 | } else {
931 | resolve({ success: false, error: 'Asset UUID not found' });
932 | }
933 | }).catch((err: Error) => {
934 | resolve({ success: false, error: err.message });
935 | });
936 | });
937 | }
938 |
939 | private async queryAssetUrl(uuid: string): Promise<ToolResponse> {
940 | return new Promise((resolve) => {
941 | Editor.Message.request('asset-db', 'query-url', uuid).then((url: string | null) => {
942 | if (url) {
943 | resolve({
944 | success: true,
945 | data: {
946 | uuid: uuid,
947 | url: url,
948 | message: 'Asset URL retrieved successfully'
949 | }
950 | });
951 | } else {
952 | resolve({ success: false, error: 'Asset URL not found' });
953 | }
954 | }).catch((err: Error) => {
955 | resolve({ success: false, error: err.message });
956 | });
957 | });
958 | }
959 |
960 | private async findAssetByName(args: any): Promise<ToolResponse> {
961 | const { name, exactMatch = false, assetType = 'all', folder = 'db://assets', maxResults = 20 } = args;
962 |
963 | return new Promise(async (resolve) => {
964 | try {
965 | // Get all assets in the specified folder
966 | const allAssetsResponse = await this.getAssets(assetType, folder);
967 | if (!allAssetsResponse.success || !allAssetsResponse.data) {
968 | resolve({
969 | success: false,
970 | error: `Failed to get assets: ${allAssetsResponse.error}`
971 | });
972 | return;
973 | }
974 |
975 | const allAssets = allAssetsResponse.data.assets as any[];
976 | let matchedAssets: any[] = [];
977 |
978 | // Search for matching assets
979 | for (const asset of allAssets) {
980 | const assetName = asset.name;
981 | let matches = false;
982 |
983 | if (exactMatch) {
984 | matches = assetName === name;
985 | } else {
986 | matches = assetName.toLowerCase().includes(name.toLowerCase());
987 | }
988 |
989 | if (matches) {
990 | // Get detailed asset info if needed
991 | try {
992 | const detailResponse = await this.getAssetInfo(asset.path);
993 | if (detailResponse.success) {
994 | matchedAssets.push({
995 | ...asset,
996 | details: detailResponse.data
997 | });
998 | } else {
999 | matchedAssets.push(asset);
1000 | }
1001 | } catch {
1002 | matchedAssets.push(asset);
1003 | }
1004 |
1005 | if (matchedAssets.length >= maxResults) {
1006 | break;
1007 | }
1008 | }
1009 | }
1010 |
1011 | resolve({
1012 | success: true,
1013 | data: {
1014 | searchTerm: name,
1015 | exactMatch,
1016 | assetType,
1017 | folder,
1018 | totalFound: matchedAssets.length,
1019 | maxResults,
1020 | assets: matchedAssets,
1021 | message: `Found ${matchedAssets.length} assets matching '${name}'`
1022 | }
1023 | });
1024 |
1025 | } catch (error: any) {
1026 | resolve({
1027 | success: false,
1028 | error: `Asset search failed: ${error.message}`
1029 | });
1030 | }
1031 | });
1032 | }
1033 |
1034 | private async getAssetDetails(assetPath: string, includeSubAssets: boolean = true): Promise<ToolResponse> {
1035 | return new Promise(async (resolve) => {
1036 | try {
1037 | // Get basic asset info
1038 | const assetInfoResponse = await this.getAssetInfo(assetPath);
1039 | if (!assetInfoResponse.success) {
1040 | resolve(assetInfoResponse);
1041 | return;
1042 | }
1043 |
1044 | const assetInfo = assetInfoResponse.data;
1045 | const detailedInfo: any = {
1046 | ...assetInfo,
1047 | subAssets: []
1048 | };
1049 |
1050 | if (includeSubAssets && assetInfo) {
1051 | // For image assets, try to get spriteFrame and texture sub-assets
1052 | if (assetInfo.type === 'cc.ImageAsset' || assetPath.match(/\.(png|jpg|jpeg|gif|tga|bmp|psd)$/i)) {
1053 | // Generate common sub-asset UUIDs
1054 | const baseUuid = assetInfo.uuid;
1055 | const possibleSubAssets = [
1056 | { type: 'spriteFrame', uuid: `${baseUuid}@f9941`, suffix: '@f9941' },
1057 | { type: 'texture', uuid: `${baseUuid}@6c48a`, suffix: '@6c48a' },
1058 | { type: 'texture2D', uuid: `${baseUuid}@6c48a`, suffix: '@6c48a' }
1059 | ];
1060 |
1061 | for (const subAsset of possibleSubAssets) {
1062 | try {
1063 | // Try to get URL for the sub-asset to verify it exists
1064 | const subAssetUrl = await Editor.Message.request('asset-db', 'query-url', subAsset.uuid);
1065 | if (subAssetUrl) {
1066 | detailedInfo.subAssets.push({
1067 | type: subAsset.type,
1068 | uuid: subAsset.uuid,
1069 | url: subAssetUrl,
1070 | suffix: subAsset.suffix
1071 | });
1072 | }
1073 | } catch {
1074 | // Sub-asset doesn't exist, skip it
1075 | }
1076 | }
1077 | }
1078 | }
1079 |
1080 | resolve({
1081 | success: true,
1082 | data: {
1083 | assetPath,
1084 | includeSubAssets,
1085 | ...detailedInfo,
1086 | message: `Asset details retrieved. Found ${detailedInfo.subAssets.length} sub-assets.`
1087 | }
1088 | });
1089 |
1090 | } catch (error: any) {
1091 | resolve({
1092 | success: false,
1093 | error: `Failed to get asset details: ${error.message}`
1094 | });
1095 | }
1096 | });
1097 | }
1098 | }
```
--------------------------------------------------------------------------------
/source/tools/node-tools.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ToolDefinition, ToolResponse, ToolExecutor, NodeInfo } from '../types';
2 | import { ComponentTools } from './component-tools';
3 |
4 | export class NodeTools implements ToolExecutor {
5 | private componentTools = new ComponentTools();
6 | getTools(): ToolDefinition[] {
7 | return [
8 | {
9 | name: 'create_node',
10 | description: 'Create a new node in the scene. Supports creating empty nodes, nodes with components, or instantiating from assets (prefabs, etc.). IMPORTANT: You should always provide parentUuid to specify where to create the node.',
11 | inputSchema: {
12 | type: 'object',
13 | properties: {
14 | name: {
15 | type: 'string',
16 | description: 'Node name'
17 | },
18 | parentUuid: {
19 | type: 'string',
20 | 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.'
21 | },
22 | nodeType: {
23 | type: 'string',
24 | description: 'Node type: Node, 2DNode, 3DNode',
25 | enum: ['Node', '2DNode', '3DNode'],
26 | default: 'Node'
27 | },
28 | siblingIndex: {
29 | type: 'number',
30 | description: 'Sibling index for ordering (-1 means append at end)',
31 | default: -1
32 | },
33 | assetUuid: {
34 | type: 'string',
35 | description: 'Asset UUID to instantiate from (e.g., prefab UUID). When provided, creates a node instance from the asset instead of an empty node.'
36 | },
37 | assetPath: {
38 | type: 'string',
39 | description: 'Asset path to instantiate from (e.g., "db://assets/prefabs/MyPrefab.prefab"). Alternative to assetUuid.'
40 | },
41 | components: {
42 | type: 'array',
43 | items: { type: 'string' },
44 | description: 'Array of component type names to add to the new node (e.g., ["cc.Sprite", "cc.Button"])'
45 | },
46 | unlinkPrefab: {
47 | type: 'boolean',
48 | description: 'If true and creating from prefab, unlink from prefab to create a regular node',
49 | default: false
50 | },
51 | keepWorldTransform: {
52 | type: 'boolean',
53 | description: 'Whether to keep world transform when creating the node',
54 | default: false
55 | },
56 | initialTransform: {
57 | type: 'object',
58 | properties: {
59 | position: {
60 | type: 'object',
61 | properties: {
62 | x: { type: 'number' },
63 | y: { type: 'number' },
64 | z: { type: 'number' }
65 | }
66 | },
67 | rotation: {
68 | type: 'object',
69 | properties: {
70 | x: { type: 'number' },
71 | y: { type: 'number' },
72 | z: { type: 'number' }
73 | }
74 | },
75 | scale: {
76 | type: 'object',
77 | properties: {
78 | x: { type: 'number' },
79 | y: { type: 'number' },
80 | z: { type: 'number' }
81 | }
82 | }
83 | },
84 | description: 'Initial transform to apply to the created node'
85 | }
86 | },
87 | required: ['name']
88 | }
89 | },
90 | {
91 | name: 'get_node_info',
92 | description: 'Get node information by UUID',
93 | inputSchema: {
94 | type: 'object',
95 | properties: {
96 | uuid: {
97 | type: 'string',
98 | description: 'Node UUID'
99 | }
100 | },
101 | required: ['uuid']
102 | }
103 | },
104 | {
105 | name: 'find_nodes',
106 | description: 'Find nodes by name pattern',
107 | inputSchema: {
108 | type: 'object',
109 | properties: {
110 | pattern: {
111 | type: 'string',
112 | description: 'Name pattern to search'
113 | },
114 | exactMatch: {
115 | type: 'boolean',
116 | description: 'Exact match or partial match',
117 | default: false
118 | }
119 | },
120 | required: ['pattern']
121 | }
122 | },
123 | {
124 | name: 'find_node_by_name',
125 | description: 'Find first node by exact name',
126 | inputSchema: {
127 | type: 'object',
128 | properties: {
129 | name: {
130 | type: 'string',
131 | description: 'Node name to find'
132 | }
133 | },
134 | required: ['name']
135 | }
136 | },
137 | {
138 | name: 'get_all_nodes',
139 | description: 'Get all nodes in the scene with their UUIDs',
140 | inputSchema: {
141 | type: 'object',
142 | properties: {}
143 | }
144 | },
145 | {
146 | name: 'set_node_property',
147 | description: 'Set node property value (prefer using set_node_transform for active/layer/mobility/position/rotation/scale)',
148 | inputSchema: {
149 | type: 'object',
150 | properties: {
151 | uuid: {
152 | type: 'string',
153 | description: 'Node UUID'
154 | },
155 | property: {
156 | type: 'string',
157 | description: 'Property name (e.g., active, name, layer)'
158 | },
159 | value: {
160 | description: 'Property value'
161 | }
162 | },
163 | required: ['uuid', 'property', 'value']
164 | }
165 | },
166 | {
167 | name: 'set_node_transform',
168 | description: 'Set node transform properties (position, rotation, scale) with unified interface. Automatically handles 2D/3D node differences.',
169 | inputSchema: {
170 | type: 'object',
171 | properties: {
172 | uuid: {
173 | type: 'string',
174 | description: 'Node UUID'
175 | },
176 | position: {
177 | type: 'object',
178 | properties: {
179 | x: { type: 'number' },
180 | y: { type: 'number' },
181 | z: { type: 'number', description: 'Z coordinate (ignored for 2D nodes)' }
182 | },
183 | description: 'Node position. For 2D nodes, only x,y are used; z is ignored. For 3D nodes, all coordinates are used.'
184 | },
185 | rotation: {
186 | type: 'object',
187 | properties: {
188 | x: { type: 'number', description: 'X rotation (ignored for 2D nodes)' },
189 | y: { type: 'number', description: 'Y rotation (ignored for 2D nodes)' },
190 | z: { type: 'number', description: 'Z rotation (main rotation axis for 2D nodes)' }
191 | },
192 | description: 'Node rotation in euler angles. For 2D nodes, only z rotation is used. For 3D nodes, all axes are used.'
193 | },
194 | scale: {
195 | type: 'object',
196 | properties: {
197 | x: { type: 'number' },
198 | y: { type: 'number' },
199 | z: { type: 'number', description: 'Z scale (usually 1 for 2D nodes)' }
200 | },
201 | description: 'Node scale. For 2D nodes, z is typically 1. For 3D nodes, all axes are used.'
202 | }
203 | },
204 | required: ['uuid']
205 | }
206 | },
207 | {
208 | name: 'delete_node',
209 | description: 'Delete a node from scene',
210 | inputSchema: {
211 | type: 'object',
212 | properties: {
213 | uuid: {
214 | type: 'string',
215 | description: 'Node UUID to delete'
216 | }
217 | },
218 | required: ['uuid']
219 | }
220 | },
221 | {
222 | name: 'move_node',
223 | description: 'Move node to new parent',
224 | inputSchema: {
225 | type: 'object',
226 | properties: {
227 | nodeUuid: {
228 | type: 'string',
229 | description: 'Node UUID to move'
230 | },
231 | newParentUuid: {
232 | type: 'string',
233 | description: 'New parent node UUID'
234 | },
235 | siblingIndex: {
236 | type: 'number',
237 | description: 'Sibling index in new parent',
238 | default: -1
239 | }
240 | },
241 | required: ['nodeUuid', 'newParentUuid']
242 | }
243 | },
244 | {
245 | name: 'duplicate_node',
246 | description: 'Duplicate a node',
247 | inputSchema: {
248 | type: 'object',
249 | properties: {
250 | uuid: {
251 | type: 'string',
252 | description: 'Node UUID to duplicate'
253 | },
254 | includeChildren: {
255 | type: 'boolean',
256 | description: 'Include children nodes',
257 | default: true
258 | }
259 | },
260 | required: ['uuid']
261 | }
262 | },
263 | {
264 | name: 'detect_node_type',
265 | description: 'Detect if a node is 2D or 3D based on its components and properties',
266 | inputSchema: {
267 | type: 'object',
268 | properties: {
269 | uuid: {
270 | type: 'string',
271 | description: 'Node UUID to analyze'
272 | }
273 | },
274 | required: ['uuid']
275 | }
276 | }
277 | ];
278 | }
279 |
280 | async execute(toolName: string, args: any): Promise<ToolResponse> {
281 | switch (toolName) {
282 | case 'create_node':
283 | return await this.createNode(args);
284 | case 'get_node_info':
285 | return await this.getNodeInfo(args.uuid);
286 | case 'find_nodes':
287 | return await this.findNodes(args.pattern, args.exactMatch);
288 | case 'find_node_by_name':
289 | return await this.findNodeByName(args.name);
290 | case 'get_all_nodes':
291 | return await this.getAllNodes();
292 | case 'set_node_property':
293 | return await this.setNodeProperty(args.uuid, args.property, args.value);
294 | case 'set_node_transform':
295 | return await this.setNodeTransform(args);
296 | case 'delete_node':
297 | return await this.deleteNode(args.uuid);
298 | case 'move_node':
299 | return await this.moveNode(args.nodeUuid, args.newParentUuid, args.siblingIndex);
300 | case 'duplicate_node':
301 | return await this.duplicateNode(args.uuid, args.includeChildren);
302 | case 'detect_node_type':
303 | return await this.detectNodeType(args.uuid);
304 | default:
305 | throw new Error(`Unknown tool: ${toolName}`);
306 | }
307 | }
308 |
309 | private async createNode(args: any): Promise<ToolResponse> {
310 | return new Promise(async (resolve) => {
311 | try {
312 | let targetParentUuid = args.parentUuid;
313 |
314 | // 如果没有提供父节点UUID,获取场景根节点
315 | if (!targetParentUuid) {
316 | try {
317 | const sceneInfo = await Editor.Message.request('scene', 'query-node-tree');
318 | if (sceneInfo && typeof sceneInfo === 'object' && !Array.isArray(sceneInfo) && Object.prototype.hasOwnProperty.call(sceneInfo, 'uuid')) {
319 | targetParentUuid = (sceneInfo as any).uuid;
320 | console.log(`No parent specified, using scene root: ${targetParentUuid}`);
321 | } else if (Array.isArray(sceneInfo) && sceneInfo.length > 0 && sceneInfo[0].uuid) {
322 | targetParentUuid = sceneInfo[0].uuid;
323 | console.log(`No parent specified, using scene root: ${targetParentUuid}`);
324 | } else {
325 | const currentScene = await Editor.Message.request('scene', 'query-current-scene');
326 | if (currentScene && currentScene.uuid) {
327 | targetParentUuid = currentScene.uuid;
328 | }
329 | }
330 | } catch (err) {
331 | console.warn('Failed to get scene root, will use default behavior');
332 | }
333 | }
334 |
335 | // 如果提供了assetPath,先解析为assetUuid
336 | let finalAssetUuid = args.assetUuid;
337 | if (args.assetPath && !finalAssetUuid) {
338 | try {
339 | const assetInfo = await Editor.Message.request('asset-db', 'query-asset-info', args.assetPath);
340 | if (assetInfo && assetInfo.uuid) {
341 | finalAssetUuid = assetInfo.uuid;
342 | console.log(`Asset path '${args.assetPath}' resolved to UUID: ${finalAssetUuid}`);
343 | } else {
344 | resolve({
345 | success: false,
346 | error: `Asset not found at path: ${args.assetPath}`
347 | });
348 | return;
349 | }
350 | } catch (err) {
351 | resolve({
352 | success: false,
353 | error: `Failed to resolve asset path '${args.assetPath}': ${err}`
354 | });
355 | return;
356 | }
357 | }
358 |
359 | // 构建create-node选项
360 | const createNodeOptions: any = {
361 | name: args.name
362 | };
363 |
364 | // 设置父节点
365 | if (targetParentUuid) {
366 | createNodeOptions.parent = targetParentUuid;
367 | }
368 |
369 | // 从资源实例化
370 | if (finalAssetUuid) {
371 | createNodeOptions.assetUuid = finalAssetUuid;
372 | if (args.unlinkPrefab) {
373 | createNodeOptions.unlinkPrefab = true;
374 | }
375 | }
376 |
377 | // 添加组件
378 | if (args.components && args.components.length > 0) {
379 | createNodeOptions.components = args.components;
380 | } else if (args.nodeType && args.nodeType !== 'Node' && !finalAssetUuid) {
381 | // 只有在不从资源实例化时才添加nodeType组件
382 | createNodeOptions.components = [args.nodeType];
383 | }
384 |
385 | // 保持世界变换
386 | if (args.keepWorldTransform) {
387 | createNodeOptions.keepWorldTransform = true;
388 | }
389 |
390 | // 不使用dump参数处理初始变换,创建后使用set_node_transform设置
391 |
392 | console.log('Creating node with options:', createNodeOptions);
393 |
394 | // 创建节点
395 | const nodeUuid = await Editor.Message.request('scene', 'create-node', createNodeOptions);
396 | const uuid = Array.isArray(nodeUuid) ? nodeUuid[0] : nodeUuid;
397 |
398 | // 处理兄弟索引
399 | if (args.siblingIndex !== undefined && args.siblingIndex >= 0 && uuid && targetParentUuid) {
400 | try {
401 | await new Promise(resolve => setTimeout(resolve, 100)); // 等待内部状态更新
402 | await Editor.Message.request('scene', 'set-parent', {
403 | parent: targetParentUuid,
404 | uuids: [uuid],
405 | keepWorldTransform: args.keepWorldTransform || false
406 | });
407 | } catch (err) {
408 | console.warn('Failed to set sibling index:', err);
409 | }
410 | }
411 |
412 | // 添加组件(如果提供的话)
413 | if (args.components && args.components.length > 0 && uuid) {
414 | try {
415 | await new Promise(resolve => setTimeout(resolve, 100)); // 等待节点创建完成
416 | for (const componentType of args.components) {
417 | try {
418 | const result = await this.componentTools.execute('add_component', {
419 | nodeUuid: uuid,
420 | componentType: componentType
421 | });
422 | if (result.success) {
423 | console.log(`Component ${componentType} added successfully`);
424 | } else {
425 | console.warn(`Failed to add component ${componentType}:`, result.error);
426 | }
427 | } catch (err) {
428 | console.warn(`Failed to add component ${componentType}:`, err);
429 | }
430 | }
431 | } catch (err) {
432 | console.warn('Failed to add components:', err);
433 | }
434 | }
435 |
436 | // 设置初始变换(如果提供的话)
437 | if (args.initialTransform && uuid) {
438 | try {
439 | await new Promise(resolve => setTimeout(resolve, 150)); // 等待节点和组件创建完成
440 | await this.setNodeTransform({
441 | uuid: uuid,
442 | position: args.initialTransform.position,
443 | rotation: args.initialTransform.rotation,
444 | scale: args.initialTransform.scale
445 | });
446 | console.log('Initial transform applied successfully');
447 | } catch (err) {
448 | console.warn('Failed to set initial transform:', err);
449 | }
450 | }
451 |
452 | // 获取创建后的节点信息进行验证
453 | let verificationData: any = null;
454 | try {
455 | const nodeInfo = await this.getNodeInfo(uuid);
456 | if (nodeInfo.success) {
457 | verificationData = {
458 | nodeInfo: nodeInfo.data,
459 | creationDetails: {
460 | parentUuid: targetParentUuid,
461 | nodeType: args.nodeType || 'Node',
462 | fromAsset: !!finalAssetUuid,
463 | assetUuid: finalAssetUuid,
464 | assetPath: args.assetPath,
465 | timestamp: new Date().toISOString()
466 | }
467 | };
468 | }
469 | } catch (err) {
470 | console.warn('Failed to get verification data:', err);
471 | }
472 |
473 | const successMessage = finalAssetUuid
474 | ? `Node '${args.name}' instantiated from asset successfully`
475 | : `Node '${args.name}' created successfully`;
476 |
477 | resolve({
478 | success: true,
479 | data: {
480 | uuid: uuid,
481 | name: args.name,
482 | parentUuid: targetParentUuid,
483 | nodeType: args.nodeType || 'Node',
484 | fromAsset: !!finalAssetUuid,
485 | assetUuid: finalAssetUuid,
486 | message: successMessage
487 | },
488 | verificationData: verificationData
489 | });
490 |
491 | } catch (err: any) {
492 | resolve({
493 | success: false,
494 | error: `Failed to create node: ${err.message}. Args: ${JSON.stringify(args)}`
495 | });
496 | }
497 | });
498 | }
499 |
500 | private async getNodeInfo(uuid: string): Promise<ToolResponse> {
501 | return new Promise((resolve) => {
502 | Editor.Message.request('scene', 'query-node', uuid).then((nodeData: any) => {
503 | if (!nodeData) {
504 | resolve({
505 | success: false,
506 | error: 'Node not found or invalid response'
507 | });
508 | return;
509 | }
510 |
511 | // 根据实际返回的数据结构解析节点信息
512 | const info: NodeInfo = {
513 | uuid: nodeData.uuid?.value || uuid,
514 | name: nodeData.name?.value || 'Unknown',
515 | active: nodeData.active?.value !== undefined ? nodeData.active.value : true,
516 | position: nodeData.position?.value || { x: 0, y: 0, z: 0 },
517 | rotation: nodeData.rotation?.value || { x: 0, y: 0, z: 0 },
518 | scale: nodeData.scale?.value || { x: 1, y: 1, z: 1 },
519 | parent: nodeData.parent?.value?.uuid || null,
520 | children: nodeData.children || [],
521 | components: (nodeData.__comps__ || []).map((comp: any) => ({
522 | type: comp.__type__ || 'Unknown',
523 | enabled: comp.enabled !== undefined ? comp.enabled : true
524 | })),
525 | layer: nodeData.layer?.value || 1073741824,
526 | mobility: nodeData.mobility?.value || 0
527 | };
528 | resolve({ success: true, data: info });
529 | }).catch((err: Error) => {
530 | resolve({ success: false, error: err.message });
531 | });
532 | });
533 | }
534 |
535 | private async findNodes(pattern: string, exactMatch: boolean = false): Promise<ToolResponse> {
536 | return new Promise((resolve) => {
537 | // Note: 'query-nodes-by-name' API doesn't exist in official documentation
538 | // Using tree traversal as primary approach
539 | Editor.Message.request('scene', 'query-node-tree').then((tree: any) => {
540 | const nodes: any[] = [];
541 |
542 | const searchTree = (node: any, currentPath: string = '') => {
543 | const nodePath = currentPath ? `${currentPath}/${node.name}` : node.name;
544 |
545 | const matches = exactMatch ?
546 | node.name === pattern :
547 | node.name.toLowerCase().includes(pattern.toLowerCase());
548 |
549 | if (matches) {
550 | nodes.push({
551 | uuid: node.uuid,
552 | name: node.name,
553 | path: nodePath
554 | });
555 | }
556 |
557 | if (node.children) {
558 | for (const child of node.children) {
559 | searchTree(child, nodePath);
560 | }
561 | }
562 | };
563 |
564 | if (tree) {
565 | searchTree(tree);
566 | }
567 |
568 | resolve({ success: true, data: nodes });
569 | }).catch((err: Error) => {
570 | // 备用方案:使用场景脚本
571 | const options = {
572 | name: 'cocos-mcp-server',
573 | method: 'findNodes',
574 | args: [pattern, exactMatch]
575 | };
576 |
577 | Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
578 | resolve(result);
579 | }).catch((err2: Error) => {
580 | resolve({ success: false, error: `Tree search failed: ${err.message}, Scene script failed: ${err2.message}` });
581 | });
582 | });
583 | });
584 | }
585 |
586 | private async findNodeByName(name: string): Promise<ToolResponse> {
587 | return new Promise((resolve) => {
588 | // 优先尝试使用 Editor API 查询节点树并搜索
589 | Editor.Message.request('scene', 'query-node-tree').then((tree: any) => {
590 | const foundNode = this.searchNodeInTree(tree, name);
591 | if (foundNode) {
592 | resolve({
593 | success: true,
594 | data: {
595 | uuid: foundNode.uuid,
596 | name: foundNode.name,
597 | path: this.getNodePath(foundNode)
598 | }
599 | });
600 | } else {
601 | resolve({ success: false, error: `Node '${name}' not found` });
602 | }
603 | }).catch((err: Error) => {
604 | // 备用方案:使用场景脚本
605 | const options = {
606 | name: 'cocos-mcp-server',
607 | method: 'findNodeByName',
608 | args: [name]
609 | };
610 |
611 | Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
612 | resolve(result);
613 | }).catch((err2: Error) => {
614 | resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
615 | });
616 | });
617 | });
618 | }
619 |
620 | private searchNodeInTree(node: any, targetName: string): any {
621 | if (node.name === targetName) {
622 | return node;
623 | }
624 |
625 | if (node.children) {
626 | for (const child of node.children) {
627 | const found = this.searchNodeInTree(child, targetName);
628 | if (found) {
629 | return found;
630 | }
631 | }
632 | }
633 |
634 | return null;
635 | }
636 |
637 | private async getAllNodes(): Promise<ToolResponse> {
638 | return new Promise((resolve) => {
639 | // 尝试查询场景节点树
640 | Editor.Message.request('scene', 'query-node-tree').then((tree: any) => {
641 | const nodes: any[] = [];
642 |
643 | const traverseTree = (node: any) => {
644 | nodes.push({
645 | uuid: node.uuid,
646 | name: node.name,
647 | type: node.type,
648 | active: node.active,
649 | path: this.getNodePath(node)
650 | });
651 |
652 | if (node.children) {
653 | for (const child of node.children) {
654 | traverseTree(child);
655 | }
656 | }
657 | };
658 |
659 | if (tree && tree.children) {
660 | traverseTree(tree);
661 | }
662 |
663 | resolve({
664 | success: true,
665 | data: {
666 | totalNodes: nodes.length,
667 | nodes: nodes
668 | }
669 | });
670 | }).catch((err: Error) => {
671 | // 备用方案:使用场景脚本
672 | const options = {
673 | name: 'cocos-mcp-server',
674 | method: 'getAllNodes',
675 | args: []
676 | };
677 |
678 | Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
679 | resolve(result);
680 | }).catch((err2: Error) => {
681 | resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
682 | });
683 | });
684 | });
685 | }
686 |
687 | private getNodePath(node: any): string {
688 | const path = [node.name];
689 | let current = node.parent;
690 | while (current && current.name !== 'Canvas') {
691 | path.unshift(current.name);
692 | current = current.parent;
693 | }
694 | return path.join('/');
695 | }
696 |
697 | private async setNodeProperty(uuid: string, property: string, value: any): Promise<ToolResponse> {
698 | return new Promise((resolve) => {
699 | // 尝试直接使用 Editor API 设置节点属性
700 | Editor.Message.request('scene', 'set-property', {
701 | uuid: uuid,
702 | path: property,
703 | dump: {
704 | value: value
705 | }
706 | }).then(() => {
707 | // Get comprehensive verification data including updated node info
708 | this.getNodeInfo(uuid).then((nodeInfo) => {
709 | resolve({
710 | success: true,
711 | message: `Property '${property}' updated successfully`,
712 | data: {
713 | nodeUuid: uuid,
714 | property: property,
715 | newValue: value
716 | },
717 | verificationData: {
718 | nodeInfo: nodeInfo.data,
719 | changeDetails: {
720 | property: property,
721 | value: value,
722 | timestamp: new Date().toISOString()
723 | }
724 | }
725 | });
726 | }).catch(() => {
727 | resolve({
728 | success: true,
729 | message: `Property '${property}' updated successfully (verification failed)`
730 | });
731 | });
732 | }).catch((err: Error) => {
733 | // 如果直接设置失败,尝试使用场景脚本
734 | const options = {
735 | name: 'cocos-mcp-server',
736 | method: 'setNodeProperty',
737 | args: [uuid, property, value]
738 | };
739 |
740 | Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
741 | resolve(result);
742 | }).catch((err2: Error) => {
743 | resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
744 | });
745 | });
746 | });
747 | }
748 |
749 | private async setNodeTransform(args: any): Promise<ToolResponse> {
750 | return new Promise(async (resolve) => {
751 | const { uuid, position, rotation, scale } = args;
752 | const updatePromises: Promise<any>[] = [];
753 | const updates: string[] = [];
754 | const warnings: string[] = [];
755 |
756 | try {
757 | // First get node info to determine if it's 2D or 3D
758 | const nodeInfoResponse = await this.getNodeInfo(uuid);
759 | if (!nodeInfoResponse.success || !nodeInfoResponse.data) {
760 | resolve({ success: false, error: 'Failed to get node information' });
761 | return;
762 | }
763 |
764 | const nodeInfo = nodeInfoResponse.data;
765 | const is2DNode = this.is2DNode(nodeInfo);
766 |
767 | if (position) {
768 | const normalizedPosition = this.normalizeTransformValue(position, 'position', is2DNode);
769 | if (normalizedPosition.warning) {
770 | warnings.push(normalizedPosition.warning);
771 | }
772 |
773 | updatePromises.push(
774 | Editor.Message.request('scene', 'set-property', {
775 | uuid: uuid,
776 | path: 'position',
777 | dump: { value: normalizedPosition.value }
778 | })
779 | );
780 | updates.push('position');
781 | }
782 |
783 | if (rotation) {
784 | const normalizedRotation = this.normalizeTransformValue(rotation, 'rotation', is2DNode);
785 | if (normalizedRotation.warning) {
786 | warnings.push(normalizedRotation.warning);
787 | }
788 |
789 | updatePromises.push(
790 | Editor.Message.request('scene', 'set-property', {
791 | uuid: uuid,
792 | path: 'rotation',
793 | dump: { value: normalizedRotation.value }
794 | })
795 | );
796 | updates.push('rotation');
797 | }
798 |
799 | if (scale) {
800 | const normalizedScale = this.normalizeTransformValue(scale, 'scale', is2DNode);
801 | if (normalizedScale.warning) {
802 | warnings.push(normalizedScale.warning);
803 | }
804 |
805 | updatePromises.push(
806 | Editor.Message.request('scene', 'set-property', {
807 | uuid: uuid,
808 | path: 'scale',
809 | dump: { value: normalizedScale.value }
810 | })
811 | );
812 | updates.push('scale');
813 | }
814 |
815 | if (updatePromises.length === 0) {
816 | resolve({ success: false, error: 'No transform properties specified' });
817 | return;
818 | }
819 |
820 | await Promise.all(updatePromises);
821 |
822 | // Verify the changes by getting updated node info
823 | const updatedNodeInfo = await this.getNodeInfo(uuid);
824 | const response: any = {
825 | success: true,
826 | message: `Transform properties updated: ${updates.join(', ')} ${is2DNode ? '(2D node)' : '(3D node)'}`,
827 | updatedProperties: updates,
828 | data: {
829 | nodeUuid: uuid,
830 | nodeType: is2DNode ? '2D' : '3D',
831 | appliedChanges: updates,
832 | transformConstraints: {
833 | position: is2DNode ? 'x, y only (z ignored)' : 'x, y, z all used',
834 | rotation: is2DNode ? 'z only (x, y ignored)' : 'x, y, z all used',
835 | scale: is2DNode ? 'x, y main, z typically 1' : 'x, y, z all used'
836 | }
837 | },
838 | verificationData: {
839 | nodeInfo: updatedNodeInfo.data,
840 | transformDetails: {
841 | originalNodeType: is2DNode ? '2D' : '3D',
842 | appliedTransforms: updates,
843 | timestamp: new Date().toISOString()
844 | },
845 | beforeAfterComparison: {
846 | before: nodeInfo,
847 | after: updatedNodeInfo.data
848 | }
849 | }
850 | };
851 |
852 | if (warnings.length > 0) {
853 | response.warning = warnings.join('; ');
854 | }
855 |
856 | resolve(response);
857 |
858 | } catch (err: any) {
859 | resolve({
860 | success: false,
861 | error: `Failed to update transform: ${err.message}`
862 | });
863 | }
864 | });
865 | }
866 |
867 | private is2DNode(nodeInfo: any): boolean {
868 | // Check if node has 2D-specific components or is under Canvas
869 | const components = nodeInfo.components || [];
870 |
871 | // Check for common 2D components
872 | const has2DComponents = components.some((comp: any) =>
873 | comp.type && (
874 | comp.type.includes('cc.Sprite') ||
875 | comp.type.includes('cc.Label') ||
876 | comp.type.includes('cc.Button') ||
877 | comp.type.includes('cc.Layout') ||
878 | comp.type.includes('cc.Widget') ||
879 | comp.type.includes('cc.Mask') ||
880 | comp.type.includes('cc.Graphics')
881 | )
882 | );
883 |
884 | if (has2DComponents) {
885 | return true;
886 | }
887 |
888 | // Check for 3D-specific components
889 | const has3DComponents = components.some((comp: any) =>
890 | comp.type && (
891 | comp.type.includes('cc.MeshRenderer') ||
892 | comp.type.includes('cc.Camera') ||
893 | comp.type.includes('cc.Light') ||
894 | comp.type.includes('cc.DirectionalLight') ||
895 | comp.type.includes('cc.PointLight') ||
896 | comp.type.includes('cc.SpotLight')
897 | )
898 | );
899 |
900 | if (has3DComponents) {
901 | return false;
902 | }
903 |
904 | // Default heuristic: if z position is 0 and hasn't been changed, likely 2D
905 | const position = nodeInfo.position;
906 | if (position && Math.abs(position.z) < 0.001) {
907 | return true;
908 | }
909 |
910 | // Default to 3D if uncertain
911 | return false;
912 | }
913 |
914 | private normalizeTransformValue(value: any, type: 'position' | 'rotation' | 'scale', is2D: boolean): { value: any, warning?: string } {
915 | const result = { ...value };
916 | let warning: string | undefined;
917 |
918 | if (is2D) {
919 | switch (type) {
920 | case 'position':
921 | if (value.z !== undefined && Math.abs(value.z) > 0.001) {
922 | warning = `2D node: z position (${value.z}) ignored, set to 0`;
923 | result.z = 0;
924 | } else if (value.z === undefined) {
925 | result.z = 0;
926 | }
927 | break;
928 |
929 | case 'rotation':
930 | if ((value.x !== undefined && Math.abs(value.x) > 0.001) ||
931 | (value.y !== undefined && Math.abs(value.y) > 0.001)) {
932 | warning = `2D node: x,y rotations ignored, only z rotation applied`;
933 | result.x = 0;
934 | result.y = 0;
935 | } else {
936 | result.x = result.x || 0;
937 | result.y = result.y || 0;
938 | }
939 | result.z = result.z || 0;
940 | break;
941 |
942 | case 'scale':
943 | if (value.z === undefined) {
944 | result.z = 1; // Default scale for 2D
945 | }
946 | break;
947 | }
948 | } else {
949 | // 3D node - ensure all axes are defined
950 | result.x = result.x !== undefined ? result.x : (type === 'scale' ? 1 : 0);
951 | result.y = result.y !== undefined ? result.y : (type === 'scale' ? 1 : 0);
952 | result.z = result.z !== undefined ? result.z : (type === 'scale' ? 1 : 0);
953 | }
954 |
955 | return { value: result, warning };
956 | }
957 |
958 | private async deleteNode(uuid: string): Promise<ToolResponse> {
959 | return new Promise((resolve) => {
960 | Editor.Message.request('scene', 'remove-node', { uuid: uuid }).then(() => {
961 | resolve({
962 | success: true,
963 | message: 'Node deleted successfully'
964 | });
965 | }).catch((err: Error) => {
966 | resolve({ success: false, error: err.message });
967 | });
968 | });
969 | }
970 |
971 | private async moveNode(nodeUuid: string, newParentUuid: string, siblingIndex: number = -1): Promise<ToolResponse> {
972 | return new Promise((resolve) => {
973 | // Use correct set-parent API instead of move-node
974 | Editor.Message.request('scene', 'set-parent', {
975 | parent: newParentUuid,
976 | uuids: [nodeUuid],
977 | keepWorldTransform: false
978 | }).then(() => {
979 | resolve({
980 | success: true,
981 | message: 'Node moved successfully'
982 | });
983 | }).catch((err: Error) => {
984 | resolve({ success: false, error: err.message });
985 | });
986 | });
987 | }
988 |
989 | private async duplicateNode(uuid: string, includeChildren: boolean = true): Promise<ToolResponse> {
990 | return new Promise((resolve) => {
991 | // Note: includeChildren parameter is accepted for future use but not currently implemented
992 | Editor.Message.request('scene', 'duplicate-node', uuid).then((result: any) => {
993 | resolve({
994 | success: true,
995 | data: {
996 | newUuid: result.uuid,
997 | message: 'Node duplicated successfully'
998 | }
999 | });
1000 | }).catch((err: Error) => {
1001 | resolve({ success: false, error: err.message });
1002 | });
1003 | });
1004 | }
1005 |
1006 | private async detectNodeType(uuid: string): Promise<ToolResponse> {
1007 | return new Promise(async (resolve) => {
1008 | try {
1009 | const nodeInfoResponse = await this.getNodeInfo(uuid);
1010 | if (!nodeInfoResponse.success || !nodeInfoResponse.data) {
1011 | resolve({ success: false, error: 'Failed to get node information' });
1012 | return;
1013 | }
1014 |
1015 | const nodeInfo = nodeInfoResponse.data;
1016 | const is2D = this.is2DNode(nodeInfo);
1017 | const components = nodeInfo.components || [];
1018 |
1019 | // Collect detection reasons
1020 | const detectionReasons: string[] = [];
1021 |
1022 | // Check for 2D components
1023 | const twoDComponents = components.filter((comp: any) =>
1024 | comp.type && (
1025 | comp.type.includes('cc.Sprite') ||
1026 | comp.type.includes('cc.Label') ||
1027 | comp.type.includes('cc.Button') ||
1028 | comp.type.includes('cc.Layout') ||
1029 | comp.type.includes('cc.Widget') ||
1030 | comp.type.includes('cc.Mask') ||
1031 | comp.type.includes('cc.Graphics')
1032 | )
1033 | );
1034 |
1035 | // Check for 3D components
1036 | const threeDComponents = components.filter((comp: any) =>
1037 | comp.type && (
1038 | comp.type.includes('cc.MeshRenderer') ||
1039 | comp.type.includes('cc.Camera') ||
1040 | comp.type.includes('cc.Light') ||
1041 | comp.type.includes('cc.DirectionalLight') ||
1042 | comp.type.includes('cc.PointLight') ||
1043 | comp.type.includes('cc.SpotLight')
1044 | )
1045 | );
1046 |
1047 | if (twoDComponents.length > 0) {
1048 | detectionReasons.push(`Has 2D components: ${twoDComponents.map((c: any) => c.type).join(', ')}`);
1049 | }
1050 |
1051 | if (threeDComponents.length > 0) {
1052 | detectionReasons.push(`Has 3D components: ${threeDComponents.map((c: any) => c.type).join(', ')}`);
1053 | }
1054 |
1055 | // Check position for heuristic
1056 | const position = nodeInfo.position;
1057 | if (position && Math.abs(position.z) < 0.001) {
1058 | detectionReasons.push('Z position is ~0 (likely 2D)');
1059 | } else if (position && Math.abs(position.z) > 0.001) {
1060 | detectionReasons.push(`Z position is ${position.z} (likely 3D)`);
1061 | }
1062 |
1063 | if (detectionReasons.length === 0) {
1064 | detectionReasons.push('No specific indicators found, defaulting based on heuristics');
1065 | }
1066 |
1067 | resolve({
1068 | success: true,
1069 | data: {
1070 | nodeUuid: uuid,
1071 | nodeName: nodeInfo.name,
1072 | nodeType: is2D ? '2D' : '3D',
1073 | detectionReasons: detectionReasons,
1074 | components: components.map((comp: any) => ({
1075 | type: comp.type,
1076 | category: this.getComponentCategory(comp.type)
1077 | })),
1078 | position: nodeInfo.position,
1079 | transformConstraints: {
1080 | position: is2D ? 'x, y only (z ignored)' : 'x, y, z all used',
1081 | rotation: is2D ? 'z only (x, y ignored)' : 'x, y, z all used',
1082 | scale: is2D ? 'x, y main, z typically 1' : 'x, y, z all used'
1083 | }
1084 | }
1085 | });
1086 |
1087 | } catch (err: any) {
1088 | resolve({
1089 | success: false,
1090 | error: `Failed to detect node type: ${err.message}`
1091 | });
1092 | }
1093 | });
1094 | }
1095 |
1096 | private getComponentCategory(componentType: string): string {
1097 | if (!componentType) return 'unknown';
1098 |
1099 | if (componentType.includes('cc.Sprite') || componentType.includes('cc.Label') ||
1100 | componentType.includes('cc.Button') || componentType.includes('cc.Layout') ||
1101 | componentType.includes('cc.Widget') || componentType.includes('cc.Mask') ||
1102 | componentType.includes('cc.Graphics')) {
1103 | return '2D';
1104 | }
1105 |
1106 | if (componentType.includes('cc.MeshRenderer') || componentType.includes('cc.Camera') ||
1107 | componentType.includes('cc.Light') || componentType.includes('cc.DirectionalLight') ||
1108 | componentType.includes('cc.PointLight') || componentType.includes('cc.SpotLight')) {
1109 | return '3D';
1110 | }
1111 |
1112 | return 'generic';
1113 | }
1114 | }
```