diff --git a/.gitignore b/.gitignore index cc801b9..bb4fc05 100644 --- a/.gitignore +++ b/.gitignore @@ -31,10 +31,19 @@ yarn-error.log* cocoscreator_projects/games/*/assets/framework cocoscreator_projects/games/*/assets/framework.meta -# ---------- cocos-creator-mcp 扩展:本地依赖与构建产物 ---------- -# clone 后在该目录跑 `npm install && npm run build` 重新生成 -cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/node_modules/ -cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/dist/ +# ---------- cocos-mcp-server 扩展:构建产物与二进制 ---------- +# 商业付费扩展(vberai),非 npm 可重装:dist/sidecar/static 是发行产物+加密载荷+ +# 平台二进制,约 16M,不入库。换机器时从购买渠道重新拷贝整个扩展目录。 +cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/dist/ +cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/sidecar/ +cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/static/ + +# ---------- Claude Code 个人本地配置 ---------- +# .claude/skills/ 入库(团队共享),本地权限覆盖不入库 +.claude/settings.local.json + +# 运行时状态(编辑器每次启动重写) +cocoscreator_projects/YouleNexus/settings/.mcp-port.json # ============================================================ # 说明:以下内容【应当】入库,勿忽略—— diff --git a/.mcp.json b/.mcp.json index f9741c0..f8d1883 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,13 +1,8 @@ { "mcpServers": { "cocos-creator-mcp": { - "command": "node", - "args": [ - "G:/Works/YouleGamesCocosCreator/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/stdio-bridge.js" - ], - "env": { - "COCOS_MCP_URL": "http://127.0.0.1:3000/mcp" - } + "type": "http", + "url": "http://127.0.0.1:3000/mcp" } } } diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/LICENSE b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/LICENSE deleted file mode 100644 index 4dfe64f..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 Yusuke Harada - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/README.md b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/README.md deleted file mode 100644 index fd1bf9b..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/README.md +++ /dev/null @@ -1,539 +0,0 @@ -# Cocos Creator MCP - -[MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server extension for Cocos Creator 3.8+. - -AI assistants like Claude can control Cocos Creator editor through this extension — creating nodes, editing scenes, managing prefabs, building projects, and more. - -## Features - -- **164 Tools** across 13 categories — comprehensive editor automation -- **Streamable HTTP (SSE)** — Native support for MCP's Streamable HTTP transport -- **JSON-RPC 2.0** — Standard MCP protocol compliance -- **Prefab Property Persistence** — Component properties are correctly preserved when saving prefabs -- **Preview in Editor** — Start editor preview programmatically (no manual button click needed) -- **Screenshot Capture** — Capture editor window and game preview screenshots (WebP / PNG) -- **Video Recording** — Record game preview canvas to video (MP4 / WebM) via Preview Recorder panel -- **Game Command Control** — Send commands to running game preview (screenshot, click, navigate, state, inspect) -- **Client Scripts** — Drop-in TypeScript files for game preview integration (`client/`) -- **Auto Start** — Server starts automatically when the extension loads -- **Tool Call Logging** — All tool invocations logged with timing for debugging -- **UUID Validation** — Input validation helpers for better error messages -- **i18n** — English, Japanese, Chinese -- **Regression Tests** — 200+ assertions covering core tool flows - -## Quick Start - -### 1. Install - -Copy or symlink this extension into your Cocos Creator project's `extensions/` directory: - -```bash -# Windows (Junction — no admin required) -mklink /J "your-project\extensions\cocos-creator-mcp" "path\to\cocos-creator-mcp" - -# macOS / Linux -ln -s /path/to/cocos-creator-mcp your-project/extensions/cocos-creator-mcp -``` - -### 2. Build - -```bash -cd cocos-creator-mcp -npm install -npm run build -``` - -### 3. Enable in Cocos Creator - -1. Open your project in Cocos Creator -2. Go to **Extension > Extension Manager** -3. Enable **Cocos Creator MCP** -4. Open the panel: **Extension > Cocos Creator MCP > Open Panel** -5. Click **Start Server** (or set `autoStart: true` in config) - -### 4. Connect from Claude Code - -Pick one of the two transports below. - -#### Option A — stdio bridge (recommended for Claude Code VSCode extension) - -The Claude Code VSCode extension currently has a bug where it unconditionally -tries OAuth Dynamic Client Registration for HTTP-type MCP servers and fails -with `SDK auth failed` (see upstream issues -[#26917](https://github.com/anthropics/claude-code/issues/26917), -[#38102](https://github.com/anthropics/claude-code/issues/38102), -[#29697](https://github.com/anthropics/claude-code/issues/29697)). -To avoid it entirely, use the bundled stdio bridge. It speaks JSON-RPC on -stdin/stdout and forwards to the HTTP server internally. - -```json -{ - "mcpServers": { - "cocos-creator-mcp": { - "command": "node", - "args": [ - "/cocos-creator-mcp/client/stdio-bridge.js" - ] - } - } -} -``` - -Optional env var: `COCOS_MCP_URL` (default `http://127.0.0.1:3000/mcp`). - -#### Option B — direct HTTP - -Works with Claude Code CLI, Cursor, Cline, and other clients that don't force -OAuth on HTTP MCP. The server ships minimal dummy OAuth endpoints -(`/.well-known/oauth-*`, `/oauth/register|authorize|token`) so OAuth-requiring -clients can still complete a pro-forma flow on localhost. - -```json -{ - "mcpServers": { - "cocos-creator-mcp": { - "type": "http", - "url": "http://127.0.0.1:3000/mcp" - } - } -} -``` - -> The dummy OAuth endpoints will be removed once upstream issues -> ([#26917](https://github.com/anthropics/claude-code/issues/26917), -> [#38102](https://github.com/anthropics/claude-code/issues/38102)) -> are resolved or real authentication is introduced. - -### 5. Verify - -```bash -curl http://127.0.0.1:3000/health -# {"status":"ok","tools":164} -``` - -## Available Tools (164) - -
-Scene (6) — Scene lifecycle and hierarchy - -| Tool | Description | -|------|-------------| -| `scene_get_hierarchy` | Get the node tree (with optional component info) | -| `scene_open` | Open a scene by UUID or db:// path | -| `scene_save` | Save the current scene | -| `scene_get_list` | List all .scene files | -| `scene_close` | Close the current scene | -| `scene_get_current` | Get name and UUID of the current scene | -
- -
-Scene Advanced (30) — Undo, clipboard, queries, property manipulation - -| Tool | Description | -|------|-------------| -| `scene_execute_script` | Execute a custom scene script method | -| `scene_snapshot` | Take a snapshot for undo | -| `scene_snapshot_abort` | Abort the current undo snapshot | -| `scene_begin_undo` | Begin recording undo operations | -| `scene_end_undo` | End undo recording | -| `scene_cancel_undo` | Cancel undo recording | -| `scene_query_dirty` | Check if scene has unsaved changes | -| `scene_query_ready` | Check if scene is fully loaded | -| `scene_query_classes` | List all available component classes | -| `scene_query_components` | Query components for a node | -| `scene_query_component_has_script` | Check if a component has a script file | -| `scene_query_node_tree` | Get raw node tree from editor | -| `scene_query_node` | Get full property dump of a node | -| `scene_query_component` | Get full property dump of a component | -| `scene_query_nodes_by_asset` | Find nodes referencing an asset | -| `scene_query_scene_bounds` | Get scene bounding rect | -| `scene_soft_reload` | Soft reload scene | -| `scene_reset_node_transform` | Reset transform to default | -| `scene_reset_property` | Reset a specific property to default | -| `scene_reset_component` | Reset a component to defaults | -| `scene_copy_node` | Copy node to clipboard | -| `scene_paste_node` | Paste node from clipboard | -| `scene_cut_node` | Cut node to clipboard | -| `scene_create` | Create a new empty scene | -| `scene_save_as` | Save scene to a new file | -| `scene_set_parent` | Reparent node(s) with official API | -| `scene_restore_prefab` | Restore prefab node to original state | -| `scene_execute_component_method` | Call a method on a component | -| `scene_move_array_element` | Reorder array property element | -| `scene_remove_array_element` | Remove array property element | -
- -
-Scene View (19) — Gizmo, camera, grid, viewport - -| Tool | Description | -|------|-------------| -| `view_change_gizmo_tool` | Switch gizmo tool (move/rotate/scale/rect) | -| `view_query_gizmo_tool` | Get current gizmo tool | -| `view_change_gizmo_pivot` | Change pivot mode (center/pivot) | -| `view_query_gizmo_pivot` | Get current pivot mode | -| `view_change_gizmo_coordinate` | Change coordinate system (local/global) | -| `view_query_gizmo_coordinate` | Get current coordinate system | -| `view_change_mode_2d_3d` | Switch 2D/3D view | -| `view_query_mode_2d_3d` | Get current view mode | -| `view_set_grid_visible` | Show/hide grid | -| `view_query_grid_visible` | Check grid visibility | -| `view_set_icon_gizmo_3d` | Toggle 3D icon gizmos | -| `view_query_icon_gizmo_3d` | Check 3D icon gizmo state | -| `view_set_icon_gizmo_size` | Set icon gizmo size | -| `view_query_icon_gizmo_size` | Get icon gizmo size | -| `view_focus_on_node` | Focus camera on node(s) | -| `view_align_with_view` | Align node with camera view | -| `view_align_view_with_node` | Align camera with node | -| `view_get_status` | Get all view settings at once | -| `view_reset` | Reset scene view to default | -
- -
-Node (14) — Create, edit, move, delete nodes - -| Tool | Description | -|------|-------------| -| `node_create` | Create node (with optional components) | -| `node_get_info` | Get node details (position, scale, components) | -| `node_find_by_name` | Find nodes by name | -| `node_set_property` | Set node property | -| `node_set_transform` | Set position/rotation/scale at once | -| `node_set_active` | Set node visibility | -| `node_set_layer` | Set node layer | -| `node_delete` | Delete node | -| `node_move` | Move node to new parent | -| `node_duplicate` | Duplicate node | -| `node_get_all` | List all nodes | -| `node_detect_type` | Detect node type (2D/3D/Node) | -| `node_create_tree` | Create a node hierarchy in one call (v1.6) | -| `node_set_layout` | Set UITransform + Widget + color/opacity at once (v1.13) | -
- -
-Component (8) — Add, remove, configure components - -| Tool | Description | -|------|-------------| -| `component_add` | Add component (e.g. `cc.Label`, `cc.Sprite`) | -| `component_remove` | Remove component | -| `component_get_components` | List components on node | -| `component_set_property` | Set component property (Label.string, fontSize, etc.) | -| `component_get_info` | Get full component dump by UUID | -| `component_get_available` | List all available component classes | -| `component_auto_bind` | Auto-match `@property` fields to nodes by name (v1.12) | -| `component_query_enum` | Query enum values of a component property (v1.6) | -
- -
-Prefab (12) — Prefab lifecycle and validation - -| Tool | Description | -|------|-------------| -| `prefab_list` | List all prefabs | -| `prefab_create` | Create prefab from node (properties preserved) | -| `prefab_instantiate` | Instantiate prefab into scene | -| `prefab_get_info` | Get prefab asset info | -| `prefab_update` | Apply prefab changes | -| `prefab_revert` | Revert prefab instance to original | -| `prefab_duplicate` | Copy prefab to new path | -| `prefab_validate` | Validate prefab for broken references | -| `prefab_open` | Open prefab for editing (v1.5) | -| `prefab_close` | Close prefab editing mode and return to scene (v1.5) | -| `prefab_create_and_replace` | Create prefab and replace instance in one call (v1.5) | -| `prefab_create_from_spec` | Create node tree + auto-bind + prefab_create in one call (v1.12) | -
- -
-Asset (18) — CRUD, queries, metadata, dependencies - -| Tool | Description | -|------|-------------| -| `asset_create` | Create new asset | -| `asset_delete` | Delete asset | -| `asset_move` | Move/rename asset | -| `asset_copy` | Copy asset | -| `asset_save` | Save asset | -| `asset_reimport` | Re-import asset | -| `asset_import` | Import external file into project | -| `asset_query_path` | Get file path for UUID | -| `asset_query_uuid` | Get UUID for path | -| `asset_query_url` | Get URL for UUID | -| `asset_get_details` | Get asset metadata | -| `asset_get_dependencies` | Get asset dependencies | -| `asset_open_external` | Open in external editor | -| `asset_save_meta` | Save asset meta/importer settings | -| `asset_generate_available_url` | Generate non-conflicting asset path | -| `asset_query_ready` | Check if asset DB is ready | -| `asset_query_users` | Find assets that reference this asset | -| `asset_query_missing` | Check for missing references | -
- -
-Project (8) — Project info, settings, engine - -| Tool | Description | -|------|-------------| -| `project_get_info` | Get project name and path | -| `project_refresh_assets` | Refresh asset database | -| `project_get_asset_info` | Get asset info by UUID | -| `project_find_asset` | Find assets by glob pattern | -| `project_get_settings` | Get project settings | -| `project_set_settings` | Set a project setting | -| `project_get_engine_info` | Get engine version and paths | -| `project_query_scripts` | Query all script plugins | -
- -
-Preferences (4) — Editor preferences - -| Tool | Description | -|------|-------------| -| `preferences_get` | Get preference value | -| `preferences_set` | Set preference value | -| `preferences_get_all` | Get all preferences for a protocol | -| `preferences_reset` | Reset preference to default | -
- -
-Debug (22) — Editor info, logs, preview, screenshots, recording, game control - -| Tool | Description | -|------|-------------| -| `debug_get_editor_info` | Get editor version and environment | -| `debug_list_messages` | List available Editor messages | -| `debug_execute_script` | Execute scene script method | -| `debug_get_console_logs` | Get console log entries (scene + game preview) | -| `debug_clear_console` | Clear editor console and log buffers | -| `debug_preview` | Start Preview in Editor (play button) | -| `debug_clear_code_cache` | Clear code cache (Developer > Cache) | -| `debug_screenshot` | Capture editor window screenshot | -| `debug_game_command` | Send command to game preview (screenshot/click/navigate/state/inspect) | -| `debug_batch_screenshot` | Navigate to multiple pages and screenshot each | -| `debug_record_start` | Start recording game preview canvas (webm/mp4) | -| `debug_record_stop` | Stop recording and save video file | -| `debug_reload_extension` | Reload this MCP extension (after build) | -| `debug_list_extensions` | List installed extensions | -| `debug_get_extension_info` | Get extension details | -| `debug_get_project_logs` | Read project log entries | -| `debug_search_project_logs` | Search patterns in project logs | -| `debug_get_log_file_info` | Get log file metadata | -| `debug_validate_scene` | Validate scene for common issues | -| `debug_query_devices` | List connected devices | -| `debug_open_url` | Open URL in system browser | -| `debug_wait_compile` | Wait for TypeScript compile to finish (v1.12) | -
- -
-Server (7) — Editor server and network - -| Tool | Description | -|------|-------------| -| `server_query_ip_list` | Get editor server IPs | -| `server_query_port` | Get editor server port | -| `server_get_status` | Get full server status | -| `server_check_connectivity` | Check if editor server is reachable | -| `server_get_network_interfaces` | Get network interface details | -| `server_get_build_hash` | Get build hash of MCP dist files (v1.6) | -| `server_check_code_sync` | Check if runtime matches dist hash (v1.6) | -
- -
-Builder (5) — Build and preview - -| Tool | Description | -|------|-------------| -| `builder_open_panel` | Open Build panel | -| `builder_get_settings` | Get build configuration | -| `builder_query_tasks` | Query active build tasks | -| `builder_run_preview` | Start preview server | -| `builder_stop_preview` | Stop preview server | -
- -
-Reference Image (11) — Scene overlay images - -| Tool | Description | -|------|-------------| -| `refimage_add` | Add a reference image | -| `refimage_remove` | Remove a reference image | -| `refimage_list` | List all reference images | -| `refimage_clear_all` | Remove all reference images | -| `refimage_switch` | Switch active reference image | -| `refimage_set_position` | Set image position | -| `refimage_set_scale` | Set image scale | -| `refimage_set_opacity` | Set image opacity | -| `refimage_query_config` | Get reference image config | -| `refimage_query_current` | Get current active image info | -| `refimage_refresh` | Refresh image display | -
- -## Client Scripts - -The `client/` directory contains TypeScript files for runtime communication between the game preview and the MCP server. Since the extension is installed in `extensions/`, these files can be imported directly — no copying needed. - -### McpConsoleCapture - -Captures `console.log/warn/error` from the game preview and sends them to the MCP server. - -```typescript -// Import from extensions/ (adjust relative path as needed) -import { initMcpConsoleCapture } from "../../extensions/cocos-creator-mcp/client/McpConsoleCapture"; -initMcpConsoleCapture(); -``` - -### McpDebugClient - -Enables AI-driven game control: screenshots, node clicking, and custom commands. - -```typescript -import { initMcpDebugClient } from "../../extensions/cocos-creator-mcp/client/McpDebugClient"; - -initMcpDebugClient({ - customCommands: { - // Add project-specific commands - state: () => ({ success: true, data: { dump: MyDb.dump() } }), - navigate: async (args) => { - await MyRouter.goTo(args.page); - return { success: true }; - }, - }, -}); -``` - -**Built-in commands** (no setup needed): -- `screenshot` — Capture game screen via RenderTexture -- `click` — Click a node by name - -**Custom commands** (project-specific): -- Register any handler via `customCommands` option -- Called via `debug_game_command` MCP tool - -Both scripts silently ignore when the MCP server is not running, so they are safe to leave in development builds. - -## Console Log Capture (Details) - -`debug_get_console_logs` captures logs from two sources: - -### Scene Process Logs (automatic) - -Console output from scene scripts (`console.log/warn/error` in the scene renderer process) is automatically captured. No setup required. - -### Game Preview Logs (opt-in) - -Game code runs in a browser during preview, which is a separate process. To capture game preview logs, your game code needs to send logs to the MCP server's `/log` endpoint. - -**Setup:** - -Add a console capture script to your game project: - -```typescript -const MCP_LOG_URL = "http://127.0.0.1:3000/log"; -const FLUSH_INTERVAL = 500; - -let buffer: Array<{ timestamp: string; level: string; message: string }> = []; - -function hook(level: string, original: (...args: any[]) => void) { - return function (...args: any[]) { - original.apply(console, args); - buffer.push({ - timestamp: new Date().toISOString(), - level, - message: args.map(a => typeof a === "string" ? a : JSON.stringify(a)).join(" "), - }); - }; -} - -console.log = hook("log", console.log); -console.warn = hook("warn", console.warn); -console.error = hook("error", console.error); - -setInterval(() => { - if (buffer.length === 0) return; - const entries = buffer.splice(0, 50); - fetch(MCP_LOG_URL, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(entries), - }).catch(() => {}); // silently ignore if MCP server is not running -}, FLUSH_INTERVAL); -``` - -**`POST /log` format:** - -```json -[ - { "timestamp": "2026-03-26T12:00:00.000Z", "level": "log", "message": "Hello" }, - { "timestamp": "2026-03-26T12:00:01.000Z", "level": "error", "message": "Something failed" } -] -``` - -Both scene and game logs are merged chronologically when retrieved via `debug_get_console_logs`. Each log entry includes a `source` field (`"scene"` or `"game"`) to distinguish the origin. - -## Configuration - -Settings are stored in `{project}/settings/cocos-creator-mcp.json`: - -```json -{ - "port": 3000, - "autoStart": true -} -``` - -| Option | Default | Description | -|--------|---------|-------------| -| `port` | `3000` | HTTP server port | -| `autoStart` | `false` | Start server automatically when extension loads | - -## Testing - -```bash -node test/regression.mjs # default port 3000 -node test/regression.mjs 3001 # custom port -``` - -## Version History - -- **v0.1** — MCP server + scene/node tools (13 tools) -- **v0.5** — Component, prefab, project, debug tools (27 tools) -- **v1.0** — Full tool coverage (145 tools, 13 categories, 224 test assertions) -- **v1.1** — Console log capture (scene process auto-capture + game preview via `/log` endpoint) -- **v1.2** — AI autonomous development: Preview in Editor, screenshot capture, game command control, code cache clear, scene save fix. Client scripts for game preview integration (`client/`) -- **v1.3** — `scene:set-property` for prefab save support, prefab_create overwrite guard, param alias (`component` → `componentType`) -- **v1.5** — `prefab_create_and_replace`, batch `set_property`, `prefab_open` -- **v1.6** — `debug_batch_screenshot`, widget support in `create_tree`, `component_query_enum`, `server_check_code_sync` -- **v1.8.0** — Preview Recorder panel: `debug_record_start` / `debug_record_stop` (MediaRecorder via canvas.captureStream, MP4/WebM, quality presets) -- **v1.8.1** — Fix: `component_set_property` cc.Asset references (cc.Font etc.) falling back to cc.Node when type is unspecified -- **v1.8.2** — Preview Recorder: screenshot button (webp/png toggle, max width), section-based UI layout -- **v1.9.0** — Preview Recorder auto-archive of old recordings + preflight "preview not running" check -- **v1.10.0** — `scene_create` asset-db fallback, stringified args preventive validation, test coverage expansion -- **v1.11.0** — HTTP MCP OAuth workaround (stdio bridge + dummy OAuth endpoints for Claude Code VSCode upstream bug) + dialog prevention for scene switching tools (`force` param, `ensureSceneSafeToSwitch`, `safeSaveScene`) + regression tests for both -- **v1.12.0** — Prefab authoring efficiency: `component_auto_bind` (auto-match `@property` fields to node names), `debug_wait_compile` (wait for TS compile to finish), `prefab_create_from_spec` (create node tree + auto-bind + prefab_create in one call) -- **v1.13.0** — `nodeName` parameter on component/get_components/auto_bind (no UUID required), `screenshot` auto-return option on `component_set_property` / `node_set_layout`, `node_set_layout` unified tool (UITransform + Widget + color/opacity in one call), dialog auto-response for untitled+dirty scenes, shared screenshot / node-resolve utilities -- **v1.14.0** — Widget `_alignFlags` auto-recalc bug fix: `setProperty` / `setProperties` / `node_set_layout` now re-query `isAlign*` values from scene and rebuild `_alignFlags` bitmask after isAlign updates (Editor bug where bitmask was not updated automatically, causing prefabs to save with `_alignFlags: 45` stuck state). Also `node_create` component addition now waits for editor reflection (`waitForComponent`) to fix flaky tests - -## Development - -```bash -npm run watch # Watch mode -npm run build # One-time build -``` - -After building, reload the extension in Cocos Creator: -- **Extension Manager** — disable then re-enable -- **Developer > Reload** — reloads main process -- **Full restart** — required for scene script or new category changes - -## Requirements - -- Cocos Creator 3.8+ -- Node.js 18+ - -## Known Limitations - -- **`scene_create`**: Does not work on Cocos Creator 3.8.x because the underlying `scene:new-scene` Editor message is not exposed on that version. As a workaround, create the `.scene` JSON file directly under `db://assets/` and call `project_refresh_assets` so the editor picks it up. See [#13](https://github.com/harady/cocos-creator-mcp/issues/13) for details. - -## License - -MIT diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/McpConsoleCapture.js b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/McpConsoleCapture.js deleted file mode 100644 index 94edbf7..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/McpConsoleCapture.js +++ /dev/null @@ -1,82 +0,0 @@ -"use strict"; -/** - * McpConsoleCapture — ゲームプレビュー時のconsole.log/warn/errorをMCPサーバーに送信する。 - * - * MCPサーバーの POST /log エンドポイントにバッチ送信する。 - * MCPサーバー未起動時は黙って無視(本番影響なし)。 - * - * Usage: - * import { initMcpConsoleCapture } from "./McpConsoleCapture"; - * initMcpConsoleCapture(); // or initMcpConsoleCapture({ mcpBaseUrl: "http://127.0.0.1:3001" }); - */ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.initMcpConsoleCapture = initMcpConsoleCapture; -exports.stopMcpConsoleCapture = stopMcpConsoleCapture; -let _buffer = []; -let _timer = null; -let _initialized = false; -let _config; -function formatArgs(args) { - return args.map(a => { - if (typeof a === "string") - return a; - if (a instanceof Error) - return `${a.message}\n${a.stack || ""}`; - try { - return JSON.stringify(a); - } - catch (_a) { - return String(a); - } - }).join(" "); -} -function flush() { - if (_buffer.length === 0) - return; - const entries = _buffer.splice(0, _config.maxBatchSize); - fetch(`${_config.mcpBaseUrl}/log`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(entries), - }).catch(() => { }); -} -/** ゲーム起動時に1回呼ぶ。console.log/warn/errorをフックしてMCPに送信開始 */ -function initMcpConsoleCapture(config) { - var _a, _b, _c; - if (_initialized) - return; - _initialized = true; - _config = { - mcpBaseUrl: (_a = config === null || config === void 0 ? void 0 : config.mcpBaseUrl) !== null && _a !== void 0 ? _a : "http://127.0.0.1:3000", - flushInterval: (_b = config === null || config === void 0 ? void 0 : config.flushInterval) !== null && _b !== void 0 ? _b : 500, - maxBatchSize: (_c = config === null || config === void 0 ? void 0 : config.maxBatchSize) !== null && _c !== void 0 ? _c : 50, - }; - const originalLog = console.log; - const originalWarn = console.warn; - const originalError = console.error; - function hook(level, original) { - return function (...args) { - original.apply(console, args); - _buffer.push({ - timestamp: new Date().toISOString(), - level, - message: formatArgs(args), - }); - }; - } - console.log = hook("log", originalLog); - console.warn = hook("warn", originalWarn); - console.error = hook("error", originalError); - _timer = setInterval(flush, _config.flushInterval); - console.log("[McpConsoleCapture] initialized"); -} -/** 停止 */ -function stopMcpConsoleCapture() { - if (_timer) { - clearInterval(_timer); - _timer = null; - } - flush(); - _initialized = false; -} -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWNwQ29uc29sZUNhcHR1cmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJNY3BDb25zb2xlQ2FwdHVyZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7OztHQVNHOztBQXlDSCxzREE4QkM7QUFHRCxzREFPQztBQWhFRCxJQUFJLE9BQU8sR0FBZSxFQUFFLENBQUM7QUFDN0IsSUFBSSxNQUFNLEdBQVEsSUFBSSxDQUFDO0FBQ3ZCLElBQUksWUFBWSxHQUFHLEtBQUssQ0FBQztBQUN6QixJQUFJLE9BQTBDLENBQUM7QUFFL0MsU0FBUyxVQUFVLENBQUMsSUFBVztJQUMzQixPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUU7UUFDaEIsSUFBSSxPQUFPLENBQUMsS0FBSyxRQUFRO1lBQUUsT0FBTyxDQUFDLENBQUM7UUFDcEMsSUFBSSxDQUFDLFlBQVksS0FBSztZQUFFLE9BQU8sR0FBRyxDQUFDLENBQUMsT0FBTyxLQUFLLENBQUMsQ0FBQyxLQUFLLElBQUksRUFBRSxFQUFFLENBQUM7UUFDaEUsSUFBSSxDQUFDO1lBQUMsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQUMsQ0FBQztRQUFDLFdBQU0sQ0FBQztZQUFDLE9BQU8sTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQUMsQ0FBQztJQUNqRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDakIsQ0FBQztBQUVELFNBQVMsS0FBSztJQUNWLElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDO1FBQUUsT0FBTztJQUNqQyxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUMsRUFBRSxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUM7SUFDeEQsS0FBSyxDQUFDLEdBQUcsT0FBTyxDQUFDLFVBQVUsTUFBTSxFQUFFO1FBQy9CLE1BQU0sRUFBRSxNQUFNO1FBQ2QsT0FBTyxFQUFFLEVBQUUsY0FBYyxFQUFFLGtCQUFrQixFQUFFO1FBQy9DLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQztLQUNoQyxDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxHQUFFLENBQUMsQ0FBQyxDQUFDO0FBQ3ZCLENBQUM7QUFFRCx1REFBdUQ7QUFDdkQsU0FBZ0IscUJBQXFCLENBQUMsTUFBZ0M7O0lBQ2xFLElBQUksWUFBWTtRQUFFLE9BQU87SUFDekIsWUFBWSxHQUFHLElBQUksQ0FBQztJQUNwQixPQUFPLEdBQUc7UUFDTixVQUFVLEVBQUUsTUFBQSxNQUFNLGFBQU4sTUFBTSx1QkFBTixNQUFNLENBQUUsVUFBVSxtQ0FBSSx1QkFBdUI7UUFDekQsYUFBYSxFQUFFLE1BQUEsTUFBTSxhQUFOLE1BQU0sdUJBQU4sTUFBTSxDQUFFLGFBQWEsbUNBQUksR0FBRztRQUMzQyxZQUFZLEVBQUUsTUFBQSxNQUFNLGFBQU4sTUFBTSx1QkFBTixNQUFNLENBQUUsWUFBWSxtQ0FBSSxFQUFFO0tBQzNDLENBQUM7SUFFRixNQUFNLFdBQVcsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDO0lBQ2hDLE1BQU0sWUFBWSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUM7SUFDbEMsTUFBTSxhQUFhLEdBQUcsT0FBTyxDQUFDLEtBQUssQ0FBQztJQUVwQyxTQUFTLElBQUksQ0FBQyxLQUF3QixFQUFFLFFBQWtDO1FBQ3RFLE9BQU8sVUFBVSxHQUFHLElBQVc7WUFDM0IsUUFBUSxDQUFDLEtBQUssQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLENBQUM7WUFDOUIsT0FBTyxDQUFDLElBQUksQ0FBQztnQkFDVCxTQUFTLEVBQUUsSUFBSSxJQUFJLEVBQUUsQ0FBQyxXQUFXLEVBQUU7Z0JBQ25DLEtBQUs7Z0JBQ0wsT0FBTyxFQUFFLFVBQVUsQ0FBQyxJQUFJLENBQUM7YUFDNUIsQ0FBQyxDQUFDO1FBQ1AsQ0FBQyxDQUFDO0lBQ04sQ0FBQztJQUVELE9BQU8sQ0FBQyxHQUFHLEdBQUcsSUFBSSxDQUFDLEtBQUssRUFBRSxXQUFXLENBQUMsQ0FBQztJQUN2QyxPQUFPLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsWUFBWSxDQUFDLENBQUM7SUFDMUMsT0FBTyxDQUFDLEtBQUssR0FBRyxJQUFJLENBQUMsT0FBTyxFQUFFLGFBQWEsQ0FBQyxDQUFDO0lBRTdDLE1BQU0sR0FBRyxXQUFXLENBQUMsS0FBSyxFQUFFLE9BQU8sQ0FBQyxhQUFhLENBQUMsQ0FBQztJQUNuRCxPQUFPLENBQUMsR0FBRyxDQUFDLGlDQUFpQyxDQUFDLENBQUM7QUFDbkQsQ0FBQztBQUVELFNBQVM7QUFDVCxTQUFnQixxQkFBcUI7SUFDakMsSUFBSSxNQUFNLEVBQUUsQ0FBQztRQUNULGFBQWEsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUN0QixNQUFNLEdBQUcsSUFBSSxDQUFDO0lBQ2xCLENBQUM7SUFDRCxLQUFLLEVBQUUsQ0FBQztJQUNSLFlBQVksR0FBRyxLQUFLLENBQUM7QUFDekIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogTWNwQ29uc29sZUNhcHR1cmUg4oCUIOOCsuODvOODoOODl+ODrOODk+ODpeODvOaZguOBrmNvbnNvbGUubG9nL3dhcm4vZXJyb3LjgpJNQ1DjgrXjg7zjg5Djg7zjgavpgIHkv6HjgZnjgovjgIJcbiAqXG4gKiBNQ1DjgrXjg7zjg5Djg7zjga4gUE9TVCAvbG9nIOOCqOODs+ODieODneOCpOODs+ODiOOBq+ODkOODg+ODgemAgeS/oeOBmeOCi+OAglxuICogTUNQ44K144O844OQ44O85pyq6LW35YuV5pmC44Gv6buZ44Gj44Gm54Sh6KaW77yI5pys55Wq5b2x6Z+/44Gq44GX77yJ44CCXG4gKlxuICogVXNhZ2U6XG4gKiAgIGltcG9ydCB7IGluaXRNY3BDb25zb2xlQ2FwdHVyZSB9IGZyb20gXCIuL01jcENvbnNvbGVDYXB0dXJlXCI7XG4gKiAgIGluaXRNY3BDb25zb2xlQ2FwdHVyZSgpOyAvLyBvciBpbml0TWNwQ29uc29sZUNhcHR1cmUoeyBtY3BCYXNlVXJsOiBcImh0dHA6Ly8xMjcuMC4wLjE6MzAwMVwiIH0pO1xuICovXG5cbmV4cG9ydCBpbnRlcmZhY2UgTWNwQ29uc29sZUNhcHR1cmVDb25maWcge1xuICAgIC8qKiBNQ1DjgrXjg7zjg5Djg7zjga7jg5njg7zjgrlVUkwgKGRlZmF1bHQ6IFwiaHR0cDovLzEyNy4wLjAuMTozMDAwXCIpICovXG4gICAgbWNwQmFzZVVybD86IHN0cmluZztcbiAgICAvKiog44OQ44OD44OB6YCB5L+h6ZaT6ZqUbXMgKGRlZmF1bHQ6IDUwMCkgKi9cbiAgICBmbHVzaEludGVydmFsPzogbnVtYmVyO1xuICAgIC8qKiAx5Zue44Gu6YCB5L+h5pyA5aSn5Lu25pWwIChkZWZhdWx0OiA1MCkgKi9cbiAgICBtYXhCYXRjaFNpemU/OiBudW1iZXI7XG59XG5cbmludGVyZmFjZSBMb2dFbnRyeSB7XG4gICAgdGltZXN0YW1wOiBzdHJpbmc7XG4gICAgbGV2ZWw6IFwibG9nXCIgfCBcIndhcm5cIiB8IFwiZXJyb3JcIjtcbiAgICBtZXNzYWdlOiBzdHJpbmc7XG59XG5cbmxldCBfYnVmZmVyOiBMb2dFbnRyeVtdID0gW107XG5sZXQgX3RpbWVyOiBhbnkgPSBudWxsO1xubGV0IF9pbml0aWFsaXplZCA9IGZhbHNlO1xubGV0IF9jb25maWc6IFJlcXVpcmVkPE1jcENvbnNvbGVDYXB0dXJlQ29uZmlnPjtcblxuZnVuY3Rpb24gZm9ybWF0QXJncyhhcmdzOiBhbnlbXSk6IHN0cmluZyB7XG4gICAgcmV0dXJuIGFyZ3MubWFwKGEgPT4ge1xuICAgICAgICBpZiAodHlwZW9mIGEgPT09IFwic3RyaW5nXCIpIHJldHVybiBhO1xuICAgICAgICBpZiAoYSBpbnN0YW5jZW9mIEVycm9yKSByZXR1cm4gYCR7YS5tZXNzYWdlfVxcbiR7YS5zdGFjayB8fCBcIlwifWA7XG4gICAgICAgIHRyeSB7IHJldHVybiBKU09OLnN0cmluZ2lmeShhKTsgfSBjYXRjaCB7IHJldHVybiBTdHJpbmcoYSk7IH1cbiAgICB9KS5qb2luKFwiIFwiKTtcbn1cblxuZnVuY3Rpb24gZmx1c2goKTogdm9pZCB7XG4gICAgaWYgKF9idWZmZXIubGVuZ3RoID09PSAwKSByZXR1cm47XG4gICAgY29uc3QgZW50cmllcyA9IF9idWZmZXIuc3BsaWNlKDAsIF9jb25maWcubWF4QmF0Y2hTaXplKTtcbiAgICBmZXRjaChgJHtfY29uZmlnLm1jcEJhc2VVcmx9L2xvZ2AsIHtcbiAgICAgICAgbWV0aG9kOiBcIlBPU1RcIixcbiAgICAgICAgaGVhZGVyczogeyBcIkNvbnRlbnQtVHlwZVwiOiBcImFwcGxpY2F0aW9uL2pzb25cIiB9LFxuICAgICAgICBib2R5OiBKU09OLnN0cmluZ2lmeShlbnRyaWVzKSxcbiAgICB9KS5jYXRjaCgoKSA9PiB7fSk7XG59XG5cbi8qKiDjgrLjg7zjg6Dotbfli5XmmYLjgasx5Zue5ZG844G244CCY29uc29sZS5sb2cvd2Fybi9lcnJvcuOCkuODleODg+OCr+OBl+OBpk1DUOOBq+mAgeS/oemWi+WniyAqL1xuZXhwb3J0IGZ1bmN0aW9uIGluaXRNY3BDb25zb2xlQ2FwdHVyZShjb25maWc/OiBNY3BDb25zb2xlQ2FwdHVyZUNvbmZpZyk6IHZvaWQge1xuICAgIGlmIChfaW5pdGlhbGl6ZWQpIHJldHVybjtcbiAgICBfaW5pdGlhbGl6ZWQgPSB0cnVlO1xuICAgIF9jb25maWcgPSB7XG4gICAgICAgIG1jcEJhc2VVcmw6IGNvbmZpZz8ubWNwQmFzZVVybCA/PyBcImh0dHA6Ly8xMjcuMC4wLjE6MzAwMFwiLFxuICAgICAgICBmbHVzaEludGVydmFsOiBjb25maWc/LmZsdXNoSW50ZXJ2YWwgPz8gNTAwLFxuICAgICAgICBtYXhCYXRjaFNpemU6IGNvbmZpZz8ubWF4QmF0Y2hTaXplID8/IDUwLFxuICAgIH07XG5cbiAgICBjb25zdCBvcmlnaW5hbExvZyA9IGNvbnNvbGUubG9nO1xuICAgIGNvbnN0IG9yaWdpbmFsV2FybiA9IGNvbnNvbGUud2FybjtcbiAgICBjb25zdCBvcmlnaW5hbEVycm9yID0gY29uc29sZS5lcnJvcjtcblxuICAgIGZ1bmN0aW9uIGhvb2sobGV2ZWw6IExvZ0VudHJ5W1wibGV2ZWxcIl0sIG9yaWdpbmFsOiAoLi4uYXJnczogYW55W10pID0+IHZvaWQpIHtcbiAgICAgICAgcmV0dXJuIGZ1bmN0aW9uICguLi5hcmdzOiBhbnlbXSkge1xuICAgICAgICAgICAgb3JpZ2luYWwuYXBwbHkoY29uc29sZSwgYXJncyk7XG4gICAgICAgICAgICBfYnVmZmVyLnB1c2goe1xuICAgICAgICAgICAgICAgIHRpbWVzdGFtcDogbmV3IERhdGUoKS50b0lTT1N0cmluZygpLFxuICAgICAgICAgICAgICAgIGxldmVsLFxuICAgICAgICAgICAgICAgIG1lc3NhZ2U6IGZvcm1hdEFyZ3MoYXJncyksXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICBjb25zb2xlLmxvZyA9IGhvb2soXCJsb2dcIiwgb3JpZ2luYWxMb2cpO1xuICAgIGNvbnNvbGUud2FybiA9IGhvb2soXCJ3YXJuXCIsIG9yaWdpbmFsV2Fybik7XG4gICAgY29uc29sZS5lcnJvciA9IGhvb2soXCJlcnJvclwiLCBvcmlnaW5hbEVycm9yKTtcblxuICAgIF90aW1lciA9IHNldEludGVydmFsKGZsdXNoLCBfY29uZmlnLmZsdXNoSW50ZXJ2YWwpO1xuICAgIGNvbnNvbGUubG9nKFwiW01jcENvbnNvbGVDYXB0dXJlXSBpbml0aWFsaXplZFwiKTtcbn1cblxuLyoqIOWBnOatoiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHN0b3BNY3BDb25zb2xlQ2FwdHVyZSgpOiB2b2lkIHtcbiAgICBpZiAoX3RpbWVyKSB7XG4gICAgICAgIGNsZWFySW50ZXJ2YWwoX3RpbWVyKTtcbiAgICAgICAgX3RpbWVyID0gbnVsbDtcbiAgICB9XG4gICAgZmx1c2goKTtcbiAgICBfaW5pdGlhbGl6ZWQgPSBmYWxzZTtcbn1cbiJdfQ== \ No newline at end of file diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/McpConsoleCapture.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/McpConsoleCapture.ts deleted file mode 100644 index d0b5c81..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/McpConsoleCapture.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * McpConsoleCapture — ゲームプレビュー時のconsole.log/warn/errorをMCPサーバーに送信する。 - * - * MCPサーバーの POST /log エンドポイントにバッチ送信する。 - * MCPサーバー未起動時は黙って無視(本番影響なし)。 - * - * Usage: - * import { initMcpConsoleCapture } from "./McpConsoleCapture"; - * initMcpConsoleCapture(); // or initMcpConsoleCapture({ mcpBaseUrl: "http://127.0.0.1:3001" }); - */ - -export interface McpConsoleCaptureConfig { - /** MCPサーバーのベースURL (default: "http://127.0.0.1:3000") */ - mcpBaseUrl?: string; - /** バッチ送信間隔ms (default: 500) */ - flushInterval?: number; - /** 1回の送信最大件数 (default: 50) */ - maxBatchSize?: number; -} - -interface LogEntry { - timestamp: string; - level: "log" | "warn" | "error"; - message: string; -} - -let _buffer: LogEntry[] = []; -let _timer: any = null; -let _initialized = false; -let _config: Required; - -function formatArgs(args: any[]): string { - return args.map(a => { - if (typeof a === "string") return a; - if (a instanceof Error) return `${a.message}\n${a.stack || ""}`; - try { return JSON.stringify(a); } catch { return String(a); } - }).join(" "); -} - -function flush(): void { - if (_buffer.length === 0) return; - const entries = _buffer.splice(0, _config.maxBatchSize); - fetch(`${_config.mcpBaseUrl}/log`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(entries), - }).catch(() => {}); -} - -/** ゲーム起動時に1回呼ぶ。console.log/warn/errorをフックしてMCPに送信開始 */ -export function initMcpConsoleCapture(config?: McpConsoleCaptureConfig): void { - if (_initialized) return; - _initialized = true; - _config = { - mcpBaseUrl: config?.mcpBaseUrl ?? "http://127.0.0.1:3000", - flushInterval: config?.flushInterval ?? 500, - maxBatchSize: config?.maxBatchSize ?? 50, - }; - - const originalLog = console.log; - const originalWarn = console.warn; - const originalError = console.error; - - function hook(level: LogEntry["level"], original: (...args: any[]) => void) { - return function (...args: any[]) { - original.apply(console, args); - _buffer.push({ - timestamp: new Date().toISOString(), - level, - message: formatArgs(args), - }); - }; - } - - console.log = hook("log", originalLog); - console.warn = hook("warn", originalWarn); - console.error = hook("error", originalError); - - _timer = setInterval(flush, _config.flushInterval); - console.log("[McpConsoleCapture] initialized"); -} - -/** 停止 */ -export function stopMcpConsoleCapture(): void { - if (_timer) { - clearInterval(_timer); - _timer = null; - } - flush(); - _initialized = false; -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/McpDebugClient.js b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/McpDebugClient.js deleted file mode 100644 index cf1a0b5..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/McpDebugClient.js +++ /dev/null @@ -1,176 +0,0 @@ -"use strict"; -/** - * McpDebugClient — MCPサーバーとCocosCreatorプレビューの双方向通信 - * - * MCPサーバーのコマンドキューをポーリングし、コマンドを実行して結果を返す。 - * MCPサーバー未起動時は黙って無視(本番影響なし)。 - * - * ビルトインコマンド: - * - screenshot: RenderTexture経由でゲーム画面キャプチャ - * - click: ノード名指定でクリックイベント発火 - * - * カスタムコマンドを追加可能(プロジェクト固有の機能): - * initMcpDebugClient({ - * customCommands: { - * state: () => ({ success: true, data: myDb.dump() }), - * navigate: async (args) => { await myRouter.go(args.page); return { success: true }; }, - * }, - * }); - */ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.initMcpDebugClient = initMcpDebugClient; -exports.stopMcpDebugClient = stopMcpDebugClient; -const cc_1 = require("cc"); -let _polling = false; -let _timer = null; -let _config; -async function pollCommand() { - if (_polling) - return; - _polling = true; - try { - const res = await fetch(`${_config.mcpBaseUrl}/game/command`); - if (!res.ok) - return; - const cmd = await res.json(); - if (!cmd) - return; - console.log(`[McpDebugClient] command: ${cmd.type}`, cmd.args || ""); - const result = await executeCommand(cmd); - await fetch(`${_config.mcpBaseUrl}/game/result`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(result), - }).catch(() => { }); - } - catch (_a) { - // MCP server not running - } - finally { - _polling = false; - } -} -async function executeCommand(cmd) { - var _a; - try { - // ビルトインコマンド - switch (cmd.type) { - case "screenshot": - return Object.assign({ id: cmd.id }, takeScreenshot()); - case "click": - return Object.assign({ id: cmd.id }, clickNode((_a = cmd.args) === null || _a === void 0 ? void 0 : _a.name)); - } - // カスタムコマンド - const handler = _config.customCommands[cmd.type]; - if (handler) { - const result = await handler(cmd.args); - return Object.assign({ id: cmd.id }, result); - } - return { id: cmd.id, success: false, error: `Unknown command: ${cmd.type}` }; - } - catch (e) { - return { id: cmd.id, success: false, error: e.message || String(e) }; - } -} -// ─── ビルトインコマンド ─── -function takeScreenshot() { - try { - const scene = cc_1.director.getScene(); - if (!scene) - return { success: false, error: "No active scene" }; - let camera = null; - const findCamera = (node) => { - const cam = node.getComponent(cc_1.Camera); - if (cam && cam.enabled) { - camera = cam; - return; - } - for (const child of node.children) { - if (camera) - return; - findCamera(child); - } - }; - findCamera(scene); - if (!camera) - return { success: false, error: "No camera found" }; - const cam = camera; - const width = Math.floor(cam.camera.width); - const height = Math.floor(cam.camera.height); - const rt = new cc_1.RenderTexture(); - rt.reset({ width, height }); - const prevTarget = cam.targetTexture; - cam.targetTexture = rt; - cc_1.director.root.frameMove(0); - cam.targetTexture = prevTarget; - const region = new cc_1.gfx.BufferTextureCopy(); - region.texOffset.x = 0; - region.texOffset.y = 0; - region.texExtent.width = width; - region.texExtent.height = height; - const buffer = new Uint8Array(width * height * 4); - const gfxTex = rt.getGFXTexture(); - cc_1.director.root.device.copyTextureToBuffers(gfxTex, [buffer], [region]); - const cvs = document.createElement("canvas"); - cvs.width = width; - cvs.height = height; - const ctx = cvs.getContext("2d"); - const imageData = ctx.createImageData(width, height); - for (let y = 0; y < height; y++) { - const srcRow = (height - 1 - y) * width * 4; - const dstRow = y * width * 4; - for (let x = 0; x < width * 4; x++) { - imageData.data[dstRow + x] = buffer[srcRow + x]; - } - } - ctx.putImageData(imageData, 0, 0); - const dataUrl = cvs.toDataURL("image/png"); - rt.destroy(); - return { success: true, data: { dataUrl, width, height } }; - } - catch (e) { - return { success: false, error: e.message }; - } -} -function clickNode(name) { - if (!name) - return { success: false, error: "name argument required" }; - const scene = cc_1.director.getScene(); - if (!scene) - return { success: false, error: "No active scene" }; - const found = findNodeByName(scene, name); - if (!found) - return { success: false, error: `Node '${name}' not found` }; - found.emit(cc_1.Button.EventType.CLICK, found); - return { success: true }; -} -function findNodeByName(root, name) { - if (root.name === name) - return root; - for (const child of root.children) { - const found = findNodeByName(child, name); - if (found) - return found; - } - return null; -} -// ─── 初期化・停止 ─── -function initMcpDebugClient(config) { - var _a, _b, _c; - if (_timer) - return; - _config = { - mcpBaseUrl: (_a = config === null || config === void 0 ? void 0 : config.mcpBaseUrl) !== null && _a !== void 0 ? _a : "http://127.0.0.1:3000", - pollInterval: (_b = config === null || config === void 0 ? void 0 : config.pollInterval) !== null && _b !== void 0 ? _b : 500, - customCommands: (_c = config === null || config === void 0 ? void 0 : config.customCommands) !== null && _c !== void 0 ? _c : {}, - }; - _timer = setInterval(pollCommand, _config.pollInterval); - console.log("[McpDebugClient] initialized"); -} -function stopMcpDebugClient() { - if (_timer) { - clearInterval(_timer); - _timer = null; - } -} -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWNwRGVidWdDbGllbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyJNY3BEZWJ1Z0NsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBaUJHOztBQTRKSCxnREFTQztBQUVELGdEQUtDO0FBMUtELDJCQUF3RTtBQWlCeEUsSUFBSSxRQUFRLEdBQUcsS0FBSyxDQUFDO0FBQ3JCLElBQUksTUFBTSxHQUFRLElBQUksQ0FBQztBQUN2QixJQUFJLE9BQW9KLENBQUM7QUFFekosS0FBSyxVQUFVLFdBQVc7SUFDdEIsSUFBSSxRQUFRO1FBQUUsT0FBTztJQUNyQixRQUFRLEdBQUcsSUFBSSxDQUFDO0lBQ2hCLElBQUksQ0FBQztRQUNELE1BQU0sR0FBRyxHQUFHLE1BQU0sS0FBSyxDQUFDLEdBQUcsT0FBTyxDQUFDLFVBQVUsZUFBZSxDQUFDLENBQUM7UUFDOUQsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFO1lBQUUsT0FBTztRQUNwQixNQUFNLEdBQUcsR0FBc0IsTUFBTSxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDaEQsSUFBSSxDQUFDLEdBQUc7WUFBRSxPQUFPO1FBRWpCLE9BQU8sQ0FBQyxHQUFHLENBQUMsNkJBQTZCLEdBQUcsQ0FBQyxJQUFJLEVBQUUsRUFBRSxHQUFHLENBQUMsSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQ3JFLE1BQU0sTUFBTSxHQUFHLE1BQU0sY0FBYyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBRXpDLE1BQU0sS0FBSyxDQUFDLEdBQUcsT0FBTyxDQUFDLFVBQVUsY0FBYyxFQUFFO1lBQzdDLE1BQU0sRUFBRSxNQUFNO1lBQ2QsT0FBTyxFQUFFLEVBQUUsY0FBYyxFQUFFLGtCQUFrQixFQUFFO1lBQy9DLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQztTQUMvQixDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRSxHQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ3ZCLENBQUM7SUFBQyxXQUFNLENBQUM7UUFDTCx5QkFBeUI7SUFDN0IsQ0FBQztZQUFTLENBQUM7UUFDUCxRQUFRLEdBQUcsS0FBSyxDQUFDO0lBQ3JCLENBQUM7QUFDTCxDQUFDO0FBRUQsS0FBSyxVQUFVLGNBQWMsQ0FBQyxHQUFlOztJQUN6QyxJQUFJLENBQUM7UUFDRCxZQUFZO1FBQ1osUUFBUSxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDZixLQUFLLFlBQVk7Z0JBQ2IsdUJBQVMsRUFBRSxFQUFFLEdBQUcsQ0FBQyxFQUFFLElBQUssY0FBYyxFQUFFLEVBQUc7WUFDL0MsS0FBSyxPQUFPO2dCQUNSLHVCQUFTLEVBQUUsRUFBRSxHQUFHLENBQUMsRUFBRSxJQUFLLFNBQVMsQ0FBQyxNQUFBLEdBQUcsQ0FBQyxJQUFJLDBDQUFFLElBQUksQ0FBQyxFQUFHO1FBQzVELENBQUM7UUFDRCxXQUFXO1FBQ1gsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDakQsSUFBSSxPQUFPLEVBQUUsQ0FBQztZQUNWLE1BQU0sTUFBTSxHQUFHLE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUN2Qyx1QkFBUyxFQUFFLEVBQUUsR0FBRyxDQUFDLEVBQUUsSUFBSyxNQUFNLEVBQUc7UUFDckMsQ0FBQztRQUNELE9BQU8sRUFBRSxFQUFFLEVBQUUsR0FBRyxDQUFDLEVBQUUsRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxvQkFBb0IsR0FBRyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUM7SUFDakYsQ0FBQztJQUFDLE9BQU8sQ0FBTSxFQUFFLENBQUM7UUFDZCxPQUFPLEVBQUUsRUFBRSxFQUFFLEdBQUcsQ0FBQyxFQUFFLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsQ0FBQyxDQUFDLE9BQU8sSUFBSSxNQUFNLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUN6RSxDQUFDO0FBQ0wsQ0FBQztBQUVELG9CQUFvQjtBQUVwQixTQUFTLGNBQWM7SUFDbkIsSUFBSSxDQUFDO1FBQ0QsTUFBTSxLQUFLLEdBQUcsYUFBUSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ2xDLElBQUksQ0FBQyxLQUFLO1lBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLGlCQUFpQixFQUFFLENBQUM7UUFFaEUsSUFBSSxNQUFNLEdBQWtCLElBQUksQ0FBQztRQUNqQyxNQUFNLFVBQVUsR0FBRyxDQUFDLElBQVUsRUFBRSxFQUFFO1lBQzlCLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxZQUFZLENBQUMsV0FBTSxDQUFDLENBQUM7WUFDdEMsSUFBSSxHQUFHLElBQUksR0FBRyxDQUFDLE9BQU8sRUFBRSxDQUFDO2dCQUFDLE1BQU0sR0FBRyxHQUFHLENBQUM7Z0JBQUMsT0FBTztZQUFDLENBQUM7WUFDakQsS0FBSyxNQUFNLEtBQUssSUFBSSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7Z0JBQ2hDLElBQUksTUFBTTtvQkFBRSxPQUFPO2dCQUNuQixVQUFVLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDdEIsQ0FBQztRQUNMLENBQUMsQ0FBQztRQUNGLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUNsQixJQUFJLENBQUMsTUFBTTtZQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLEtBQUssRUFBRSxpQkFBaUIsRUFBRSxDQUFDO1FBRWpFLE1BQU0sR0FBRyxHQUFHLE1BQWdCLENBQUM7UUFDN0IsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzNDLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUU3QyxNQUFNLEVBQUUsR0FBRyxJQUFJLGtCQUFhLEVBQUUsQ0FBQztRQUMvQixFQUFFLENBQUMsS0FBSyxDQUFDLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxDQUFDLENBQUM7UUFFNUIsTUFBTSxVQUFVLEdBQUcsR0FBRyxDQUFDLGFBQWEsQ0FBQztRQUNyQyxHQUFHLENBQUMsYUFBYSxHQUFHLEVBQUUsQ0FBQztRQUN2QixhQUFRLENBQUMsSUFBSyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUM1QixHQUFHLENBQUMsYUFBYSxHQUFHLFVBQVUsQ0FBQztRQUUvQixNQUFNLE1BQU0sR0FBRyxJQUFJLFFBQUcsQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1FBQzNDLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN2QixNQUFNLENBQUMsU0FBUyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDdkIsTUFBTSxDQUFDLFNBQVMsQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO1FBQy9CLE1BQU0sQ0FBQyxTQUFTLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUVqQyxNQUFNLE1BQU0sR0FBRyxJQUFJLFVBQVUsQ0FBQyxLQUFLLEdBQUcsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ2xELE1BQU0sTUFBTSxHQUFHLEVBQUUsQ0FBQyxhQUFhLEVBQUcsQ0FBQztRQUNuQyxhQUFRLENBQUMsSUFBSyxDQUFDLE1BQU0sQ0FBQyxvQkFBb0IsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7UUFFdkUsTUFBTSxHQUFHLEdBQUcsUUFBUSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUM3QyxHQUFHLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQztRQUNsQixHQUFHLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUNwQixNQUFNLEdBQUcsR0FBRyxHQUFHLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBRSxDQUFDO1FBQ2xDLE1BQU0sU0FBUyxHQUFHLEdBQUcsQ0FBQyxlQUFlLENBQUMsS0FBSyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBRXJELEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxNQUFNLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQztZQUM5QixNQUFNLE1BQU0sR0FBRyxDQUFDLE1BQU0sR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsS0FBSyxHQUFHLENBQUMsQ0FBQztZQUM1QyxNQUFNLE1BQU0sR0FBRyxDQUFDLEdBQUcsS0FBSyxHQUFHLENBQUMsQ0FBQztZQUM3QixLQUFLLElBQUksQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsS0FBSyxHQUFHLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO2dCQUNqQyxTQUFTLENBQUMsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsR0FBRyxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFDO1lBQ3BELENBQUM7UUFDTCxDQUFDO1FBRUQsR0FBRyxDQUFDLFlBQVksQ0FBQyxTQUFTLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO1FBQ2xDLE1BQU0sT0FBTyxHQUFHLEdBQUcsQ0FBQyxTQUFTLENBQUMsV0FBVyxDQUFDLENBQUM7UUFDM0MsRUFBRSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBRWIsT0FBTyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsRUFBRSxDQUFDO0lBQy9ELENBQUM7SUFBQyxPQUFPLENBQU0sRUFBRSxDQUFDO1FBQ2QsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUNoRCxDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQVMsU0FBUyxDQUFDLElBQWE7SUFDNUIsSUFBSSxDQUFDLElBQUk7UUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsd0JBQXdCLEVBQUUsQ0FBQztJQUN0RSxNQUFNLEtBQUssR0FBRyxhQUFRLENBQUMsUUFBUSxFQUFFLENBQUM7SUFDbEMsSUFBSSxDQUFDLEtBQUs7UUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxLQUFLLEVBQUUsaUJBQWlCLEVBQUUsQ0FBQztJQUVoRSxNQUFNLEtBQUssR0FBRyxjQUFjLENBQUMsS0FBSyxFQUFFLElBQUksQ0FBQyxDQUFDO0lBQzFDLElBQUksQ0FBQyxLQUFLO1FBQUUsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLFNBQVMsSUFBSSxhQUFhLEVBQUUsQ0FBQztJQUV6RSxLQUFLLENBQUMsSUFBSSxDQUFDLFdBQU0sQ0FBQyxTQUFTLENBQUMsS0FBSyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQzFDLE9BQU8sRUFBRSxPQUFPLEVBQUUsSUFBSSxFQUFFLENBQUM7QUFDN0IsQ0FBQztBQUVELFNBQVMsY0FBYyxDQUFDLElBQVUsRUFBRSxJQUFZO0lBQzVDLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxJQUFJO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFDcEMsS0FBSyxNQUFNLEtBQUssSUFBSSxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUM7UUFDaEMsTUFBTSxLQUFLLEdBQUcsY0FBYyxDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQztRQUMxQyxJQUFJLEtBQUs7WUFBRSxPQUFPLEtBQUssQ0FBQztJQUM1QixDQUFDO0lBQ0QsT0FBTyxJQUFJLENBQUM7QUFDaEIsQ0FBQztBQUVELGlCQUFpQjtBQUVqQixTQUFnQixrQkFBa0IsQ0FBQyxNQUE2Qjs7SUFDNUQsSUFBSSxNQUFNO1FBQUUsT0FBTztJQUNuQixPQUFPLEdBQUc7UUFDTixVQUFVLEVBQUUsTUFBQSxNQUFNLGFBQU4sTUFBTSx1QkFBTixNQUFNLENBQUUsVUFBVSxtQ0FBSSx1QkFBdUI7UUFDekQsWUFBWSxFQUFFLE1BQUEsTUFBTSxhQUFOLE1BQU0sdUJBQU4sTUFBTSxDQUFFLFlBQVksbUNBQUksR0FBRztRQUN6QyxjQUFjLEVBQUUsTUFBQSxNQUFNLGFBQU4sTUFBTSx1QkFBTixNQUFNLENBQUUsY0FBYyxtQ0FBSSxFQUFFO0tBQy9DLENBQUM7SUFDRixNQUFNLEdBQUcsV0FBVyxDQUFDLFdBQVcsRUFBRSxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUM7SUFDeEQsT0FBTyxDQUFDLEdBQUcsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0FBQ2hELENBQUM7QUFFRCxTQUFnQixrQkFBa0I7SUFDOUIsSUFBSSxNQUFNLEVBQUUsQ0FBQztRQUNULGFBQWEsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUN0QixNQUFNLEdBQUcsSUFBSSxDQUFDO0lBQ2xCLENBQUM7QUFDTCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBNY3BEZWJ1Z0NsaWVudCDigJQgTUNQ44K144O844OQ44O844GoQ29jb3NDcmVhdG9y44OX44Os44OT44Ol44O844Gu5Y+M5pa55ZCR6YCa5L+hXG4gKlxuICogTUNQ44K144O844OQ44O844Gu44Kz44Oe44Oz44OJ44Kt44Ol44O844KS44Od44O844Oq44Oz44Kw44GX44CB44Kz44Oe44Oz44OJ44KS5a6f6KGM44GX44Gm57WQ5p6c44KS6L+U44GZ44CCXG4gKiBNQ1DjgrXjg7zjg5Djg7zmnKrotbfli5XmmYLjga/pu5njgaPjgabnhKHoppbvvIjmnKznlarlvbHpn7/jgarjgZfvvInjgIJcbiAqXG4gKiDjg5Pjg6vjg4jjgqTjg7PjgrPjg57jg7Pjg4k6XG4gKiAgIC0gc2NyZWVuc2hvdDogUmVuZGVyVGV4dHVyZee1jOeUseOBp+OCsuODvOODoOeUu+mdouOCreODo+ODl+ODgeODo1xuICogICAtIGNsaWNrOiDjg47jg7zjg4nlkI3mjIflrprjgafjgq/jg6rjg4Pjgq/jgqTjg5njg7Pjg4jnmbrngatcbiAqXG4gKiDjgqvjgrnjgr/jg6DjgrPjg57jg7Pjg4njgpLov73liqDlj6/og73vvIjjg5fjg63jgrjjgqfjgq/jg4jlm7rmnInjga7mqZ/og73vvIk6XG4gKiAgIGluaXRNY3BEZWJ1Z0NsaWVudCh7XG4gKiAgICAgY3VzdG9tQ29tbWFuZHM6IHtcbiAqICAgICAgIHN0YXRlOiAoKSA9PiAoeyBzdWNjZXNzOiB0cnVlLCBkYXRhOiBteURiLmR1bXAoKSB9KSxcbiAqICAgICAgIG5hdmlnYXRlOiBhc3luYyAoYXJncykgPT4geyBhd2FpdCBteVJvdXRlci5nbyhhcmdzLnBhZ2UpOyByZXR1cm4geyBzdWNjZXNzOiB0cnVlIH07IH0sXG4gKiAgICAgfSxcbiAqICAgfSk7XG4gKi9cblxuaW1wb3J0IHsgZGlyZWN0b3IsIE5vZGUsIEJ1dHRvbiwgQ2FtZXJhLCBSZW5kZXJUZXh0dXJlLCBnZnggfSBmcm9tIFwiY2NcIjtcblxuZXhwb3J0IGludGVyZmFjZSBNY3BEZWJ1Z0NsaWVudENvbmZpZyB7XG4gICAgLyoqIE1DUOOCteODvOODkOODvOOBruODmeODvOOCuVVSTCAoZGVmYXVsdDogXCJodHRwOi8vMTI3LjAuMC4xOjMwMDBcIikgKi9cbiAgICBtY3BCYXNlVXJsPzogc3RyaW5nO1xuICAgIC8qKiDjg53jg7zjg6rjg7PjgrDplpPpmpRtcyAoZGVmYXVsdDogNTAwKSAqL1xuICAgIHBvbGxJbnRlcnZhbD86IG51bWJlcjtcbiAgICAvKiog44OX44Ot44K444Kn44Kv44OI5Zu65pyJ44Gu44Kz44Oe44Oz44OJ44OP44Oz44OJ44Op44O8ICovXG4gICAgY3VzdG9tQ29tbWFuZHM/OiBSZWNvcmQ8c3RyaW5nLCAoYXJnczogYW55KSA9PiBhbnkgfCBQcm9taXNlPGFueT4+O1xufVxuXG5pbnRlcmZhY2UgTWNwQ29tbWFuZCB7XG4gICAgaWQ6IHN0cmluZztcbiAgICB0eXBlOiBzdHJpbmc7XG4gICAgYXJncz86IGFueTtcbn1cblxubGV0IF9wb2xsaW5nID0gZmFsc2U7XG5sZXQgX3RpbWVyOiBhbnkgPSBudWxsO1xubGV0IF9jb25maWc6IFJlcXVpcmVkPFBpY2s8TWNwRGVidWdDbGllbnRDb25maWcsIFwibWNwQmFzZVVybFwiIHwgXCJwb2xsSW50ZXJ2YWxcIj4+ICYgeyBjdXN0b21Db21tYW5kczogUmVjb3JkPHN0cmluZywgKGFyZ3M6IGFueSkgPT4gYW55IHwgUHJvbWlzZTxhbnk+PiB9O1xuXG5hc3luYyBmdW5jdGlvbiBwb2xsQ29tbWFuZCgpOiBQcm9taXNlPHZvaWQ+IHtcbiAgICBpZiAoX3BvbGxpbmcpIHJldHVybjtcbiAgICBfcG9sbGluZyA9IHRydWU7XG4gICAgdHJ5IHtcbiAgICAgICAgY29uc3QgcmVzID0gYXdhaXQgZmV0Y2goYCR7X2NvbmZpZy5tY3BCYXNlVXJsfS9nYW1lL2NvbW1hbmRgKTtcbiAgICAgICAgaWYgKCFyZXMub2spIHJldHVybjtcbiAgICAgICAgY29uc3QgY21kOiBNY3BDb21tYW5kIHwgbnVsbCA9IGF3YWl0IHJlcy5qc29uKCk7XG4gICAgICAgIGlmICghY21kKSByZXR1cm47XG5cbiAgICAgICAgY29uc29sZS5sb2coYFtNY3BEZWJ1Z0NsaWVudF0gY29tbWFuZDogJHtjbWQudHlwZX1gLCBjbWQuYXJncyB8fCBcIlwiKTtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgZXhlY3V0ZUNvbW1hbmQoY21kKTtcblxuICAgICAgICBhd2FpdCBmZXRjaChgJHtfY29uZmlnLm1jcEJhc2VVcmx9L2dhbWUvcmVzdWx0YCwge1xuICAgICAgICAgICAgbWV0aG9kOiBcIlBPU1RcIixcbiAgICAgICAgICAgIGhlYWRlcnM6IHsgXCJDb250ZW50LVR5cGVcIjogXCJhcHBsaWNhdGlvbi9qc29uXCIgfSxcbiAgICAgICAgICAgIGJvZHk6IEpTT04uc3RyaW5naWZ5KHJlc3VsdCksXG4gICAgICAgIH0pLmNhdGNoKCgpID0+IHt9KTtcbiAgICB9IGNhdGNoIHtcbiAgICAgICAgLy8gTUNQIHNlcnZlciBub3QgcnVubmluZ1xuICAgIH0gZmluYWxseSB7XG4gICAgICAgIF9wb2xsaW5nID0gZmFsc2U7XG4gICAgfVxufVxuXG5hc3luYyBmdW5jdGlvbiBleGVjdXRlQ29tbWFuZChjbWQ6IE1jcENvbW1hbmQpOiBQcm9taXNlPGFueT4ge1xuICAgIHRyeSB7XG4gICAgICAgIC8vIOODk+ODq+ODiOOCpOODs+OCs+ODnuODs+ODiVxuICAgICAgICBzd2l0Y2ggKGNtZC50eXBlKSB7XG4gICAgICAgICAgICBjYXNlIFwic2NyZWVuc2hvdFwiOlxuICAgICAgICAgICAgICAgIHJldHVybiB7IGlkOiBjbWQuaWQsIC4uLnRha2VTY3JlZW5zaG90KCkgfTtcbiAgICAgICAgICAgIGNhc2UgXCJjbGlja1wiOlxuICAgICAgICAgICAgICAgIHJldHVybiB7IGlkOiBjbWQuaWQsIC4uLmNsaWNrTm9kZShjbWQuYXJncz8ubmFtZSkgfTtcbiAgICAgICAgfVxuICAgICAgICAvLyDjgqvjgrnjgr/jg6DjgrPjg57jg7Pjg4lcbiAgICAgICAgY29uc3QgaGFuZGxlciA9IF9jb25maWcuY3VzdG9tQ29tbWFuZHNbY21kLnR5cGVdO1xuICAgICAgICBpZiAoaGFuZGxlcikge1xuICAgICAgICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgaGFuZGxlcihjbWQuYXJncyk7XG4gICAgICAgICAgICByZXR1cm4geyBpZDogY21kLmlkLCAuLi5yZXN1bHQgfTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4geyBpZDogY21kLmlkLCBzdWNjZXNzOiBmYWxzZSwgZXJyb3I6IGBVbmtub3duIGNvbW1hbmQ6ICR7Y21kLnR5cGV9YCB9O1xuICAgIH0gY2F0Y2ggKGU6IGFueSkge1xuICAgICAgICByZXR1cm4geyBpZDogY21kLmlkLCBzdWNjZXNzOiBmYWxzZSwgZXJyb3I6IGUubWVzc2FnZSB8fCBTdHJpbmcoZSkgfTtcbiAgICB9XG59XG5cbi8vIOKUgOKUgOKUgCDjg5Pjg6vjg4jjgqTjg7PjgrPjg57jg7Pjg4kg4pSA4pSA4pSAXG5cbmZ1bmN0aW9uIHRha2VTY3JlZW5zaG90KCk6IHsgc3VjY2VzczogYm9vbGVhbjsgZGF0YT86IGFueTsgZXJyb3I/OiBzdHJpbmcgfSB7XG4gICAgdHJ5IHtcbiAgICAgICAgY29uc3Qgc2NlbmUgPSBkaXJlY3Rvci5nZXRTY2VuZSgpO1xuICAgICAgICBpZiAoIXNjZW5lKSByZXR1cm4geyBzdWNjZXNzOiBmYWxzZSwgZXJyb3I6IFwiTm8gYWN0aXZlIHNjZW5lXCIgfTtcblxuICAgICAgICBsZXQgY2FtZXJhOiBDYW1lcmEgfCBudWxsID0gbnVsbDtcbiAgICAgICAgY29uc3QgZmluZENhbWVyYSA9IChub2RlOiBOb2RlKSA9PiB7XG4gICAgICAgICAgICBjb25zdCBjYW0gPSBub2RlLmdldENvbXBvbmVudChDYW1lcmEpO1xuICAgICAgICAgICAgaWYgKGNhbSAmJiBjYW0uZW5hYmxlZCkgeyBjYW1lcmEgPSBjYW07IHJldHVybjsgfVxuICAgICAgICAgICAgZm9yIChjb25zdCBjaGlsZCBvZiBub2RlLmNoaWxkcmVuKSB7XG4gICAgICAgICAgICAgICAgaWYgKGNhbWVyYSkgcmV0dXJuO1xuICAgICAgICAgICAgICAgIGZpbmRDYW1lcmEoY2hpbGQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9O1xuICAgICAgICBmaW5kQ2FtZXJhKHNjZW5lKTtcbiAgICAgICAgaWYgKCFjYW1lcmEpIHJldHVybiB7IHN1Y2Nlc3M6IGZhbHNlLCBlcnJvcjogXCJObyBjYW1lcmEgZm91bmRcIiB9O1xuXG4gICAgICAgIGNvbnN0IGNhbSA9IGNhbWVyYSBhcyBDYW1lcmE7XG4gICAgICAgIGNvbnN0IHdpZHRoID0gTWF0aC5mbG9vcihjYW0uY2FtZXJhLndpZHRoKTtcbiAgICAgICAgY29uc3QgaGVpZ2h0ID0gTWF0aC5mbG9vcihjYW0uY2FtZXJhLmhlaWdodCk7XG5cbiAgICAgICAgY29uc3QgcnQgPSBuZXcgUmVuZGVyVGV4dHVyZSgpO1xuICAgICAgICBydC5yZXNldCh7IHdpZHRoLCBoZWlnaHQgfSk7XG5cbiAgICAgICAgY29uc3QgcHJldlRhcmdldCA9IGNhbS50YXJnZXRUZXh0dXJlO1xuICAgICAgICBjYW0udGFyZ2V0VGV4dHVyZSA9IHJ0O1xuICAgICAgICBkaXJlY3Rvci5yb290IS5mcmFtZU1vdmUoMCk7XG4gICAgICAgIGNhbS50YXJnZXRUZXh0dXJlID0gcHJldlRhcmdldDtcblxuICAgICAgICBjb25zdCByZWdpb24gPSBuZXcgZ2Z4LkJ1ZmZlclRleHR1cmVDb3B5KCk7XG4gICAgICAgIHJlZ2lvbi50ZXhPZmZzZXQueCA9IDA7XG4gICAgICAgIHJlZ2lvbi50ZXhPZmZzZXQueSA9IDA7XG4gICAgICAgIHJlZ2lvbi50ZXhFeHRlbnQud2lkdGggPSB3aWR0aDtcbiAgICAgICAgcmVnaW9uLnRleEV4dGVudC5oZWlnaHQgPSBoZWlnaHQ7XG5cbiAgICAgICAgY29uc3QgYnVmZmVyID0gbmV3IFVpbnQ4QXJyYXkod2lkdGggKiBoZWlnaHQgKiA0KTtcbiAgICAgICAgY29uc3QgZ2Z4VGV4ID0gcnQuZ2V0R0ZYVGV4dHVyZSgpITtcbiAgICAgICAgZGlyZWN0b3Iucm9vdCEuZGV2aWNlLmNvcHlUZXh0dXJlVG9CdWZmZXJzKGdmeFRleCwgW2J1ZmZlcl0sIFtyZWdpb25dKTtcblxuICAgICAgICBjb25zdCBjdnMgPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KFwiY2FudmFzXCIpO1xuICAgICAgICBjdnMud2lkdGggPSB3aWR0aDtcbiAgICAgICAgY3ZzLmhlaWdodCA9IGhlaWdodDtcbiAgICAgICAgY29uc3QgY3R4ID0gY3ZzLmdldENvbnRleHQoXCIyZFwiKSE7XG4gICAgICAgIGNvbnN0IGltYWdlRGF0YSA9IGN0eC5jcmVhdGVJbWFnZURhdGEod2lkdGgsIGhlaWdodCk7XG5cbiAgICAgICAgZm9yIChsZXQgeSA9IDA7IHkgPCBoZWlnaHQ7IHkrKykge1xuICAgICAgICAgICAgY29uc3Qgc3JjUm93ID0gKGhlaWdodCAtIDEgLSB5KSAqIHdpZHRoICogNDtcbiAgICAgICAgICAgIGNvbnN0IGRzdFJvdyA9IHkgKiB3aWR0aCAqIDQ7XG4gICAgICAgICAgICBmb3IgKGxldCB4ID0gMDsgeCA8IHdpZHRoICogNDsgeCsrKSB7XG4gICAgICAgICAgICAgICAgaW1hZ2VEYXRhLmRhdGFbZHN0Um93ICsgeF0gPSBidWZmZXJbc3JjUm93ICsgeF07XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBjdHgucHV0SW1hZ2VEYXRhKGltYWdlRGF0YSwgMCwgMCk7XG4gICAgICAgIGNvbnN0IGRhdGFVcmwgPSBjdnMudG9EYXRhVVJMKFwiaW1hZ2UvcG5nXCIpO1xuICAgICAgICBydC5kZXN0cm95KCk7XG5cbiAgICAgICAgcmV0dXJuIHsgc3VjY2VzczogdHJ1ZSwgZGF0YTogeyBkYXRhVXJsLCB3aWR0aCwgaGVpZ2h0IH0gfTtcbiAgICB9IGNhdGNoIChlOiBhbnkpIHtcbiAgICAgICAgcmV0dXJuIHsgc3VjY2VzczogZmFsc2UsIGVycm9yOiBlLm1lc3NhZ2UgfTtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIGNsaWNrTm9kZShuYW1lPzogc3RyaW5nKTogeyBzdWNjZXNzOiBib29sZWFuOyBlcnJvcj86IHN0cmluZyB9IHtcbiAgICBpZiAoIW5hbWUpIHJldHVybiB7IHN1Y2Nlc3M6IGZhbHNlLCBlcnJvcjogXCJuYW1lIGFyZ3VtZW50IHJlcXVpcmVkXCIgfTtcbiAgICBjb25zdCBzY2VuZSA9IGRpcmVjdG9yLmdldFNjZW5lKCk7XG4gICAgaWYgKCFzY2VuZSkgcmV0dXJuIHsgc3VjY2VzczogZmFsc2UsIGVycm9yOiBcIk5vIGFjdGl2ZSBzY2VuZVwiIH07XG5cbiAgICBjb25zdCBmb3VuZCA9IGZpbmROb2RlQnlOYW1lKHNjZW5lLCBuYW1lKTtcbiAgICBpZiAoIWZvdW5kKSByZXR1cm4geyBzdWNjZXNzOiBmYWxzZSwgZXJyb3I6IGBOb2RlICcke25hbWV9JyBub3QgZm91bmRgIH07XG5cbiAgICBmb3VuZC5lbWl0KEJ1dHRvbi5FdmVudFR5cGUuQ0xJQ0ssIGZvdW5kKTtcbiAgICByZXR1cm4geyBzdWNjZXNzOiB0cnVlIH07XG59XG5cbmZ1bmN0aW9uIGZpbmROb2RlQnlOYW1lKHJvb3Q6IE5vZGUsIG5hbWU6IHN0cmluZyk6IE5vZGUgfCBudWxsIHtcbiAgICBpZiAocm9vdC5uYW1lID09PSBuYW1lKSByZXR1cm4gcm9vdDtcbiAgICBmb3IgKGNvbnN0IGNoaWxkIG9mIHJvb3QuY2hpbGRyZW4pIHtcbiAgICAgICAgY29uc3QgZm91bmQgPSBmaW5kTm9kZUJ5TmFtZShjaGlsZCwgbmFtZSk7XG4gICAgICAgIGlmIChmb3VuZCkgcmV0dXJuIGZvdW5kO1xuICAgIH1cbiAgICByZXR1cm4gbnVsbDtcbn1cblxuLy8g4pSA4pSA4pSAIOWIneacn+WMluODu+WBnOatoiDilIDilIDilIBcblxuZXhwb3J0IGZ1bmN0aW9uIGluaXRNY3BEZWJ1Z0NsaWVudChjb25maWc/OiBNY3BEZWJ1Z0NsaWVudENvbmZpZyk6IHZvaWQge1xuICAgIGlmIChfdGltZXIpIHJldHVybjtcbiAgICBfY29uZmlnID0ge1xuICAgICAgICBtY3BCYXNlVXJsOiBjb25maWc/Lm1jcEJhc2VVcmwgPz8gXCJodHRwOi8vMTI3LjAuMC4xOjMwMDBcIixcbiAgICAgICAgcG9sbEludGVydmFsOiBjb25maWc/LnBvbGxJbnRlcnZhbCA/PyA1MDAsXG4gICAgICAgIGN1c3RvbUNvbW1hbmRzOiBjb25maWc/LmN1c3RvbUNvbW1hbmRzID8/IHt9LFxuICAgIH07XG4gICAgX3RpbWVyID0gc2V0SW50ZXJ2YWwocG9sbENvbW1hbmQsIF9jb25maWcucG9sbEludGVydmFsKTtcbiAgICBjb25zb2xlLmxvZyhcIltNY3BEZWJ1Z0NsaWVudF0gaW5pdGlhbGl6ZWRcIik7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBzdG9wTWNwRGVidWdDbGllbnQoKTogdm9pZCB7XG4gICAgaWYgKF90aW1lcikge1xuICAgICAgICBjbGVhckludGVydmFsKF90aW1lcik7XG4gICAgICAgIF90aW1lciA9IG51bGw7XG4gICAgfVxufVxuIl19 \ No newline at end of file diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/McpDebugClient.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/McpDebugClient.ts deleted file mode 100644 index 7c7a723..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/McpDebugClient.ts +++ /dev/null @@ -1,315 +0,0 @@ -/** - * McpDebugClient — MCPサーバーとCocosCreatorプレビューの双方向通信 - * - * MCPサーバーのコマンドキューをポーリングし、コマンドを実行して結果を返す。 - * MCPサーバー未起動時は黙って無視(本番影響なし)。 - * - * ビルトインコマンド: - * - screenshot: RenderTexture経由でゲーム画面キャプチャ - * - click: ノード名指定でクリックイベント発火 - * - * カスタムコマンドを追加可能(プロジェクト固有の機能): - * initMcpDebugClient({ - * customCommands: { - * state: () => ({ success: true, data: myDb.dump() }), - * navigate: async (args) => { await myRouter.go(args.page); return { success: true }; }, - * }, - * }); - */ - -import { director, Node, Button, Camera, RenderTexture, gfx } from "cc"; - -export interface McpDebugClientConfig { - /** MCPサーバーのベースURL (default: "http://127.0.0.1:3000") */ - mcpBaseUrl?: string; - /** ポーリング間隔ms (default: 500) */ - pollInterval?: number; - /** プロジェクト固有のコマンドハンドラー */ - customCommands?: Record any | Promise>; -} - -interface McpCommand { - id: string; - type: string; - args?: any; -} - -let _polling = false; -let _timer: any = null; -let _config: Required> & { customCommands: Record any | Promise> }; - -async function pollCommand(): Promise { - if (_polling) return; - _polling = true; - try { - const res = await fetch(`${_config.mcpBaseUrl}/game/command`); - if (!res.ok) return; - const cmd: McpCommand | null = await res.json(); - if (!cmd) return; - - console.log(`[McpDebugClient] command: ${cmd.type}`, cmd.args || ""); - const result = await executeCommand(cmd); - - await fetch(`${_config.mcpBaseUrl}/game/result`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(result), - }).catch(() => {}); - } catch { - // MCP server not running - } finally { - _polling = false; - } -} - -async function executeCommand(cmd: McpCommand): Promise { - try { - // ビルトインコマンド - switch (cmd.type) { - case "screenshot": - return { id: cmd.id, ...takeScreenshot() }; - case "click": - return { id: cmd.id, ...clickNode(cmd.args?.name) }; - case "record_start": - return { id: cmd.id, ...startRecording(cmd.args) }; - case "record_stop": - return { id: cmd.id, ...(await stopRecording()) }; - } - // カスタムコマンド - const handler = _config.customCommands[cmd.type]; - if (handler) { - const result = await handler(cmd.args); - return { id: cmd.id, ...result }; - } - return { id: cmd.id, success: false, error: `Unknown command: ${cmd.type}` }; - } catch (e: any) { - return { id: cmd.id, success: false, error: e.message || String(e) }; - } -} - -// ─── ビルトインコマンド ─── - -function takeScreenshot(): { success: boolean; data?: any; error?: string } { - try { - const scene = director.getScene(); - if (!scene) return { success: false, error: "No active scene" }; - - let camera: Camera | null = null; - const findCamera = (node: Node) => { - const cam = node.getComponent(Camera); - if (cam && cam.enabled) { camera = cam; return; } - for (const child of node.children) { - if (camera) return; - findCamera(child); - } - }; - findCamera(scene); - if (!camera) return { success: false, error: "No camera found" }; - - const cam = camera as Camera; - const width = Math.floor(cam.camera.width); - const height = Math.floor(cam.camera.height); - - const rt = new RenderTexture(); - rt.reset({ width, height }); - - const prevTarget = cam.targetTexture; - cam.targetTexture = rt; - director.root!.frameMove(0); - cam.targetTexture = prevTarget; - - const region = new gfx.BufferTextureCopy(); - region.texOffset.x = 0; - region.texOffset.y = 0; - region.texExtent.width = width; - region.texExtent.height = height; - - const buffer = new Uint8Array(width * height * 4); - const gfxTex = rt.getGFXTexture()!; - director.root!.device.copyTextureToBuffers(gfxTex, [buffer], [region]); - - const cvs = document.createElement("canvas"); - cvs.width = width; - cvs.height = height; - const ctx = cvs.getContext("2d")!; - const imageData = ctx.createImageData(width, height); - - for (let y = 0; y < height; y++) { - const srcRow = (height - 1 - y) * width * 4; - const dstRow = y * width * 4; - for (let x = 0; x < width * 4; x++) { - imageData.data[dstRow + x] = buffer[srcRow + x]; - } - } - - ctx.putImageData(imageData, 0, 0); - const dataUrl = cvs.toDataURL("image/png"); - rt.destroy(); - - return { success: true, data: { dataUrl, width, height } }; - } catch (e: any) { - return { success: false, error: e.message }; - } -} - -function clickNode(name?: string): { success: boolean; error?: string } { - if (!name) return { success: false, error: "name argument required" }; - const scene = director.getScene(); - if (!scene) return { success: false, error: "No active scene" }; - - const found = findNodeByName(scene, name); - if (!found) return { success: false, error: `Node '${name}' not found` }; - - found.emit(Button.EventType.CLICK, found); - return { success: true }; -} - -// ─── 録画(MediaRecorder経由) ─── - -let _mediaRecorder: MediaRecorder | null = null; -let _recordChunks: Blob[] = []; -let _recordStream: MediaStream | null = null; -let _recordId: string | null = null; - -const QUALITY_PRESETS: Record = { - low: 0.15, - medium: 0.25, - high: 0.40, - ultra: 0.60, -}; - -function startRecording(args?: { fps?: number; videoBitsPerSecond?: number; quality?: string; coefficient?: number; format?: "webm" | "mp4" }): { success: boolean; error?: string; data?: any } { - // 前回の録画状態が残っていたら強制クリア - if (_mediaRecorder) { - try { if (_mediaRecorder.state !== "inactive") _mediaRecorder.stop(); } catch {} - _recordStream?.getTracks().forEach(t => { try { t.stop(); } catch {} }); - _mediaRecorder = null; - _recordStream = null; - _recordChunks = []; - _recordId = null; - } - try { - // GameView canvas取得 - const canvas = document.getElementById("GameCanvas") as HTMLCanvasElement - || document.querySelector("canvas") as HTMLCanvasElement; - if (!canvas) return { success: false, error: "canvas not found" }; - - const fps = args?.fps ?? 30; - const quality = args?.quality ?? "medium"; - const coef = args?.coefficient ?? QUALITY_PRESETS[quality] ?? QUALITY_PRESETS.medium; - const autoBps = Math.round(canvas.width * canvas.height * fps * coef); - const bps = args?.videoBitsPerSecond ?? autoBps; - const format = args?.format ?? "mp4"; - _recordStream = canvas.captureStream(fps); - _recordChunks = []; - _recordId = `rec_${new Date().toISOString().replace(/[:.]/g, "-").replace("T", "_").substring(0, 19)}`; - - // 指定フォーマット優先、非対応ならwebmにfallback - const mp4Candidates = [ - "video/mp4;codecs=h264", - "video/mp4;codecs=avc1.42E01E", - "video/mp4", - ]; - const webmCandidates = [ - "video/webm;codecs=vp9", - "video/webm;codecs=vp8", - "video/webm", - ]; - const candidates = format === "mp4" - ? [...mp4Candidates, ...webmCandidates] - : webmCandidates; - let mimeType = ""; - for (const c of candidates) { - if (typeof MediaRecorder !== "undefined" && MediaRecorder.isTypeSupported(c)) { - mimeType = c; - break; - } - } - if (!mimeType) return { success: false, error: "No supported MediaRecorder mimeType" }; - - _mediaRecorder = new MediaRecorder(_recordStream, { mimeType, videoBitsPerSecond: bps }); - _mediaRecorder.ondataavailable = (e: BlobEvent) => { - if (e.data.size > 0) _recordChunks.push(e.data); - }; - _mediaRecorder.start(); - return { success: true, data: { id: _recordId, mimeType, fps, videoBitsPerSecond: bps, quality, canvasWidth: canvas.width, canvasHeight: canvas.height } }; - } catch (e: any) { - _mediaRecorder = null; - _recordStream = null; - return { success: false, error: e.message || String(e) }; - } -} - -async function stopRecording(): Promise<{ success: boolean; error?: string; data?: any }> { - if (!_mediaRecorder || !_recordId) return { success: false, error: "not recording" }; - const id = _recordId; - const mimeType = _mediaRecorder.mimeType; - return new Promise((resolve) => { - _mediaRecorder!.onstop = async () => { - try { - const blob = new Blob(_recordChunks, { type: mimeType }); - _recordStream?.getTracks().forEach(t => t.stop()); - _mediaRecorder = null; - _recordStream = null; - _recordChunks = []; - _recordId = null; - - // Blob → base64 → POST /game/recording - const base64 = await blobToBase64(blob); - const uploadRes = await fetch(`${_config.mcpBaseUrl}/game/recording`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ id, base64, mimeType }), - }); - const uploadData = await uploadRes.json(); - resolve({ success: true, data: { id, size: blob.size, ...uploadData } }); - } catch (e: any) { - resolve({ success: false, error: e.message || String(e) }); - } - }; - _mediaRecorder!.stop(); - }); -} - -function blobToBase64(blob: Blob): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onloadend = () => { - const result = reader.result as string; - // "data:video/webm;base64,XXX" → XXX だけ抽出 - const commaIdx = result.indexOf(","); - resolve(commaIdx >= 0 ? result.substring(commaIdx + 1) : result); - }; - reader.onerror = reject; - reader.readAsDataURL(blob); - }); -} - -function findNodeByName(root: Node, name: string): Node | null { - if (root.name === name) return root; - for (const child of root.children) { - const found = findNodeByName(child, name); - if (found) return found; - } - return null; -} - -// ─── 初期化・停止 ─── - -export function initMcpDebugClient(config?: McpDebugClientConfig): void { - if (_timer) return; - _config = { - mcpBaseUrl: config?.mcpBaseUrl ?? "http://127.0.0.1:3000", - pollInterval: config?.pollInterval ?? 500, - customCommands: config?.customCommands ?? {}, - }; - _timer = setInterval(pollCommand, _config.pollInterval); - console.log("[McpDebugClient] initialized"); -} - -export function stopMcpDebugClient(): void { - if (_timer) { - clearInterval(_timer); - _timer = null; - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/stdio-bridge.js b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/stdio-bridge.js deleted file mode 100644 index 20b7bb6..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/client/stdio-bridge.js +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env node -/** - * cocos-creator-mcp stdio bridge - * - * stdin で line-delimited JSON-RPC を読み取り、HTTP MCP エンドポイントに転送する。 - * レスポンスは stdout に 1 行ずつ書き出す。 - * - * Claude Code VSCode 拡張の HTTP MCP OAuth 強制バグ (#26917 等) を回避するための - * 代替トランスポート。ユーザーは .mcp.json で stdio 型として設定する: - * - * { - * "mcpServers": { - * "cocos-creator-mcp": { - * "command": "node", - * "args": [ - * "C:/path/to/cocos-creator-mcp/client/stdio-bridge.js" - * ] - * } - * } - * } - * - * Optional 環境変数: - * COCOS_MCP_URL - MCP サーバーの URL (default: http://127.0.0.1:3000/mcp) - */ - -"use strict"; - -const http = require("http"); -const { URL } = require("url"); -const readline = require("readline"); - -const DEFAULT_URL = "http://127.0.0.1:3000/mcp"; -const serverUrl = new URL(process.env.COCOS_MCP_URL || DEFAULT_URL); - -let sessionId = null; - -function log(...args) { - // デバッグログは stderr に出す(stdout は JSON-RPC 応答専用) - console.error("[stdio-bridge]", ...args); -} - -function postJsonRpc(body) { - return new Promise((resolve, reject) => { - const headers = { - "Content-Type": "application/json", - "Accept": "application/json", - "Content-Length": Buffer.byteLength(body), - }; - if (sessionId) headers["Mcp-Session-Id"] = sessionId; - - const req = http.request( - { - hostname: serverUrl.hostname, - port: serverUrl.port || 80, - path: serverUrl.pathname, - method: "POST", - headers, - }, - (res) => { - const chunks = []; - res.on("data", (c) => chunks.push(c)); - res.on("end", () => { - const responseSession = res.headers["mcp-session-id"]; - if (responseSession && !sessionId) { - sessionId = responseSession; - log("session established:", sessionId); - } - const text = Buffer.concat(chunks).toString("utf8"); - resolve({ status: res.statusCode || 0, body: text }); - }); - } - ); - - req.on("error", (err) => reject(err)); - req.write(body); - req.end(); - }); -} - -function writeResponse(obj) { - process.stdout.write(JSON.stringify(obj) + "\n"); -} - -async function handleLine(line) { - const trimmed = line.trim(); - if (!trimmed) return; - - let request; - try { - request = JSON.parse(trimmed); - } catch (e) { - writeResponse({ - jsonrpc: "2.0", - id: null, - error: { code: -32700, message: "Parse error", data: e.message }, - }); - return; - } - - try { - const { status, body } = await postJsonRpc(trimmed); - // Notifications (no id) may yield empty body - if (!body) { - if (request.id !== undefined) { - writeResponse({ - jsonrpc: "2.0", - id: request.id, - error: { code: -32603, message: "Empty response from server", data: { status } }, - }); - } - return; - } - // Pass through server response (single JSON object per line) - try { - const parsed = JSON.parse(body); - writeResponse(parsed); - } catch { - // サーバーが SSE 等で返した場合のフォールバック - process.stdout.write(body + (body.endsWith("\n") ? "" : "\n")); - } - } catch (err) { - writeResponse({ - jsonrpc: "2.0", - id: request.id !== undefined ? request.id : null, - error: { - code: -32603, - message: "HTTP bridge error", - data: err && err.message ? err.message : String(err), - }, - }); - } -} - -let inflight = 0; -let stdinClosed = false; - -function maybeExit() { - if (stdinClosed && inflight === 0) { - log("stdin closed, no inflight, exiting"); - process.exit(0); - } -} - -function main() { - log(`starting, target=${serverUrl.href}`); - - const rl = readline.createInterface({ - input: process.stdin, - terminal: false, - }); - - rl.on("line", (line) => { - inflight++; - handleLine(line) - .catch((e) => log("handler error:", e)) - .finally(() => { - inflight--; - maybeExit(); - }); - }); - - rl.on("close", () => { - stdinClosed = true; - maybeExit(); - }); - - process.on("SIGINT", () => process.exit(0)); - process.on("SIGTERM", () => process.exit(0)); -} - -main(); diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/i18n/en.js b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/i18n/en.js deleted file mode 100644 index 2b06dea..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/i18n/en.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict"; - -module.exports = { - description: "MCP (Model Context Protocol) server for Cocos Creator", - panel_title: "Cocos Creator MCP", - open_panel: "Open Panel", - recorder_title: "Preview Recorder", - open_recorder: "Open Recorder", -}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/i18n/ja.js b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/i18n/ja.js deleted file mode 100644 index a40ae30..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/i18n/ja.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict"; - -module.exports = { - description: "Cocos Creator用 MCP (Model Context Protocol) サーバー", - panel_title: "Cocos Creator MCP", - open_panel: "パネルを開く", - recorder_title: "Preview Recorder", - open_recorder: "Previewレコーダー", -}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/i18n/zh.js b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/i18n/zh.js deleted file mode 100644 index 9d2a781..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/i18n/zh.js +++ /dev/null @@ -1,9 +0,0 @@ -"use strict"; - -module.exports = { - description: "Cocos Creator MCP (模型上下文协议) 服务器", - panel_title: "Cocos Creator MCP", - open_panel: "打开面板", - recorder_title: "Preview Recorder", - open_recorder: "打开录制器", -}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/language.json b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/language.json deleted file mode 100644 index e36de65..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/language.json +++ /dev/null @@ -1 +0,0 @@ -nodejs diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/package-lock.json b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/package-lock.json deleted file mode 100644 index da9d72e..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/package-lock.json +++ /dev/null @@ -1,310 +0,0 @@ -{ - "name": "@iflow-mcp/harady-cocos-creator-mcp", - "version": "1.14.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@iflow-mcp/harady-cocos-creator-mcp", - "version": "1.14.0", - "dependencies": { - "vue": "^3.1.4", - "wasm-vips": "^0.0.17" - }, - "bin": { - "iflow-mcp_harady-cocos-creator-mcp": "client/stdio-bridge.js" - }, - "devDependencies": { - "@cocos/creator-types": "^3.8.6", - "@types/node": "^18.17.1", - "typescript": "^5.8.2" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@cocos/creator-types": { - "version": "3.8.7", - "resolved": "https://registry.npmmirror.com/@cocos/creator-types/-/creator-types-3.8.7.tgz", - "integrity": "sha512-QTtuC+w1s1yQ8DPlgFSiIGS+Pko6v2QdrrQRN8/SttEBo53JjyXEWHXpRkg1lGiHq0u/aadm0cN/Y4jtEmis/w==", - "dev": true - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" - }, - "node_modules/@types/node": { - "version": "18.19.130", - "resolved": "https://registry.npmmirror.com/@types/node/-/node-18.19.130.tgz", - "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.39", - "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.39.tgz", - "integrity": "sha512-16KBTEXAJCpDr0mwlw+AZyhu8iyC7R3S2vBwsI7QnWJU6X3WKc9VKeNEZpiMdZ569qWhz9574L3vV55qRL0Vtw==", - "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/shared": "3.5.39", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.39", - "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.39.tgz", - "integrity": "sha512-oQPigALqYbNxTNPvNgSOe+czwVExfbVF02lz8jP0S3AXJiu3jxYDygNUiqSep4ezzW8XgnubqH63My2A7JR/vg==", - "dependencies": { - "@vue/compiler-core": "3.5.39", - "@vue/shared": "3.5.39" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.39", - "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.39.tgz", - "integrity": "sha512-d0ki86iOyN8LoZPBmk5SJWNwHP19CnDDCfuo//+2WJa2g5Ke0Jay983PIBIcSSzldC68I8DrD5GrHV3OSDfodg==", - "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/compiler-core": "3.5.39", - "@vue/compiler-dom": "3.5.39", - "@vue/compiler-ssr": "3.5.39", - "@vue/shared": "3.5.39", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.15", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.39", - "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.39.tgz", - "integrity": "sha512-Ce7/wvwMHai74bdszfXExdazFigYnlF9zgCmEQUcM1j0fOymlouZ7XilTYNo8oUjhlnjYOZbGrcYKuqjz89Ucw==", - "dependencies": { - "@vue/compiler-dom": "3.5.39", - "@vue/shared": "3.5.39" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.39", - "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.39.tgz", - "integrity": "sha512-TpsuBJ9gGlZa5d23XcM2y8EXanz9dZeVDQBXRwzy46ItgvM+rWpzs+UVM0wcRLxGvcav0HE5jz2gNL53xlRAog==", - "dependencies": { - "@vue/shared": "3.5.39" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.39", - "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.39.tgz", - "integrity": "sha512-9GLtNyRvPAUMbX+7ono0RC2j0guo2LXVi8LvcmAooImACUKm0oFf0jjwbX8/H0AE/t1nxhAkn8RSl9PMCzzxZw==", - "dependencies": { - "@vue/reactivity": "3.5.39", - "@vue/shared": "3.5.39" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.39", - "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.39.tgz", - "integrity": "sha512-7Y6aAGboKcXAZ3ECuUy7RrS5yy2r47dhTp2SKaJmYxjopImaVFaNa5Ne66NwGovsrxVAl5S5rwc7m22UG7Lmww==", - "dependencies": { - "@vue/reactivity": "3.5.39", - "@vue/runtime-core": "3.5.39", - "@vue/shared": "3.5.39", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.39", - "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.39.tgz", - "integrity": "sha512-yZSakiAGw85rZfG7UM8akMnIF+FmeiNk47uvHf2nVBBSe+dIKUhZuZq9+XgJhbV3nS5Z4ALH23/MpXofW+mbcw==", - "dependencies": { - "@vue/compiler-ssr": "3.5.39", - "@vue/shared": "3.5.39" - }, - "peerDependencies": { - "vue": "3.5.39" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.39", - "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.39.tgz", - "integrity": "sha512-l1rrBtBfTnmxvtsvdQDXltUUy8S1Y+ZaqdfUzmAnJkTd8Z8rv5v/ytW+TKiqEOWyHPoqtPlNFSs0lhRmYVSHVA==" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" - }, - "node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" - }, - "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/vue": { - "version": "3.5.39", - "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.39.tgz", - "integrity": "sha512-xmZCYabFGcirU8r0fTuvl/LICc1OU620rnqepaJDL/a141ZigkG7AyaxQLdqJ02ZRYzWe6YPaDHeQx7MfknQfA==", - "dependencies": { - "@vue/compiler-dom": "3.5.39", - "@vue/compiler-sfc": "3.5.39", - "@vue/runtime-dom": "3.5.39", - "@vue/server-renderer": "3.5.39", - "@vue/shared": "3.5.39" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/wasm-vips": { - "version": "0.0.17", - "resolved": "https://registry.npmmirror.com/wasm-vips/-/wasm-vips-0.0.17.tgz", - "integrity": "sha512-nhkqUNJDUymImoXGrVfImC4wzIFTb9KfBpAngb7dcEQNPP1gVTx4+WL3VVVDSXQpMsyeacsQDOx0+DM33Rpurg==", - "engines": { - "node": ">=16.4.0" - } - } - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/package.json b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/package.json deleted file mode 100644 index b486352..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/package.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "package_version": 2, - "name": "cocos-creator-mcp", - "version": "1.14.0", - "author": "harady", - "editor": ">=3.8.0", - "scripts": { - "build": "tsc && node scripts/postbuild.js", - "watch": "tsc -w" - }, - "description": "MCP Server for Cocos Creator [2ade3d2fa124]", - "main": "./dist/main.js", - "bin": { - "iflow-mcp_harady-cocos-creator-mcp": "client/stdio-bridge.js" - }, - "dependencies": { - "vue": "^3.1.4", - "wasm-vips": "^0.0.17" - }, - "devDependencies": { - "@cocos/creator-types": "^3.8.6", - "@types/node": "^18.17.1", - "typescript": "^5.8.2" - }, - "panels": { - "default": { - "title": "i18n:cocos-creator-mcp.panel_title", - "type": "dockable", - "main": "dist/panels/default", - "size": { - "min-width": 350, - "min-height": 200, - "width": 450, - "height": 350 - } - }, - "recorder": { - "title": "i18n:cocos-creator-mcp.recorder_title", - "type": "dockable", - "main": "dist/panels/recorder", - "size": { - "min-width": 400, - "min-height": 400, - "width": 500, - "height": 600 - } - } - }, - "contributions": { - "menu": [ - { - "path": "i18n:menu.extension/Cocos Creator MCP", - "label": "i18n:cocos-creator-mcp.open_panel", - "message": "open-panel" - }, - { - "path": "i18n:menu.extension/Cocos Creator MCP", - "label": "i18n:cocos-creator-mcp.open_recorder", - "message": "open-recorder" - } - ], - "messages": { - "open-panel": { - "methods": [ - "openPanel" - ] - }, - "open-recorder": { - "methods": [ - "openRecorder" - ] - }, - "start-server": { - "methods": [ - "startServer" - ] - }, - "stop-server": { - "methods": [ - "stopServer" - ] - }, - "get-server-status": { - "methods": [ - "getServerStatus" - ] - }, - "update-port": { - "methods": [ - "updatePort" - ] - } - }, - "scene": { - "script": "./dist/scene.js", - "methods": [ - "getNodeInfo", - "getAllNodes", - "findNodesByName", - "setNodeProperty", - "setComponentProperty", - "getSceneHierarchy", - "moveNode", - "addComponentToNode", - "removeComponentFromNode" - ] - } - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/package_name b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/package_name deleted file mode 100644 index 557c63f..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/package_name +++ /dev/null @@ -1 +0,0 @@ -@iflow-mcp/harady-cocos-creator-mcp diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/push_info.json b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/push_info.json deleted file mode 100644 index 2fbc338..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/push_info.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "push_platform": "github", - "fork_url": "https://github.com/iflow-mcp/harady-cocos-creator-mcp", - "fork_branch": "iflow" -} \ No newline at end of file diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/scripts/postbuild.js b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/scripts/postbuild.js deleted file mode 100644 index 357aabc..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/scripts/postbuild.js +++ /dev/null @@ -1,98 +0,0 @@ -const fs = require("fs"); -const crypto = require("crypto"); -const path = require("path"); - -// dist/ 配下の全 .js ファイルの内容からハッシュを生成 -// コードが同じなら常に同じ値になる -const distDir = path.join(__dirname, "..", "dist"); -const hash = crypto.createHash("sha256"); - -// dist/ 配下を再帰的に走査して全 .js ファイルを収集 -function collectJsFiles(dir, prefix = "") { - let files = []; - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const relPath = prefix ? `${prefix}/${entry.name}` : entry.name; - if (entry.isDirectory()) { - files = files.concat(collectJsFiles(path.join(dir, entry.name), relPath)); - } else if (entry.name.endsWith(".js")) { - files.push(relPath); - } - } - return files; -} - -const jsFiles = collectJsFiles(distDir).sort(); - -for (const file of jsFiles) { - const content = fs.readFileSync(path.join(distDir, file), "utf8"); - // __BUILD_HASH__ プレースホルダーは除外して計算(自己参照を避ける) - hash.update(content.replace(/__BUILD_HASH__/g, "")); -} - -const buildHash = hash.digest("hex").substring(0, 12); - -// mcp-server.js にハッシュを埋め込む -const serverFile = path.join(distDir, "mcp-server.js"); -const serverContent = fs.readFileSync(serverFile, "utf8"); -fs.writeFileSync(serverFile, serverContent.replace("__BUILD_HASH__", buildHash)); - -// package.json の description にハッシュを付与 -const pkgFile = path.join(__dirname, "..", "package.json"); -const pkg = JSON.parse(fs.readFileSync(pkgFile, "utf8")); -const baseDesc = pkg.description.replace(/\s*\[[a-f0-9]+\]$/, ""); // 既存ハッシュを除去 -pkg.description = `${baseDesc} [${buildHash}]`; -fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 4) + "\n"); - -console.log("BUILD_HASH:", buildHash); - -// ── dist自動同期: ゲームプロジェクトの extensions/ にコピー ── -// SYNC_TARGET 環境変数 or settings ファイルで同期先を指定 -const settingsFile = path.join(__dirname, "..", "sync-targets.json"); -let syncTargets = []; - -if (process.env.SYNC_TARGET) { - syncTargets = process.env.SYNC_TARGET.split(",").map(s => s.trim()); -} else if (fs.existsSync(settingsFile)) { - syncTargets = JSON.parse(fs.readFileSync(settingsFile, "utf8")).targets || []; -} - -if (syncTargets.length > 0) { - const rootDir = path.join(__dirname, ".."); - const filesToSync = ["dist", "client", "i18n", "static", "package.json", "package-lock.json", "node_modules"]; - - for (const target of syncTargets) { - const targetDir = path.resolve(target); - if (!fs.existsSync(path.dirname(targetDir))) { - console.warn(`SYNC SKIP: parent dir not found: ${targetDir}`); - continue; - } - console.log(`SYNC: ${targetDir}`); - for (const entry of filesToSync) { - const src = path.join(rootDir, entry); - const dst = path.join(targetDir, entry); - if (!fs.existsSync(src)) continue; - if (fs.statSync(src).isDirectory()) { - copyDirSync(src, dst); - } else { - fs.mkdirSync(path.dirname(dst), { recursive: true }); - fs.copyFileSync(src, dst); - } - } - console.log(`SYNC DONE: ${targetDir}`); - } -} else { - console.log("SYNC: no targets configured (set SYNC_TARGET or create sync-targets.json)"); -} - -function copyDirSync(src, dst) { - fs.mkdirSync(dst, { recursive: true }); - for (const entry of fs.readdirSync(src, { withFileTypes: true })) { - const srcPath = path.join(src, entry.name); - const dstPath = path.join(dst, entry.name); - if (entry.isDirectory()) { - copyDirSync(srcPath, dstPath); - } else { - fs.copyFileSync(srcPath, dstPath); - } - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/archive.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/archive.ts deleted file mode 100644 index 50e674b..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/archive.ts +++ /dev/null @@ -1,44 +0,0 @@ -import fs from "fs"; -import path from "path"; - -const HOURS_24 = 24 * 60 * 60 * 1000; - -/** - * 24時間以上前のファイルを OLD_yyyyMM フォルダに移動する. - */ -export function archiveOldFiles(dir: string): void { - try { - let files: string[]; - try { - files = fs.readdirSync(dir); - } catch { - return; - } - - const now = Date.now(); - let archived = 0; - - for (const file of files) { - const filePath = path.join(dir, file); - const stat = fs.statSync(filePath); - - if (stat.isDirectory()) continue; - if (now - stat.mtimeMs < HOURS_24) continue; - - const fileDate = new Date(stat.mtimeMs); - const yyyy = fileDate.getFullYear(); - const mm = String(fileDate.getMonth() + 1).padStart(2, "0"); - const archiveDir = path.join(dir, `OLD_${yyyy}${mm}`); - fs.mkdirSync(archiveDir, { recursive: true }); - - fs.renameSync(filePath, path.join(archiveDir, file)); - archived++; - } - - if (archived > 0) { - console.log(`[cocos-creator-mcp] ${archived}件のファイルを月別フォルダにアーカイブ (${dir})`); - } - } catch (e) { - console.warn("[cocos-creator-mcp] アーカイブ失敗:", e); - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/main.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/main.ts deleted file mode 100644 index 717b826..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/main.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { McpServer } from "./mcp-server"; -import { SceneTools } from "./tools/scene-tools"; -import { NodeTools } from "./tools/node-tools"; -import { ComponentTools } from "./tools/component-tools"; -import { PrefabTools } from "./tools/prefab-tools"; -import { ProjectTools } from "./tools/project-tools"; -import { DebugTools } from "./tools/debug-tools"; -import { SceneAdvancedTools } from "./tools/scene-advanced-tools"; -import { SceneViewTools } from "./tools/scene-view-tools"; -import { AssetTools } from "./tools/asset-tools"; -import { PreferencesTools } from "./tools/preferences-tools"; -import { ServerTools } from "./tools/server-tools"; -import { BuilderTools } from "./tools/builder-tools"; -import { ReferenceImageTools } from "./tools/reference-image-tools"; -import { ServerConfig, DEFAULT_CONFIG } from "./types"; -import path from "path"; -import fs from "fs"; - -let server: McpServer | null = null; - -function getSettingsPath(): string { - return path.join(Editor.Project.path, "settings", "cocos-creator-mcp.json"); -} - -function loadConfig(): ServerConfig { - try { - const p = getSettingsPath(); - if (fs.existsSync(p)) { - const data = JSON.parse(fs.readFileSync(p, "utf-8")); - return { ...DEFAULT_CONFIG, ...data }; - } - } catch (e) { - console.warn("[cocos-creator-mcp] Failed to load settings, using defaults"); - } - return { ...DEFAULT_CONFIG }; -} - -function saveConfig(config: ServerConfig): void { - try { - const p = getSettingsPath(); - const dir = path.dirname(p); - if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); - fs.writeFileSync(p, JSON.stringify(config, null, 2), "utf-8"); - } catch (e) { - console.error("[cocos-creator-mcp] Failed to save settings:", e); - } -} - -function createServer(config: ServerConfig): McpServer { - const s = new McpServer(config); - s.register(new SceneTools()); - s.register(new NodeTools()); - const componentTools = new ComponentTools(); - s.register(componentTools); - s.register(new PrefabTools(componentTools)); - s.register(new ProjectTools()); - s.register(new DebugTools()); - s.register(new SceneAdvancedTools()); - s.register(new SceneViewTools()); - s.register(new AssetTools()); - s.register(new PreferencesTools()); - s.register(new ServerTools()); - s.register(new BuilderTools()); - s.register(new ReferenceImageTools()); - return s; -} - -export const methods: Record any> = { - openPanel() { - Editor.Panel.open("cocos-creator-mcp"); - }, - - openRecorder() { - Editor.Panel.open("cocos-creator-mcp.recorder"); - }, - - async startServer() { - if (server?.isRunning) return { running: true, port: server.port }; - const config = loadConfig(); - server = createServer(config); - await server.start(); - return { running: true, port: server.port }; - }, - - async stopServer() { - if (server) { - await server.stop(); - server = null; - } - return { running: false }; - }, - - async updatePort(_port: number) { - const config = loadConfig(); - config.port = _port; - saveConfig(config); - // Restart server if running - if (server?.isRunning) { - await server.stop(); - server = createServer(config); - await server.start(); - } - return { port: _port, running: server?.isRunning ?? false }; - }, - - getServerStatus() { - return { - running: server?.isRunning ?? false, - port: server?.port ?? loadConfig().port, - toolCount: server?.getAllTools().length ?? 0, - toolNames: server?.getAllTools().map((t) => t.name).sort() ?? [], - }; - }, -}; - -export async function load() { - console.log("[cocos-creator-mcp] Extension loaded"); - const config = loadConfig(); - server = createServer(config); - - if (config.autoStart) { - try { - await server.start(); - console.log(`[cocos-creator-mcp] Auto-started on port ${config.port}`); - } catch (e) { - console.error("[cocos-creator-mcp] Auto-start failed:", e); - } - } -} - -export async function unload() { - if (server) { - await server.stop(); - server = null; - } - console.log("[cocos-creator-mcp] Extension unloaded"); -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/mcp-server.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/mcp-server.ts deleted file mode 100644 index ca7458b..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/mcp-server.ts +++ /dev/null @@ -1,542 +0,0 @@ -import http from "http"; -import { ToolCategory, ToolDefinition, JsonRpcRequest, JsonRpcResponse, ServerConfig, DEFAULT_CONFIG } from "./types"; -import { archiveOldFiles } from "./archive"; - -const MCP_PROTOCOL_VERSION = "2024-11-05"; -const SESSION_ID = `cocos-mcp-${Date.now()}-${Math.random().toString(36).substring(2, 10)}`; - -/** ビルド時にコードベースのSHA256ハッシュが埋め込まれる */ -export const BUILD_HASH = "__BUILD_HASH__"; - -// ─── Game Preview Log Buffer ─── - -interface GameLogEntry { - timestamp: string; - level: "log" | "warn" | "error"; - message: string; -} - -const MAX_GAME_LOG_BUFFER = 500; -const _gameLogs: GameLogEntry[] = []; - -/** Access game preview log buffer from debug-tools */ -export function getGameLogs(count: number, level?: string): { logs: GameLogEntry[]; total: number } { - let logs = _gameLogs; - if (level) { - logs = logs.filter(l => l.level === level); - } - return { logs: logs.slice(-count), total: _gameLogs.length }; -} - -export function clearGameLogs(): void { - _gameLogs.length = 0; -} - -// ─── Game Debug Command Queue ─── - -interface GameCommand { - id: string; - type: string; - args?: any; - timestamp: string; -} - -interface GameCommandResult { - id: string; - success: boolean; - data?: any; - error?: string; - timestamp: string; -} - -let _pendingCommand: GameCommand | null = null; -let _commandResult: GameCommandResult | null = null; -let _commandIdCounter = 0; - -/** Queue a command for the game to execute */ -export function queueGameCommand(type: string, args?: any): string { - const id = `cmd_${++_commandIdCounter}_${Date.now()}`; - _pendingCommand = { id, type, args, timestamp: new Date().toISOString() }; - _commandResult = null; - return id; -} - -/** Get the result of the last command (poll until available) */ -export function getCommandResult(): GameCommandResult | null { - return _commandResult; -} - -/** Clear command state */ -export function clearCommandState(): void { - _pendingCommand = null; - _commandResult = null; -} - -// ─── Recording Storage ─── - -interface RecordingInfo { - path: string; - size: number; - createdAt: string; -} - -const _recordings = new Map(); - -/** Get completed recording info by id */ -export function getRecording(id: string): RecordingInfo | undefined { - return _recordings.get(id); -} - -export function setRecording(id: string, info: RecordingInfo): void { - _recordings.set(id, info); -} - -export class McpServer { - private server: http.Server | null = null; - private tools: Map = new Map(); - private toolIndex: Map = new Map(); // toolName -> category - private config: ServerConfig; - - constructor(config?: Partial) { - this.config = { ...DEFAULT_CONFIG, ...config }; - } - - /** Register a tool category */ - register(category: ToolCategory): void { - this.tools.set(category.categoryName, category); - for (const tool of category.getTools()) { - this.toolIndex.set(tool.name, category); - } - } - - /** Get all tool definitions */ - getAllTools(): ToolDefinition[] { - const all: ToolDefinition[] = []; - for (const cat of this.tools.values()) { - all.push(...cat.getTools()); - } - return all; - } - - /** Start HTTP server */ - start(): Promise { - return new Promise((resolve, reject) => { - if (this.server) { - resolve(); - return; - } - - this.server = http.createServer((req, res) => this.handleRequest(req, res)); - this.server.listen(this.config.port, "127.0.0.1", () => { - console.log(`[cocos-creator-mcp] Server started on http://127.0.0.1:${this.config.port}/mcp`); - resolve(); - }); - this.server.on("error", (e) => { - console.error(`[cocos-creator-mcp] Server error:`, e); - reject(e); - }); - }); - } - - /** Stop HTTP server */ - stop(): Promise { - return new Promise((resolve) => { - if (!this.server) { - resolve(); - return; - } - this.server.close(() => { - this.server = null; - console.log("[cocos-creator-mcp] Server stopped"); - resolve(); - }); - }); - } - - get isRunning(): boolean { - return this.server !== null; - } - - get port(): number { - return this.config.port; - } - - private async handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise { - // CORS - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS"); - res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept"); - - if (req.method === "OPTIONS") { - res.writeHead(204); - res.end(); - return; - } - - const url = req.url || "/"; - const origin = `http://127.0.0.1:${this.config.port}`; - - // ─── OAuth endpoints (MCP spec 2025-06-18 / RFC 9728 / RFC 8414 / RFC 7591) ─── - // - // Claude Code の VSCode 拡張は HTTP トランスポートの MCP サーバーに対して - // 無条件で OAuth discovery / DCR を試みる (#26917 等の既知バグ)。 - // cocos-creator-mcp は localhost-only のローカル開発ツールで本物の認証は不要だが、 - // クライアントを満足させるため OAuth エンドポイント群をダミー実装して常時許可する。 - // - // TODO: 以下のいずれかが発生したら削除する - // 1. anthropics/claude-code #26917 / #38102 等の HTTP OAuth バグが修正される - // 2. 本物の認証機構を実装する必要が出る(偽 OAuth と衝突するため) - // 3. MCP spec が PKCE 検証・トークンローテーション必須等に更新される - // 4. stdio ブリッジが十分定着して HTTP transport 自体を deprecate する - - // RFC 9728 Protected Resource Metadata - if (url === "/.well-known/oauth-protected-resource" && req.method === "GET") { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ - resource: `${origin}/mcp`, - authorization_servers: [origin], - bearer_methods_supported: ["header"], - scopes_supported: ["mcp"], - })); - return; - } - - // RFC 8414 Authorization Server Metadata - if (url === "/.well-known/oauth-authorization-server" && req.method === "GET") { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ - issuer: origin, - authorization_endpoint: `${origin}/oauth/authorize`, - token_endpoint: `${origin}/oauth/token`, - registration_endpoint: `${origin}/oauth/register`, - response_types_supported: ["code"], - grant_types_supported: ["authorization_code"], - code_challenge_methods_supported: ["S256", "plain"], - token_endpoint_auth_methods_supported: ["none"], - scopes_supported: ["mcp"], - })); - return; - } - - // RFC 7591 Dynamic Client Registration — accept anything, return dummy client - if (url === "/oauth/register" && req.method === "POST") { - const body = await readBody(req); - let reg: any = {}; - try { reg = JSON.parse(body); } catch { /* ignore */ } - const clientId = `cocos-mcp-client-${Date.now()}`; - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ - client_id: clientId, - client_id_issued_at: Math.floor(Date.now() / 1000), - client_name: reg.client_name || "cocos-creator-mcp client", - redirect_uris: reg.redirect_uris || [], - token_endpoint_auth_method: "none", - grant_types: ["authorization_code"], - response_types: ["code"], - })); - return; - } - - // OAuth authorization endpoint — auto-consent, redirect immediately with code - if (url.startsWith("/oauth/authorize") && req.method === "GET") { - const parsed = new URL(url, origin); - const redirectUri = parsed.searchParams.get("redirect_uri") || ""; - const state = parsed.searchParams.get("state") || ""; - if (!redirectUri) { - res.writeHead(400, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ error: "invalid_request", error_description: "redirect_uri required" })); - return; - } - const code = `cocos-mcp-code-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; - const location = `${redirectUri}${redirectUri.includes("?") ? "&" : "?"}code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`; - res.writeHead(302, { Location: location }); - res.end(); - return; - } - - // OAuth token endpoint — always issue a dummy token - if (url === "/oauth/token" && req.method === "POST") { - await readBody(req); // drain - res.writeHead(200, { - "Content-Type": "application/json", - "Cache-Control": "no-store", - }); - res.end(JSON.stringify({ - access_token: "cocos-mcp-public-token", - token_type: "Bearer", - expires_in: 86400, - scope: "mcp", - })); - return; - } - - // Health check - if (url === "/health" && req.method === "GET") { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ status: "ok", tools: this.getAllTools().length })); - return; - } - - // Game debug command queue — game polls for commands - if (url === "/game/command" && req.method === "GET") { - const cmd = _pendingCommand; - _pendingCommand = null; // consume - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify(cmd)); - return; - } - - // Game debug command result — game posts result - if (url === "/game/result" && req.method === "POST") { - const body = await readBody(req); - try { - _commandResult = JSON.parse(body); - } catch { /* ignore */ } - res.writeHead(204); - res.end(); - return; - } - - // Game preview recording receiver - if (url === "/game/recording" && req.method === "POST") { - const body = await readBody(req); - try { - const { id, base64, mimeType, savePath } = JSON.parse(body); - if (!id || !base64) throw new Error("id/base64 required"); - - const fs = require("fs"); - const path = require("path"); - const buffer = Buffer.from(base64, "base64"); - - // savePath指定があればそこに保存(絶対パスまたはプロジェクト相対パス) - const projectPath = (global as any).Editor?.Project?.path - || process.cwd(); - let dir: string; - if (savePath) { - dir = path.isAbsolute(savePath) ? savePath : path.join(projectPath, savePath); - } else { - dir = path.join(projectPath, "temp", "recordings"); - } - if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); - const mt = (mimeType || "").toLowerCase(); - const ext = mt.includes("webm") ? "webm" - : mt.includes("mp4") ? "mp4" - : "bin"; - const fileName = `${id}.${ext}`; - const filePath = path.join(dir, fileName); - fs.writeFileSync(filePath, buffer); - - setRecording(id, { - path: filePath, - size: buffer.length, - createdAt: new Date().toISOString(), - }); - if (this.config.autoArchiveRecordings) { - archiveOldFiles(dir); - } - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ success: true, path: filePath, size: buffer.length })); - } catch (e: any) { - res.writeHead(400, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ success: false, error: e.message })); - } - return; - } - - // Game preview log receiver - if (url === "/log" && req.method === "POST") { - const body = await readBody(req); - try { - const entries: GameLogEntry[] = JSON.parse(body); - for (const entry of (Array.isArray(entries) ? entries : [entries])) { - _gameLogs.push({ - timestamp: entry.timestamp || new Date().toISOString(), - level: entry.level || "log", - message: entry.message || "", - }); - // __debug_state__ ログから userId を debug-menu.json に保存 - try { - const msg = JSON.parse(entry.message || ""); - if (msg.__debug_state__ && msg.userId) { - const _fs = require("fs"); - const _path = require("path"); - const projectPath = (global as any).Editor?.Project?.path || process.cwd(); - const settingsPath = _path.join(projectPath, "settings", "debug-menu.json"); - _fs.writeFileSync(settingsPath, JSON.stringify({ userId: msg.userId }, null, 2), "utf-8"); - } - } catch { /* not debug_state */ } - } - if (_gameLogs.length > MAX_GAME_LOG_BUFFER) { - _gameLogs.splice(0, _gameLogs.length - MAX_GAME_LOG_BUFFER); - } - } catch { /* ignore malformed */ } - res.writeHead(204); - res.end(); - return; - } - - // MCP endpoint - if (url === "/mcp") { - if (req.method === "GET") { - // SSE keepalive stream - res.writeHead(200, { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - "Connection": "keep-alive", - }); - // Send initial comment to keep connection alive - res.write(": connected\n\n"); - return; - } - - if (req.method === "POST") { - await this.handleMcpPost(req, res); - return; - } - - if (req.method === "DELETE") { - res.writeHead(200, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ ok: true })); - return; - } - } - - // 404 - res.writeHead(404, { "Content-Type": "application/json" }); - res.end(JSON.stringify({ error: "Not found" })); - } - - private async handleMcpPost(req: http.IncomingMessage, res: http.ServerResponse): Promise { - const body = await readBody(req); - let rpc: JsonRpcRequest; - try { - rpc = JSON.parse(body); - } catch { - this.sendJsonRpc(res, { jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }); - return; - } - - const accept = req.headers["accept"] || ""; - const wantSse = accept.includes("text/event-stream"); - - let response: JsonRpcResponse; - - switch (rpc.method) { - case "initialize": - response = { - jsonrpc: "2.0", - id: rpc.id, - result: { - protocolVersion: MCP_PROTOCOL_VERSION, - capabilities: { tools: {} }, - serverInfo: { - name: "cocos-creator-mcp", - version: "1.0.0", - }, - }, - }; - break; - - case "notifications/initialized": - // No response needed for notification - res.writeHead(204, { "Mcp-Session-Id": SESSION_ID }); - res.end(); - return; - - case "tools/list": - response = { - jsonrpc: "2.0", - id: rpc.id, - result: { tools: this.getAllTools() }, - }; - break; - - case "tools/call": { - const toolName = rpc.params?.name; - const args = rpc.params?.arguments || {}; - const category = this.toolIndex.get(toolName); - - if (!category) { - response = { - jsonrpc: "2.0", - id: rpc.id, - error: { code: -32602, message: `Unknown tool: ${toolName}` }, - }; - } else { - try { - const start = Date.now(); - console.log(`[cocos-creator-mcp] ▶ ${toolName}`, Object.keys(args).length > 0 ? JSON.stringify(args).substring(0, 200) : ""); - const timeoutMs = (toolName.startsWith("prefab_") || toolName === "scene_open") ? 120000 : 30000; - const result = await withTimeout(category.execute(toolName, args), timeoutMs, `Tool ${toolName} timed out`); - console.log(`[cocos-creator-mcp] ✓ ${toolName} (${Date.now() - start}ms)`); - response = { - jsonrpc: "2.0", - id: rpc.id, - result, - }; - } catch (e: any) { - console.error(`[cocos-creator-mcp] ✗ ${toolName}:`, e.message || String(e)); - response = { - jsonrpc: "2.0", - id: rpc.id, - error: { code: -32603, message: e.message || String(e) }, - }; - } - } - break; - } - - default: - response = { - jsonrpc: "2.0", - id: rpc.id, - error: { code: -32601, message: `Method not found: ${rpc.method}` }, - }; - } - - if (wantSse) { - this.sendSse(res, [response]); - } else { - this.sendJsonRpc(res, response); - } - } - - private sendJsonRpc(res: http.ServerResponse, data: JsonRpcResponse): void { - res.writeHead(200, { - "Content-Type": "application/json", - "Mcp-Session-Id": SESSION_ID, - }); - res.end(JSON.stringify(data)); - } - - private sendSse(res: http.ServerResponse, messages: JsonRpcResponse[]): void { - res.writeHead(200, { - "Content-Type": "text/event-stream", - "Cache-Control": "no-cache", - "Mcp-Session-Id": SESSION_ID, - }); - for (const msg of messages) { - res.write(`event: message\ndata: ${JSON.stringify(msg)}\n\n`); - } - res.end(); - } -} - -function withTimeout(promise: Promise, ms: number, message: string): Promise { - return new Promise((resolve, reject) => { - const timer = setTimeout(() => reject(new Error(message)), ms); - promise.then( - (v) => { clearTimeout(timer); resolve(v); }, - (e) => { clearTimeout(timer); reject(e); }, - ); - }); -} - -function readBody(req: http.IncomingMessage): Promise { - return new Promise((resolve, reject) => { - const chunks: Buffer[] = []; - req.on("data", (c) => chunks.push(c)); - req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8"))); - req.on("error", reject); - }); -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/node-resolve.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/node-resolve.ts deleted file mode 100644 index e5152d1..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/node-resolve.ts +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Shared node UUID resolution — resolves nodeName to UUID via scene script. - */ - -const EXT_NAME = "cocos-creator-mcp"; - -export interface ResolvedNode { - uuid: string; - name: string; -} - -/** - * Resolve a node UUID from either `uuid` or `nodeName` parameter. - * If `uuid` is provided, it is returned as-is. - * If `nodeName` is provided, searches the scene for a matching node. - * Throws if neither is provided or no node is found. - */ -export async function resolveNodeUuid(args: { uuid?: string; nodeName?: string }): Promise { - if (args.uuid) { - return { uuid: args.uuid, name: "" }; - } - if (!args.nodeName) { - throw new Error("Either 'uuid' or 'nodeName' is required"); - } - const result = await Editor.Message.request("scene", "execute-scene-script", { - name: EXT_NAME, - method: "findNodesByName", - args: [args.nodeName], - }); - if (!result?.success || !result?.data?.length) { - throw new Error(`Node not found: "${args.nodeName}"`); - } - // 最初のマッチを使用 - const node = result.data[0]; - return { uuid: node.uuid, name: node.name || args.nodeName }; -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/panels/default/index.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/panels/default/index.ts deleted file mode 100644 index 18bc350..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/panels/default/index.ts +++ /dev/null @@ -1,117 +0,0 @@ -const { createApp } = require("vue"); - -const panelDataMap = new WeakMap(); - -module.exports = Editor.Panel.define({ - template: ` -
-

Cocos Creator MCP

-
- Status: {{ running ? 'Running' : 'Stopped' }} -
-
- - - Apply -
-
- Start Server - Stop Server -
-
-

Endpoint: http://127.0.0.1:{{ port }}/mcp

-

Tools: {{ toolCount }}

-
-
{{ error }}
-
- `, - style: ` -#app { padding: 12px; font-family: sans-serif; color: #ccc; } -h2 { margin: 0 0 8px 0; font-size: 16px; } -.status { margin: 8px 0; } -.on { color: #4f4; } -.off { color: #f66; } -.port-row { margin: 8px 0; display: flex; align-items: center; gap: 8px; } -.port-row label { font-size: 12px; } -.port-row input { width: 80px; padding: 3px 6px; background: #222; color: #ccc; border: 1px solid #444; border-radius: 3px; font-size: 12px; } -.port-row input:disabled { opacity: 0.5; } -.actions { margin: 8px 0; } -.info { margin: 8px 0; padding: 8px; background: var(--color-normal-fill-emphasis); border-radius: 4px; } -.info p { margin: 4px 0; font-size: 12px; } -.info code { background: #333; padding: 2px 6px; border-radius: 3px; font-size: 11px; } -.error { margin: 8px 0; color: #f66; font-size: 12px; } - `, - $: { app: "#app" }, - ready() { - if (!this.$.app) return; - const app = createApp({ - data() { - return { - running: false, - port: 3000, - editPort: 3000, - toolCount: 0, - error: "", - }; - }, - methods: { - async start(this: any) { - try { - this.error = ""; - const result = await Editor.Message.request("cocos-creator-mcp", "start-server"); - this.running = result.running; - this.port = result.port; - this.editPort = result.port; - await this.refresh(); - } catch (e: any) { - this.error = e.message || String(e); - } - }, - async stop(this: any) { - try { - await Editor.Message.request("cocos-creator-mcp", "stop-server"); - this.running = false; - this.toolCount = 0; - } catch (e: any) { - this.error = e.message || String(e); - } - }, - async applyPort(this: any) { - try { - this.error = ""; - const result = await Editor.Message.request("cocos-creator-mcp", "update-port", this.editPort); - this.port = result.port; - this.running = result.running; - if (this.running) await this.refresh(); - } catch (e: any) { - this.error = e.message || String(e); - } - }, - async refresh(this: any) { - try { - const status = await Editor.Message.request("cocos-creator-mcp", "get-server-status"); - this.running = status.running; - this.port = status.port; - this.editPort = status.port; - this.toolCount = status.toolCount; - } catch (e: any) { - console.warn("[cocos-creator-mcp] refresh failed:", e); - } - }, - }, - async mounted(this: any) { - try { - await this.refresh(); - } catch (e) { - console.warn("[cocos-creator-mcp] mounted refresh failed:", e); - } - }, - }); - app.mount(this.$.app); - panelDataMap.set(this, app); - }, - close() { - const app = panelDataMap.get(this); - if (app) app.unmount(); - }, -}); diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/panels/recorder/index.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/panels/recorder/index.ts deleted file mode 100644 index c8bd1e2..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/panels/recorder/index.ts +++ /dev/null @@ -1,540 +0,0 @@ -const { createApp: createAppRec } = require("vue"); - -const panelDataMapRec = new WeakMap(); - -module.exports = Editor.Panel.define({ - template: ` -
-

Preview Recorder

- -
- - - -
- -
- ● REC {{ elapsed }}s - {{ recordingInfo }} -
- -
録画設定
-
- - - - -
-
- - - - - -
- -
スクショ設定
-
- - -
- -
保存先
-
- - - -
-
- - - -
-
- 自動整理について
- ONにすると、録画やスクショを保存するたびに、24時間以上前の古いファイルを
- OLD_yyyyMM/ フォルダ(例: OLD_202604/)に自動で移動します。
- 保存フォルダ直下には直近のファイルだけが残り、整理された状態を保てます。 -
- -
-
- ✓ {{ lastResult.kind === 'shot' ? 'スクショ保存' : '録画完了' }}
- {{ lastResult.path }}
- {{ (lastResult.size / 1024).toFixed(1) }} KB -
-
- ✗ エラー: {{ lastResult.error || lastResult.message || 'unknown' }} -
-
- -
- ※ ゲームプレビュー実行中に使ってください(GameDebugClientが必要) -
-
- `, - style: ` -#app { padding: 16px; font-family: sans-serif; color: #ccc; font-size: 12px; } -h2 { margin: 0 0 12px 0; font-size: 18px; } -.controls { margin: 12px 0; } -.btn { - padding: 10px 20px; - color: #fff; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 14px; - font-weight: bold; -} -.btn:disabled { background: #555; color: #999; cursor: not-allowed; } -.btn-start { background: #d44; } -.btn-start:hover { background: #e55; } -.btn-stop { background: #888; } -.btn-stop:hover { background: #999; } -.btn-shot { background: #468; margin-left: 8px; } -.btn-shot:hover { background: #579; } -.section-title { - margin-top: 14px; - margin-bottom: 4px; - padding: 4px 0 3px 0; - font-size: 11px; - font-weight: bold; - color: #8af; - border-bottom: 1px solid #333; -} -.btn-small { - padding: 4px 10px; - background: #4a8; - font-size: 11px; - margin-left: 8px; - font-weight: normal; -} -.btn-small:hover { background: #5b9; } -.status-row { margin: 10px 0; font-size: 14px; } -.rec-dot { color: #f44; animation: blink 1s infinite; } -@keyframes blink { 50% { opacity: 0.3; } } -.info { color: #888; font-size: 11px; margin-left: 8px; } -.row { margin: 10px 0; display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; } -.row label { font-size: 12px; } -.row input { width: 60px; padding: 4px 8px; background: #222; color: #ccc; border: 1px solid #444; border-radius: 3px; } -.row select { padding: 4px 8px; background: #222; color: #ccc; border: 1px solid #444; border-radius: 3px; } -.path-input { flex: 1; min-width: 200px; width: auto !important; font-family: monospace; } -.custom-bitrate { width: 70px !important; } -.unit { font-size: 11px; color: #888; } -.result { margin: 12px 0; padding: 10px; border-radius: 4px; font-size: 12px; line-height: 1.5; } -.result.success { background: #1a3a1a; color: #afa; } -.result.error { background: #3a1a1a; color: #faa; } -.result code { font-size: 10px; word-break: break-all; background: #000; padding: 2px 4px; border-radius: 2px; } -.checkbox-label { font-size: 12px; display: flex; align-items: center; gap: 4px; margin-left: auto; cursor: pointer; } -.checkbox-label input[type="checkbox"] { cursor: pointer; } -.btn-help { padding: 2px 7px; background: #555; font-size: 11px; font-weight: bold; border-radius: 50%; min-width: 20px; margin-left: 4px; } -.btn-help:hover { background: #777; } -.help-box { margin: 8px 0; padding: 10px; background: #1a2a3a; border: 1px solid #345; border-radius: 4px; font-size: 11px; line-height: 1.6; color: #bcd; } -.help-box code { background: #000; padding: 1px 4px; border-radius: 2px; font-size: 10px; } -.note { margin-top: 16px; padding-top: 8px; border-top: 1px solid #333; font-size: 10px; color: #888; } - `, - $: { app: "#app" }, - ready() { - if (!this.$.app) return; - const MCP_BASE = "http://127.0.0.1:3000"; - const STORAGE_KEY = "cocos-mcp-recorder-settings"; - const PERSISTED_KEYS = ["fps", "quality", "coefficient", "format", "savePath", "shotFormat"]; - // localStorage から設定を読み込み - const loadSettings = () => { - try { - const raw = localStorage.getItem(STORAGE_KEY); - return raw ? JSON.parse(raw) : {}; - } catch { return {}; } - }; - const saved = loadSettings(); - // プロジェクト設定から autoArchiveRecordings を読み込み - const loadProjectConfig = () => { - try { - const fs = require("fs"); - const path = require("path"); - const p = path.join(Editor.Project.path, "settings", "cocos-creator-mcp.json"); - if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, "utf-8")); - } catch { /* ignore */ } - return {}; - }; - const projectCfg = loadProjectConfig(); - const app = createAppRec({ - data() { - return { - recording: false, - stopping: false, - shooting: false, - elapsed: "0.0", - recordingInfo: "", - fps: saved.fps ?? 30, - quality: saved.quality ?? "medium", - coefficient: saved.coefficient ?? 0.25, - format: saved.format ?? "mp4", - savePath: saved.savePath ?? "temp/recordings", - shotFormat: saved.shotFormat ?? "png", - autoArchive: projectCfg.autoArchiveRecordings ?? false, - showArchiveHelp: false, - lastResult: null as any, - lastError: false, - _startTime: 0, - _timer: null as any, - _aliveCheckTimer: null as any, - }; - }, - watch: { - fps(this: any) { this.saveSettings(); }, - quality(this: any) { this.saveSettings(); }, - coefficient(this: any) { this.saveSettings(); }, - format(this: any) { this.saveSettings(); }, - savePath(this: any) { this.saveSettings(); }, - shotFormat(this: any) { this.saveSettings(); }, - }, - methods: { - saveSettings(this: any) { - const data: any = {}; - for (const key of PERSISTED_KEYS) data[key] = this[key]; - try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch { /* ignore */ } - }, - async isPreviewRunning(this: any): Promise { - try { - const res = await fetch(`${MCP_BASE}/mcp`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", id: 98, method: "tools/call", - params: { - name: "debug_game_command", - arguments: { type: "inspect", args: { name: "Canvas" }, timeout: 1500 }, - }, - }), - }); - const json = await res.json(); - const content = json.result?.content?.[0]?.text; - const parsed = content ? JSON.parse(content) : null; - return !!parsed?.success; - } catch { - return false; - } - }, - async start(this: any) { - this.lastResult = null; - if (!await this.isPreviewRunning()) { - this.lastResult = { error: "ゲームプレビューが実行されていません。プレビューを開始してから録画してください。" }; - this.lastError = true; - return; - } - try { - const res = await fetch(`${MCP_BASE}/mcp`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: 1, - method: "tools/call", - params: { - name: "debug_record_start", - arguments: { - fps: this.fps, - coefficient: this.coefficient, - format: this.format, - savePath: this.savePath, - }, - }, - }), - }); - const json = await res.json(); - const content = json.result?.content?.[0]?.text; - const parsed = content ? JSON.parse(content) : null; - if (parsed?.success && parsed.data?.id) { - this.recording = true; - const d = parsed.data; - const mbps = d?.videoBitsPerSecond ? (d.videoBitsPerSecond / 1_000_000).toFixed(2) : "?"; - this.recordingInfo = `${d?.canvasWidth || "?"}x${d?.canvasHeight || "?"} @ ${d?.fps || "?"}fps / ${mbps}Mbps / ${d?.mimeType || ""}`; - this._startTime = Date.now(); - this._timer = setInterval(() => { - this.elapsed = ((Date.now() - this._startTime) / 1000).toFixed(1); - }, 100); - // プレビュー停止検知用ポーリング(2秒毎) - this._aliveCheckTimer = setInterval(() => { - this.checkPreviewAlive(); - }, 2000); - } else { - // 可能な限り詳細なエラー情報を表示 - const errDetail = parsed?.data?.error - || parsed?.error - || (parsed?.data ? JSON.stringify(parsed.data) : null) - || (parsed ? JSON.stringify(parsed).substring(0, 200) : "no response") - || "録画開始失敗"; - this.lastResult = { error: errDetail }; - this.lastError = true; - } - } catch (e: any) { - this.lastResult = { error: `通信エラー: ${e.message}` }; - this.lastError = true; - } - }, - async stop(this: any) { - this.stopping = true; - try { - const res = await fetch(`${MCP_BASE}/mcp`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: 2, - method: "tools/call", - params: { - name: "debug_record_stop", - arguments: { timeout: 60000 }, - }, - }), - }); - const json = await res.json(); - const content = json.result?.content?.[0]?.text; - const parsed = content ? JSON.parse(content) : null; - if (parsed?.success && parsed.data?.path) { - this.lastResult = { path: parsed.data.path, size: parsed.data.size }; - this.lastError = false; - } else { - const errDetail = parsed?.data?.error - || parsed?.error - || (parsed?.data ? JSON.stringify(parsed.data) : null) - || "録画停止失敗"; - this.lastResult = { error: errDetail }; - this.lastError = true; - } - } catch (e: any) { - this.lastResult = { error: `通信エラー: ${e.message}` }; - this.lastError = true; - } finally { - this.recording = false; - this.stopping = false; - if (this._timer) { clearInterval(this._timer); this._timer = null; } - if (this._aliveCheckTimer) { clearInterval(this._aliveCheckTimer); this._aliveCheckTimer = null; } - } - }, - onQualityChange(this: any) { - const map: Record = { low: 0.15, medium: 0.25, high: 0.40, ultra: 0.60 }; - if (this.quality !== "custom") this.coefficient = map[this.quality]; - }, - onCoefChange(this: any) { - const map: Record = { 0.15: "low", 0.25: "medium", 0.40: "high", 0.60: "ultra" }; - this.quality = map[this.coefficient] || "custom"; - }, - resetQuality(this: any) { - this.fps = 30; - this.quality = "medium"; - this.coefficient = 0.25; - this.format = "mp4"; - }, - resetSavePath(this: any) { - this.savePath = "temp/recordings"; - }, - onAutoArchiveChange(this: any) { - try { - const fs = require("fs"); - const path = require("path"); - const p = path.join(Editor.Project.path, "settings", "cocos-creator-mcp.json"); - let cfg: any = {}; - if (fs.existsSync(p)) cfg = JSON.parse(fs.readFileSync(p, "utf-8")); - cfg.autoArchiveRecordings = this.autoArchive; - const dir = path.dirname(p); - if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); - fs.writeFileSync(p, JSON.stringify(cfg, null, 2), "utf-8"); - } catch (e) { - console.warn("[recorder] 設定保存失敗:", e); - } - }, - async screenshot(this: any) { - this.shooting = true; - if (!await this.isPreviewRunning()) { - this.lastResult = { error: "ゲームプレビューが実行されていません。プレビューを開始してからスクショしてください。" }; - this.lastError = true; - this.shooting = false; - return; - } - try { - const res = await fetch(`${MCP_BASE}/mcp`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: 3, - method: "tools/call", - params: { - name: "debug_game_command", - arguments: { type: "screenshot", args: {}, timeout: 5000, maxWidth: 0, imageFormat: this.shotFormat }, - }, - }), - }); - const json = await res.json(); - const content = json.result?.content?.[0]?.text; - const parsed = content ? JSON.parse(content) : null; - if (parsed?.success && parsed.path) { - // savePath 配下にコピー - const fs = require("fs"); - const path = require("path"); - const projectPath = Editor.Project.path; - let destDir = this.savePath || "temp/recordings"; - if (!path.isAbsolute(destDir)) destDir = path.join(projectPath, destDir); - if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true }); - const ts = new Date().toISOString().replace(/[:.]/g, "-"); - const ext = path.extname(parsed.path) || ".png"; - const destPath = path.join(destDir, `screenshot_${ts}${ext}`); - fs.copyFileSync(parsed.path, destPath); - // 設定に応じて古いファイルをアーカイブ - try { - const settingsPath = path.join(projectPath, "settings", "cocos-creator-mcp.json"); - if (fs.existsSync(settingsPath)) { - const cfg = JSON.parse(fs.readFileSync(settingsPath, "utf-8")); - if (cfg.autoArchiveRecordings) { - const { archiveOldFiles } = require("../../archive"); - archiveOldFiles(destDir); - } - } - } catch { /* ignore */ } - this.lastResult = { kind: "shot", path: destPath, size: parsed.size }; - this.lastError = false; - } else { - const errDetail = parsed?.error || parsed?.message || "スクショ失敗"; - this.lastResult = { error: errDetail }; - this.lastError = true; - } - } catch (e: any) { - this.lastResult = { error: `通信エラー: ${e.message}` }; - this.lastError = true; - } finally { - this.shooting = false; - } - }, - async checkPreviewAlive(this: any) { - if (!this.recording) return; - try { - // MCP経由で軽量なinspectコマンドを送る - const res = await fetch(`${MCP_BASE}/mcp`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", id: 99, method: "tools/call", - params: { - name: "debug_game_command", - arguments: { type: "inspect", args: { name: "Canvas" }, timeout: 1500 }, - }, - }), - }); - const json = await res.json(); - const content = json.result?.content?.[0]?.text; - const parsed = content ? JSON.parse(content) : null; - if (parsed?.success) { - // プレビュー生存 - return; - } - // 応答なし → プレビュー停止とみなして録画停止状態に - console.warn("[PreviewRecorder] preview not responding, stopping recording state"); - this.recording = false; - if (this._timer) { clearInterval(this._timer); this._timer = null; } - if (this._aliveCheckTimer) { clearInterval(this._aliveCheckTimer); this._aliveCheckTimer = null; } - this.lastResult = { error: "プレビューが停止したため録画を中断しました(動画は保存されません)" }; - this.lastError = true; - } catch (e) { - // ネットワークエラーは通信エラーとして無視(一時的かもしれない) - } - }, - async selectSaveFolder(this: any) { - try { - const result = await (Editor.Dialog as any).select({ - title: "保存先フォルダを選択", - type: "directory", - multi: false, - }); - if (result?.filePaths?.length) { - const path = require("path"); - const projectPath = Editor.Project.path; - const absPath = result.filePaths[0]; - // プロジェクト配下なら相対パスで保持 - const relPath = path.relative(projectPath, absPath); - if (!relPath.startsWith("..") && !path.isAbsolute(relPath)) { - this.savePath = relPath.replace(/\\/g, "/"); - } else { - this.savePath = absPath; - } - } - } catch (e: any) { - console.error("[PreviewRecorder] selectSaveFolder failed:", e); - } - }, - openSaveFolder(this: any) { - const path = require("path"); - const fs = require("fs"); - const projectPath = Editor.Project.path; - let dir = this.savePath || "temp/recordings"; - if (!path.isAbsolute(dir)) dir = path.join(projectPath, dir); - if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); - try { - const { spawn } = require("child_process"); - const platform = process.platform; - const [cmd, ...args] = platform === "win32" - ? ["explorer.exe", dir.replace(/\//g, "\\")] - : platform === "darwin" - ? ["open", dir] - : ["xdg-open", dir]; - const p = spawn(cmd, args, { detached: true, stdio: "ignore" }); - p.unref(); - } catch (e: any) { - console.error("[PreviewRecorder] openSaveFolder failed:", e); - this.lastResult = { error: `フォルダを開けませんでした: ${e.message}` }; - this.lastError = true; - } - }, - openFolder(this: any) { - if (!this.lastResult?.path) return; - const filePath = this.lastResult.path; - try { - const { shell } = require("electron"); - if (shell?.showItemInFolder) { - shell.showItemInFolder(filePath); - return; - } - } catch (e) { /* fallback */ } - // フォールバック: OS別コマンド - try { - const { exec } = require("child_process"); - const platform = process.platform; - if (platform === "win32") { - exec(`explorer.exe /select,"${filePath.replace(/\//g, "\\")}"`); - } else if (platform === "darwin") { - exec(`open -R "${filePath}"`); - } else { - const dir = require("path").dirname(filePath); - exec(`xdg-open "${dir}"`); - } - } catch (e: any) { - console.error("[PreviewRecorder] openFolder failed:", e); - } - }, - }, - }); - app.mount(this.$.app); - panelDataMapRec.set(this, app); - }, - beforeClose() { }, - close() { - const app = panelDataMapRec.get(this); - if (app) app.unmount(); - panelDataMapRec.delete(this); - }, -}); diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/scene.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/scene.ts deleted file mode 100644 index 16d54d7..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/scene.ts +++ /dev/null @@ -1,509 +0,0 @@ -/** - * Scene script — runs inside CocosCreator's scene renderer process. - * These methods are called via Editor.Message.request('scene', 'execute-scene-script', ...) - * and have access to the `cc` module (engine runtime). - */ - -import { join } from "path"; -module.paths.push(join(Editor.App.path, "node_modules")); - -// ─── Console Log Buffer ─── - -interface ConsoleLogEntry { - timestamp: string; - level: "log" | "warn" | "error"; - message: string; -} - -const MAX_LOG_BUFFER = 500; -const _consoleLogs: ConsoleLogEntry[] = []; - -const _originalLog = console.log; -const _originalWarn = console.warn; -const _originalError = console.error; - -function formatArgs(args: any[]): string { - return args.map(a => { - if (typeof a === "string") return a; - try { return JSON.stringify(a); } catch { return String(a); } - }).join(" "); -} - -function pushLog(level: ConsoleLogEntry["level"], args: any[]): void { - _consoleLogs.push({ - timestamp: new Date().toISOString(), - level, - message: formatArgs(args), - }); - if (_consoleLogs.length > MAX_LOG_BUFFER) { - _consoleLogs.splice(0, _consoleLogs.length - MAX_LOG_BUFFER); - } -} - -console.log = function (...args: any[]) { - _originalLog.apply(console, args); - pushLog("log", args); -}; - -console.warn = function (...args: any[]) { - _originalWarn.apply(console, args); - pushLog("warn", args); -}; - -console.error = function (...args: any[]) { - _originalError.apply(console, args); - pushLog("error", args); -}; - -function getScene() { - const { director } = require("cc"); - return director.getScene(); -} - -function findNode(uuid: string) { - const scene = getScene(); - if (!scene) return null; - - // Recursive search — getChildByUuid is not recursive in cc - const queue = [...scene.children]; - while (queue.length > 0) { - const node = queue.shift()!; - if (node.uuid === uuid) return node; - if (node.children) queue.push(...node.children); - } - return null; -} - -/** - * Recursively build a node tree from a JSON spec. - * Returns { uuid, name, children: [...] } - */ -function buildNodeRecursive(parent: any, spec: any): any { - const { Node, js, Vec3 } = require("cc"); - - const node = new Node(spec.name || "Node"); - parent.addChild(node); - - // Add components - if (spec.components && Array.isArray(spec.components)) { - const { Sprite, Label } = require("cc"); - for (const compName of spec.components) { - const CompClass = js.getClassByName(compName); - if (CompClass) { - if (!node.getComponent(CompClass)) { - const comp = node.addComponent(CompClass); - // Sprite: sizeMode=CUSTOM でUITransformサイズの上書きを防ぐ - if (comp instanceof Sprite) { - comp.sizeMode = 0; // SizeMode.CUSTOM - } - // Label: useSystemFont + 色を黒に(白パネル上で見えるように) - if (comp instanceof Label) { - comp.useSystemFont = true; - const { Color } = require("cc"); - comp.color = new Color(51, 51, 51, 255); - } - } - } - } - } - - // Set component properties - // Format: { "cc.UITransform.contentSize": {width:720, height:1280} } - if (spec.properties) { - for (const [key, value] of Object.entries(spec.properties)) { - const dotIdx = key.lastIndexOf("."); - if (dotIdx < 0) continue; - const compName = key.substring(0, dotIdx); - const propName = key.substring(dotIdx + 1); - - const CompClass = js.getClassByName(compName); - if (!CompClass) continue; - const comp = node.getComponent(CompClass); - if (!comp) continue; - - try { - // contentSize needs special handling (Size type) - if (propName === "contentSize" && value && typeof value === "object") { - comp.setContentSize((value as any).width ?? 0, (value as any).height ?? 0); - } else { - comp[propName] = value; - } - } catch { /* skip invalid property */ } - } - } - - // Set UITransform anchorPoint if specified - if (spec.anchorPoint) { - const { UITransform } = require("cc"); - const ut = node.getComponent(UITransform); - if (ut) ut.setAnchorPoint(spec.anchorPoint.x ?? 0.5, spec.anchorPoint.y ?? 0.5); - } - - // Set node properties (position, scale, active) - if (spec.active === false) node.active = false; - if (spec.position) node.setPosition(spec.position.x || 0, spec.position.y || 0, spec.position.z || 0); - if (spec.scale) node.setScale(spec.scale.x ?? 1, spec.scale.y ?? 1, spec.scale.z ?? 1); - - // Set Widget properties if specified - // Format: { top: 0, bottom: 0, left: 0, right: 0 } — each field enables the corresponding alignment - if (spec.widget) { - const { Widget } = require("cc"); - let w = node.getComponent(Widget); - if (!w) w = node.addComponent(Widget); - const wSpec = spec.widget; - if (wSpec.top !== undefined) { w.isAlignTop = true; w.top = wSpec.top; } - if (wSpec.bottom !== undefined) { w.isAlignBottom = true; w.bottom = wSpec.bottom; } - if (wSpec.left !== undefined) { w.isAlignLeft = true; w.left = wSpec.left; } - if (wSpec.right !== undefined) { w.isAlignRight = true; w.right = wSpec.right; } - if (wSpec.horizontalCenter !== undefined) { w.isAlignHorizontalCenter = true; w.horizontalCenter = wSpec.horizontalCenter; } - if (wSpec.verticalCenter !== undefined) { w.isAlignVerticalCenter = true; w.verticalCenter = wSpec.verticalCenter; } - } - - // Build children - const childResults: any[] = []; - if (spec.children && Array.isArray(spec.children)) { - for (const childSpec of spec.children) { - childResults.push(buildNodeRecursive(node, childSpec)); - } - } - - return { uuid: node.uuid, name: node.name, children: childResults }; -} - -function collectNodeInfo(node: any, includeComponents: boolean = false): any { - const info: any = { - uuid: node.uuid, - name: node.name, - active: node.active, - position: { x: node.position.x, y: node.position.y, z: node.position.z }, - scale: { x: node.scale.x, y: node.scale.y, z: node.scale.z }, - parent: node.parent?.uuid || null, - childCount: node.children?.length || 0, - }; - if (includeComponents && node.components) { - info.components = node.components.map((c: any) => ({ - type: c.constructor.name, - uuid: c.uuid, - enabled: c.enabled, - })); - } - return info; -} - -export const methods: Record any> = { - getSceneHierarchy(includeComponents: boolean = false) { - try { - const scene = getScene(); - if (!scene) return { success: false, error: "No active scene" }; - - const walk = (node: any): any => { - const item: any = { - uuid: node.uuid, - name: node.name, - active: node.active, - children: [], - }; - if (includeComponents && node.components) { - item.components = node.components.map((c: any) => ({ - type: c.constructor.name, - uuid: c.uuid, - enabled: c.enabled, - })); - } - if (node.children) { - item.children = node.children.map((ch: any) => walk(ch)); - } - return item; - }; - - return { - success: true, - sceneName: scene.name, - sceneUuid: scene.uuid, - hierarchy: scene.children.map((ch: any) => walk(ch)), - }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, - - getNodeInfo(uuid: string) { - try { - const node = findNode(uuid); - if (!node) return { success: false, error: `Node ${uuid} not found` }; - return { success: true, data: collectNodeInfo(node, true) }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, - - getAllNodes() { - try { - const scene = getScene(); - if (!scene) return { success: false, error: "No active scene" }; - - const nodes: any[] = []; - const walk = (node: any) => { - nodes.push(collectNodeInfo(node)); - if (node.children) node.children.forEach(walk); - }; - scene.children.forEach(walk); - return { success: true, data: nodes }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, - - /** - * 指定ノードの子孫から名前で検索する。auto_bind 用。 - * rootUuid が空の場合はシーン全体を検索(後方互換)。 - */ - findDescendantsByName(rootUuid: string, name: string) { - try { - let root: any; - if (rootUuid) { - root = findNode(rootUuid); - if (!root) return { success: false, error: `Root node ${rootUuid} not found` }; - } else { - root = getScene(); - if (!root) return { success: false, error: "No active scene" }; - } - - const results: any[] = []; - const walk = (node: any) => { - if (node.name === name) results.push(collectNodeInfo(node)); - if (node.children) node.children.forEach(walk); - }; - if (root.children) root.children.forEach(walk); - return { success: true, data: results }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, - - /** - * 指定ノードの全子孫を depth 付きで返す。auto_bind の一括検索用。 - */ - getAllDescendants(rootUuid: string) { - try { - const root = findNode(rootUuid); - if (!root) return { success: false, error: `Node ${rootUuid} not found` }; - - const results: Array<{uuid: string, name: string, depth: number}> = []; - const walk = (node: any, depth: number) => { - results.push({ uuid: node.uuid, name: node.name, depth }); - if (node.children) node.children.forEach((c: any) => walk(c, depth + 1)); - }; - if (root.children) root.children.forEach((c: any) => walk(c, 1)); - return { success: true, data: results }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, - - findNodesByName(name: string) { - try { - const scene = getScene(); - if (!scene) return { success: false, error: "No active scene" }; - - const results: any[] = []; - const walk = (node: any) => { - if (node.name === name) results.push(collectNodeInfo(node)); - if (node.children) node.children.forEach(walk); - }; - scene.children.forEach(walk); - return { success: true, data: results }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, - - findNodeByPath(path: string) { - try { - const scene = getScene(); - if (!scene) return { success: false, error: "No active scene" }; - - const parts = path.split("/"); - let current: any = null; - - const walk = (node: any): any => { - if (node.name === parts[0]) return node; - if (node.children) { - for (const child of node.children) { - const found = walk(child); - if (found) return found; - } - } - return null; - }; - for (const child of scene.children) { - current = walk(child); - if (current) break; - } - if (!current) return { success: false, error: `Node "${parts[0]}" not found` }; - - for (let i = 1; i < parts.length; i++) { - const child = current.children?.find((c: any) => c.name === parts[i]); - if (!child) return { success: false, error: `Child "${parts[i]}" not found in "${current.name}"` }; - current = child; - } - - return { success: true, data: collectNodeInfo(current) }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, - - setNodeProperty(uuid: string, property: string, value: any) { - try { - const node = findNode(uuid); - if (!node) return { success: false, error: `Node ${uuid} not found` }; - - switch (property) { - case "position": - node.setPosition(value.x ?? 0, value.y ?? 0, value.z ?? 0); - break; - case "rotation": - node.setRotationFromEuler(value.x ?? 0, value.y ?? 0, value.z ?? 0); - break; - case "scale": - node.setScale(value.x ?? 1, value.y ?? 1, value.z ?? 1); - break; - case "active": - node.active = !!value; - break; - case "name": - node.name = String(value); - break; - default: - (node as any)[property] = value; - } - return { success: true }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, - - setComponentProperty(uuid: string, componentType: string, property: string, value: any) { - try { - const { js } = require("cc"); - const node = findNode(uuid); - if (!node) return { success: false, error: `Node ${uuid} not found` }; - - const CompClass = js.getClassByName(componentType); - if (!CompClass) return { success: false, error: `Component class ${componentType} not found` }; - - const comp = node.getComponent(CompClass); - if (!comp) return { success: false, error: `Component ${componentType} not on node ${uuid}` }; - - comp[property] = value; - return { success: true }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, - - addComponentToNode(uuid: string, componentType: string) { - try { - const { js } = require("cc"); - const node = findNode(uuid); - if (!node) return { success: false, error: `Node ${uuid} not found` }; - - const CompClass = js.getClassByName(componentType); - if (!CompClass) return { success: false, error: `Component class ${componentType} not found` }; - - const comp = node.addComponent(CompClass); - return { success: true, data: { uuid: comp.uuid, type: componentType } }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, - - moveNode(uuid: string, parentUuid: string) { - try { - const node = findNode(uuid); - if (!node) return { success: false, error: `Node ${uuid} not found` }; - const parent = findNode(parentUuid); - if (!parent) return { success: false, error: `Parent ${parentUuid} not found` }; - node.setParent(parent); - return { success: true }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, - - /** - * Build a node tree from a JSON spec in one call. - * Spec format: - * { - * name: string, - * components?: string[], // e.g. ["cc.UITransform", "cc.Layout"] - * properties?: Record, // e.g. { "cc.UITransform.contentSize": {width:720,height:1280} } - * children?: NodeSpec[] - * } - */ - buildNodeTree(parentUuid: string, spec: any) { - try { - const { Node, js, UITransform } = require("cc"); - const parent = findNode(parentUuid); - if (!parent) return { success: false, error: `Parent ${parentUuid} not found` }; - - const result = buildNodeRecursive(parent, spec); - return { success: true, data: result }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, - - testLog(message: string = "test message") { - console.log("[testLog]", message); - const methods = Object.keys(exports.methods || {}); - return { success: true, bufferSize: _consoleLogs.length, methods }; - }, - - getConsoleLogs(count: number = 50, level?: string) { - let logs = _consoleLogs; - if (level) { - logs = logs.filter(l => l.level === level); - } - return { success: true, logs: logs.slice(-count), total: _consoleLogs.length }; - }, - - clearConsoleLogs() { - _consoleLogs.length = 0; - return { success: true }; - }, - - async setPropertyViaEditor(nodeUuid: string, path: string, dump: any) { - try { - // scene:set-property API - // uuid: ノードUUID(コンポーネントUUIDではない) - // path: __comps__.{index}.{property} 形式 - // dump: { value, type } 形式 - const opts = { uuid: nodeUuid, path, dump }; - const result = await (Editor as any).Message.request("scene", "set-property", opts); - return { success: true, result }; - } catch (e: any) { - return { success: false, error: e.message || String(e) }; - } - }, - - removeComponentFromNode(uuid: string, componentType: string) { - try { - const { js } = require("cc"); - const node = findNode(uuid); - if (!node) return { success: false, error: `Node ${uuid} not found` }; - - const CompClass = js.getClassByName(componentType); - if (!CompClass) return { success: false, error: `Component class ${componentType} not found` }; - - const comp = node.getComponent(CompClass); - if (!comp) return { success: false, error: `Component ${componentType} not on node` }; - - node.removeComponent(comp); - return { success: true }; - } catch (e: any) { - return { success: false, error: e.message }; - } - }, -}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/screenshot.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/screenshot.ts deleted file mode 100644 index 4a89aaf..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/screenshot.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Shared screenshot utility — used by debug-tools and other tools that need screenshots. - */ - -export interface ProcessedImage { - buffer: Buffer; - width: number; - height: number; - format: string; -} - -export interface ScreenshotResult { - success: boolean; - path: string; - size: number; - format: string; - originalSize: string; - savedSize: string; -} - -export async function processImage(pngBuffer: Buffer, maxWidth: number, desiredFormat?: string): Promise { - try { - const Vips = require("wasm-vips"); - const vips = await Vips(); - let image = vips.Image.newFromBuffer(pngBuffer); - if (maxWidth > 0 && image.width > maxWidth) { - image = image.thumbnailImage(maxWidth); - } - if (desiredFormat === "png") { - const pngOut = image.pngsaveBuffer(); - const result = { buffer: Buffer.from(pngOut), width: image.width, height: image.height, format: "png" }; - image.delete(); - return result; - } - const outBuf = image.webpsaveBuffer({ Q: 85 }); - const result = { buffer: Buffer.from(outBuf), width: image.width, height: image.height, format: "webp" }; - image.delete(); - return result; - } catch { - // Fallback: NativeImage resize + JPEG - const electron = require("electron"); - let image = electron.nativeImage.createFromBuffer(pngBuffer); - if (maxWidth > 0) { - const size = image.getSize(); - if (size.width > maxWidth) { - const ratio = maxWidth / size.width; - image = image.resize({ width: Math.round(size.width * ratio), height: Math.round(size.height * ratio) }); - } - } - const size = image.getSize(); - const buffer = image.toJPEG(85); - return { buffer, width: size.width, height: size.height, format: "jpeg" }; - } -} - -export async function takeEditorScreenshot(savePath?: string | undefined, maxWidth?: number): Promise { - const fs = require("fs"); - const path = require("path"); - const electron = require("electron"); - const windows = electron.BrowserWindow.getAllWindows(); - if (!windows || windows.length === 0) { - throw new Error("No editor window found"); - } - // Find the main (largest) window - let win = windows[0]; - let maxArea = 0; - for (const w of windows) { - const bounds = w.getBounds(); - const area = bounds.width * bounds.height; - if (area > maxArea) { - maxArea = area; - win = w; - } - } - // Bring to front and wait for render - win.show(); - await new Promise(r => setTimeout(r, 300)); - const nativeImage = await win.webContents.capturePage(); - const originalSize = nativeImage.getSize(); - const pngBuffer = nativeImage.toPNG(); - - const effectiveMaxWidth = maxWidth !== undefined ? maxWidth : 960; - const { buffer, width, height, format } = await processImage(pngBuffer, effectiveMaxWidth); - - const ext = format === "webp" ? "webp" : format === "jpeg" ? "jpg" : "png"; - if (!savePath) { - const dir = path.join(Editor.Project.tmpDir, "screenshots"); - if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - savePath = path.join(dir, `screenshot_${timestamp}.${ext}`); - } - - const finalPath = savePath!; - fs.writeFileSync(finalPath, buffer); - return { - success: true, path: finalPath, size: buffer.length, format, - originalSize: `${originalSize.width}x${originalSize.height}`, - savedSize: `${width}x${height}`, - }; -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tool-base.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tool-base.ts deleted file mode 100644 index 63eb072..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tool-base.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ToolResult } from "./types"; - -/** Helper to create a successful text result */ -export function ok(data: any): ToolResult { - return { - content: [{ type: "text", text: JSON.stringify(data, null, 2) }], - }; -} - -/** Helper to create an error result */ -export function err(message: string): ToolResult { - return { - content: [{ type: "text", text: JSON.stringify({ error: message }) }], - isError: true, - }; -} - -/** Validate that a string looks like a CocosCreator UUID (not empty, reasonable format) */ -export function validateUuid(uuid: string, label: string = "uuid"): string | null { - if (!uuid || typeof uuid !== "string") { - return `${label} is required`; - } - if (uuid.trim().length === 0) { - return `${label} cannot be empty`; - } - // CocosCreator UUIDs: either standard format (8-4-4-4-12) or compressed (22 chars with +/=) - // Be permissive — just reject obviously wrong values - if (uuid.length < 10) { - return `${label} "${uuid}" is too short to be a valid UUID`; - } - return null; // valid -} - -/** Validate UUID and return err() if invalid, null if valid */ -export function checkUuid(uuid: string, label: string = "uuid"): ToolResult | null { - const error = validateUuid(uuid, label); - return error ? err(error) : null; -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/asset-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/asset-tools.ts deleted file mode 100644 index 2f2a09c..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/asset-tools.ts +++ /dev/null @@ -1,290 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; - -export class AssetTools implements ToolCategory { - readonly categoryName = "asset"; - - getTools(): ToolDefinition[] { - return [ - { - name: "asset_create", - description: "Create a new asset file in the project.", - inputSchema: { - type: "object", - properties: { - path: { type: "string", description: "db:// path for the new asset" }, - content: { type: "string", description: "File content (for text-based assets)" }, - }, - required: ["path"], - }, - }, - { - name: "asset_delete", - description: "Delete an asset from the project.", - inputSchema: { - type: "object", - properties: { - path: { type: "string", description: "db:// path of the asset to delete" }, - }, - required: ["path"], - }, - }, - { - name: "asset_move", - description: "Move/rename an asset.", - inputSchema: { - type: "object", - properties: { - source: { type: "string", description: "Current db:// path" }, - destination: { type: "string", description: "New db:// path" }, - }, - required: ["source", "destination"], - }, - }, - { - name: "asset_copy", - description: "Copy an asset to a new location.", - inputSchema: { - type: "object", - properties: { - source: { type: "string", description: "Source db:// path" }, - destination: { type: "string", description: "Destination db:// path" }, - }, - required: ["source", "destination"], - }, - }, - { - name: "asset_save", - description: "Save an asset by UUID.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Asset UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "asset_reimport", - description: "Re-import an asset to refresh it.", - inputSchema: { - type: "object", - properties: { - path: { type: "string", description: "db:// path of the asset" }, - }, - required: ["path"], - }, - }, - { - name: "asset_query_path", - description: "Get the file path for an asset UUID.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Asset UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "asset_query_uuid", - description: "Get the UUID for an asset path.", - inputSchema: { - type: "object", - properties: { - path: { type: "string", description: "db:// path of the asset" }, - }, - required: ["path"], - }, - }, - { - name: "asset_query_url", - description: "Get the URL for an asset UUID.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Asset UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "asset_get_details", - description: "Get detailed metadata for an asset (type, size, importer, etc.).", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Asset UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "asset_get_dependencies", - description: "Get all assets that a given asset depends on.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Asset UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "asset_open_external", - description: "Open an asset in the system's default external editor.", - inputSchema: { - type: "object", - properties: { - path: { type: "string", description: "db:// path of the asset" }, - }, - required: ["path"], - }, - }, - { - name: "asset_import", - description: "Import an external file into the project assets.", - inputSchema: { - type: "object", - properties: { - source: { type: "string", description: "Source file path on disk" }, - target: { type: "string", description: "Target db:// path in project" }, - }, - required: ["source", "target"], - }, - }, - { - name: "asset_save_meta", - description: "Save asset meta information (importer settings, etc.).", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Asset UUID" }, - meta: { type: "string", description: "Meta JSON string" }, - }, - required: ["uuid", "meta"], - }, - }, - { - name: "asset_generate_available_url", - description: "Generate a non-conflicting asset URL (avoids name collisions).", - inputSchema: { - type: "object", - properties: { - url: { type: "string", description: "Desired db:// path" }, - }, - required: ["url"], - }, - }, - { - name: "asset_query_ready", - description: "Check if the asset database is ready.", - inputSchema: { type: "object", properties: {} }, - }, - // ── 以下、既存MCP未対応のEditor API ── - { - name: "asset_query_users", - description: "Find all assets that use/reference a given asset.", - inputSchema: { - type: "object", - properties: { uuid: { type: "string", description: "Asset UUID" } }, - required: ["uuid"], - }, - }, - { - name: "asset_query_missing", - description: "Check if an asset has missing references.", - inputSchema: { - type: "object", - properties: { uuid: { type: "string", description: "Asset UUID" } }, - required: ["uuid"], - }, - }, - ]; - } - - async execute(toolName: string, args: Record): Promise { - try { - switch (toolName) { - case "asset_create": - await (Editor.Message.request as any)("asset-db", "create-asset", args.path, args.content || null); - return ok({ success: true, path: args.path }); - case "asset_delete": - await (Editor.Message.request as any)("asset-db", "delete-asset", args.path); - return ok({ success: true, path: args.path }); - case "asset_move": - await (Editor.Message.request as any)("asset-db", "move-asset", args.source, args.destination); - return ok({ success: true, source: args.source, destination: args.destination }); - case "asset_copy": - await (Editor.Message.request as any)("asset-db", "copy-asset", args.source, args.destination); - return ok({ success: true, source: args.source, destination: args.destination }); - case "asset_save": - await (Editor.Message.request as any)("asset-db", "save-asset", args.uuid); - return ok({ success: true, uuid: args.uuid }); - case "asset_reimport": - await (Editor.Message.request as any)("asset-db", "reimport-asset", args.path); - return ok({ success: true, path: args.path }); - case "asset_query_path": { - const path = await (Editor.Message.request as any)("asset-db", "query-path", args.uuid); - return ok({ success: true, uuid: args.uuid, path }); - } - case "asset_query_uuid": { - const uuid = await (Editor.Message.request as any)("asset-db", "query-uuid", args.path); - return ok({ success: true, path: args.path, uuid }); - } - case "asset_query_url": { - const url = await (Editor.Message.request as any)("asset-db", "query-url", args.uuid); - return ok({ success: true, uuid: args.uuid, url }); - } - case "asset_get_details": { - const info = await (Editor.Message.request as any)("asset-db", "query-asset-info", args.uuid); - const meta = await (Editor.Message.request as any)("asset-db", "query-asset-meta", args.uuid).catch(() => null); - return ok({ success: true, info, meta }); - } - case "asset_get_dependencies": { - // Try multiple API names as they vary by CocosCreator version - let deps; - try { - deps = await (Editor.Message.request as any)("asset-db", "query-depends", args.uuid); - } catch { - try { - deps = await (Editor.Message.request as any)("asset-db", "query-asset-depends", args.uuid); - } catch { - deps = []; - } - } - return ok({ success: true, uuid: args.uuid, dependencies: deps }); - } - case "asset_open_external": - await (Editor.Message.request as any)("asset-db", "open-asset", args.path); - return ok({ success: true, path: args.path }); - case "asset_import": - await (Editor.Message.request as any)("asset-db", "import-asset", args.source, args.target); - return ok({ success: true, source: args.source, target: args.target }); - case "asset_save_meta": - await (Editor.Message.request as any)("asset-db", "save-asset-meta", args.uuid, args.meta); - return ok({ success: true, uuid: args.uuid }); - case "asset_generate_available_url": { - const url = await (Editor.Message.request as any)("asset-db", "generate-available-url", args.url); - return ok({ success: true, url }); - } - case "asset_query_ready": { - const ready = await (Editor.Message.request as any)("asset-db", "query-ready"); - return ok({ success: true, ready }); - } - case "asset_query_users": { - const users = await (Editor.Message.request as any)("asset-db", "query-asset-users", args.uuid).catch(() => []); - return ok({ success: true, uuid: args.uuid, users }); - } - case "asset_query_missing": { - const missing = await (Editor.Message.request as any)("asset-db", "query-missing-asset-info", args.uuid).catch(() => null); - return ok({ success: true, uuid: args.uuid, missing, hasMissing: !!missing }); - } - default: - return err(`Unknown tool: ${toolName}`); - } - } catch (e: any) { - return err(e.message || String(e)); - } - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/builder-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/builder-tools.ts deleted file mode 100644 index e35e6f7..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/builder-tools.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; - -export class BuilderTools implements ToolCategory { - readonly categoryName = "builder"; - - getTools(): ToolDefinition[] { - return [ - { - name: "builder_open_panel", - description: "Open the Build panel in the editor.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "builder_get_settings", - description: "Get the current build settings/configuration.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "builder_query_tasks", - description: "Query active build tasks.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "builder_run_preview", - description: "Start the preview server for browser testing.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "builder_stop_preview", - description: "Stop the preview server.", - inputSchema: { type: "object", properties: {} }, - }, - ]; - } - - async execute(toolName: string, _args: Record): Promise { - try { - switch (toolName) { - case "builder_open_panel": - Editor.Panel.open("builder"); - return ok({ success: true }); - case "builder_get_settings": { - const settings = await (Editor.Message.request as any)("builder", "query-build-options").catch(() => null); - return ok({ success: true, settings }); - } - case "builder_query_tasks": { - const tasks = await (Editor.Message.request as any)("builder", "query-tasks").catch(() => []); - return ok({ success: true, tasks }); - } - case "builder_run_preview": - await (Editor.Message.request as any)("preview", "start"); - return ok({ success: true, message: "Preview started" }); - case "builder_stop_preview": - await (Editor.Message.request as any)("preview", "stop"); - return ok({ success: true, message: "Preview stopped" }); - default: - return err(`Unknown tool: ${toolName}`); - } - } catch (e: any) { - return err(e.message || String(e)); - } - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/component-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/component-tools.ts deleted file mode 100644 index 97d917c..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/component-tools.ts +++ /dev/null @@ -1,687 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; -import { parseMaybeJson } from "../utils"; -import { resolveNodeUuid } from "../node-resolve"; -import { takeEditorScreenshot } from "../screenshot"; - -const EXT_NAME = "cocos-creator-mcp"; - -export class ComponentTools implements ToolCategory { - readonly categoryName = "component"; - - getTools(): ToolDefinition[] { - return [ - { - name: "component_add", - description: "Add a component to a node. Use cc.XXX format (e.g. 'cc.Label', 'cc.Sprite', 'cc.Button').", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - componentType: { type: "string", description: "Component class name (e.g. 'cc.Label')" }, - }, - required: ["uuid", "componentType"], - }, - }, - { - name: "component_remove", - description: "Remove a component from a node.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - componentType: { type: "string", description: "Component class name to remove" }, - }, - required: ["uuid", "componentType"], - }, - }, - { - name: "component_get_components", - description: "Get all components on a node with their properties.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID (either uuid or nodeName required)" }, - nodeName: { type: "string", description: "Node name to find (alternative to uuid)" }, - }, - }, - }, - { - name: "component_set_property", - description: "Set one or more properties on a component. For single: use property+value. For batch: use properties array. Use nodeName instead of uuid to find node by name. Set screenshot=true to capture editor screenshot after changes. Examples: Label.string, Label.fontSize, Sprite.color, UITransform.contentSize.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID (either uuid or nodeName required)" }, - nodeName: { type: "string", description: "Node name to find (alternative to uuid — avoids UUID lookup)" }, - componentType: { type: "string", description: "Component class name (e.g. 'cc.Label')" }, - property: { type: "string", description: "Property name (single mode)" }, - value: { description: "Value to set (single mode)" }, - properties: { - type: "array", - description: "Batch mode: array of {property, value} objects to set multiple properties at once", - items: { - type: "object", - properties: { - property: { type: "string", description: "Property name" }, - value: { description: "Value to set" }, - }, - required: ["property", "value"], - }, - }, - screenshot: { type: "boolean", description: "If true, capture editor screenshot after setting properties and return the file path (default: false)" }, - }, - required: ["componentType"], - }, - }, - { - name: "component_get_info", - description: "Get detailed dump of a specific component by its UUID.", - inputSchema: { - type: "object", - properties: { - componentUuid: { type: "string", description: "Component UUID (not node UUID)" }, - }, - required: ["componentUuid"], - }, - }, - { - name: "component_get_available", - description: "List all available component classes that can be added to nodes.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "component_auto_bind", - description: "Automatically bind @property references by matching property names to descendant node names. Searches only descendants of the target node. Validates component type existence. Supports array properties (Slot_0, Slot_1...). Mode: 'fuzzy' (default) tries exact match first, then case-insensitive; 'strict' requires exact match only.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID (either uuid or nodeName required)" }, - nodeName: { type: "string", description: "Node name to find (alternative to uuid)" }, - componentType: { type: "string", description: "Script component class name (e.g. 'QuestReadyPageView')" }, - force: { type: "boolean", description: "If true, rebind even already-bound properties (default: false)" }, - mode: { type: "string", enum: ["fuzzy", "strict"], description: "Matching mode: 'fuzzy' (default) or 'strict'" }, - }, - required: ["componentType"], - }, - }, - { - name: "component_query_enum", - description: "Get enum values for a component property. Useful for knowing what values Layout.type, Layout.resizeMode, etc. accept.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - componentType: { type: "string", description: "Component class (e.g. 'cc.Layout')" }, - property: { type: "string", description: "Property name (e.g. 'type', 'resizeMode')" }, - }, - required: ["uuid", "componentType", "property"], - }, - }, - ]; - } - - async execute(toolName: string, args: Record): Promise { - // パラメータエイリアス: component → componentType - const compType = args.componentType || args.component; - - // nodeName → uuid 解決(対応ツールのみ) - const needsResolve = ["component_set_property", "component_get_components", "component_auto_bind"]; - if (needsResolve.includes(toolName) && !args.uuid && args.nodeName) { - try { - const resolved = await resolveNodeUuid({ nodeName: args.nodeName }); - args.uuid = resolved.uuid; - } catch (e: any) { - return err(e.message || String(e)); - } - } - - switch (toolName) { - case "component_add": - return this.addComponent(args.uuid, compType); - case "component_remove": - return this.removeComponent(args.uuid, compType); - case "component_get_components": - return this.getComponents(args.uuid); - case "component_set_property": { - const properties = parseMaybeJson(args.properties); - let result: ToolResult; - if (properties && Array.isArray(properties)) { - const parsed = properties.map((p: any) => ({ ...p, value: parseMaybeJson(p.value) })); - result = await this.setProperties(args.uuid, compType, parsed); - } else { - result = await this.setProperty(args.uuid, compType, args.property, parseMaybeJson(args.value)); - } - // screenshot オプション - if (args.screenshot) { - try { - const ss = await takeEditorScreenshot(); - const data = JSON.parse(result.content[0].text); - data.screenshot = { path: ss.path, size: ss.savedSize }; - return ok(data); - } catch (ssErr: any) { - // スクショ失敗してもプロパティ設定結果は返す - const data = JSON.parse(result.content[0].text); - data.screenshotError = ssErr.message || String(ssErr); - return ok(data); - } - } - return result; - } - case "component_get_info": { - try { - const dump = await (Editor.Message.request as any)("scene", "query-component", args.componentUuid); - return ok({ success: true, component: dump }); - } catch (e: any) { return err(e.message || String(e)); } - } - case "component_get_available": { - try { - const classes = await (Editor.Message.request as any)("scene", "query-classes"); - return ok({ success: true, classes }); - } catch (e: any) { return err(e.message || String(e)); } - } - case "component_auto_bind": - return this.autoBind(args.uuid, compType, args.force ?? false, args.mode ?? "fuzzy"); - case "component_query_enum": - return this.queryEnum(args.uuid, compType, args.property); - default: - return err(`Unknown tool: ${toolName}`); - } - } - - private async addComponent(uuid: string, componentType: string): Promise { - try { - const result = await this.sceneScript("addComponentToNode", [uuid, componentType]); - return ok(result); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async queryEnum(nodeUuid: string, componentType: string, property: string): Promise { - try { - const nodeDump = await (Editor.Message.request as any)("scene", "query-node", nodeUuid); - if (!nodeDump) return err("Node not found"); - - const comps = nodeDump.__comps__ || []; - for (const comp of comps) { - const compType = comp.type; - if (!compType) continue; - // Match by cc.XXX format - const normalizedType = componentType.startsWith("cc.") ? componentType.substring(3) : componentType; - if (compType !== `cc.${normalizedType}` && compType !== componentType) continue; - - const propDump = comp.value?.[property]; - if (!propDump) return err(`Property '${property}' not found on ${componentType}`); - if (propDump.type !== "Enum") { - return ok({ success: true, property, type: propDump.type, note: "Not an enum property", currentValue: propDump.value }); - } - return ok({ - success: true, - property, - currentValue: propDump.value, - enumList: propDump.enumList, - }); - } - return err(`Component ${componentType} not found on node`); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async removeComponent(uuid: string, componentType: string): Promise { - try { - const result = await this.sceneScript("removeComponentFromNode", [uuid, componentType]); - return ok(result); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async getComponents(uuid: string): Promise { - try { - const result = await this.sceneScript("getNodeInfo", [uuid]); - if (!result.success) return ok(result); - return ok({ - success: true, - uuid, - name: result.data?.name, - components: result.data?.components || [], - }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - /** - * @property 名とノード名を自動マッチングしてバインドする。 - * - * - 検索スコープ: 対象ノードの子孫のみ - * - 複数ヒット時: 階層の浅いノード(直接の子)を優先 - * - 型検証: Component 参照型の場合、該当コンポーネントの存在を確認 - * - 配列対応: @property([Node]) → 連番ノード名 (Slots_0, Slots_1...) - * - mode: - * - "fuzzy" (default): 完全一致 → case-insensitive → not_found+候補 - * - "strict": 完全一致のみ → not_found+候補 - */ - private async autoBind(nodeUuid: string, componentType: string, force: boolean, mode: string): Promise { - try { - const nodeDump = await (Editor.Message.request as any)("scene", "query-node", nodeUuid); - if (!nodeDump) return err("Node not found"); - - const comps = nodeDump.__comps__ || []; - const compName = componentType.replace("cc.", ""); - const compIndex = comps.findIndex((c: any) => { - const t = c.type || ""; - return t === compName || t === `cc.${compName}`; - }); - if (compIndex < 0) return err(`Component ${componentType} not found on node`); - - // 子孫ノード一覧を一括取得(検索効率化) - const allDescendants = await this.sceneScript("getAllDescendants", [nodeUuid]); - const descendantList: Array<{uuid: string, name: string, depth: number}> = - allDescendants?.success ? allDescendants.data : []; - - const compDump = comps[compIndex]; - const properties = compDump.value || {}; - const skipKeys = new Set(["uuid", "name", "enabled", "node", "__scriptAsset", "__prefab", "_name", "_objFlags", "_enabled"]); - - const results: any[] = []; - - for (const [propName, propDumpRaw] of Object.entries(properties)) { - if (skipKeys.has(propName) || propName.startsWith("_")) continue; - - const propDump = propDumpRaw as any; - const propType = propDump.type as string; - if (!propType) continue; - - const extendsArr = (propDump.extends || []) as string[]; - - // 配列型の判定 - const isArray = propType === "Array" || Array.isArray(propDump.value); - if (isArray) { - const arrayResult = await this.autoBindArray(nodeUuid, compIndex, propName, propDump, descendantList, mode); - results.push(arrayResult); - continue; - } - - const isNodeRef = propType === "cc.Node"; - const isComponentRef = extendsArr.includes("cc.Component"); - if (!isNodeRef && !isComponentRef) continue; - - // 既にバインド済みならスキップ - const currentValue = propDump.value; - if (!force && currentValue?.uuid) { - results.push({ property: propName, status: "already_bound" }); - continue; - } - - // 名前マッチ: 完全一致 → fuzzy時は case-insensitive - const matchResult = this.findMatchingNode(propName, descendantList, mode); - - if (matchResult && isComponentRef) { - // 型検証: コンポーネントが存在するか - const hasComp = await this.nodeHasComponent(matchResult.uuid, propType); - if (!hasComp) { - results.push({ property: propName, type: propType, status: "type_mismatch", - nodeName: matchResult.name, message: `Node "${matchResult.name}" has no ${propType} component` }); - continue; - } - } - - if (!matchResult) { - // 候補サジェスト - const suggestions = this.getSuggestions(propName, descendantList); - results.push({ property: propName, type: propType, status: "not_found", suggestions }); - continue; - } - - const path = `__comps__.${compIndex}.${propName}`; - const dump = await this.buildDumpWithTypeInfo(nodeUuid, path, matchResult.uuid); - const setResult = await this.sceneScript("setPropertyViaEditor", [nodeUuid, path, dump]); - const status = matchResult.exact ? "bound" : "fuzzy_bound"; - results.push({ property: propName, status, nodeName: matchResult.name, success: setResult?.success !== false }); - } - - const boundCount = results.filter(r => r.status === "bound" || r.status === "fuzzy_bound").length; - const fuzzyCount = results.filter(r => r.status === "fuzzy_bound").length; - const notFoundCount = results.filter(r => r.status === "not_found").length; - return ok({ success: true, boundCount, fuzzyCount, notFoundCount, results }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - /** - * 子孫リストからプロパティ名にマッチするノードを検索。 - * 完全一致を優先、fuzzy モードでは case-insensitive もフォールバック。 - * 複数ヒット時は階層の浅い(depth が小さい)ものを優先。 - */ - private findMatchingNode( - propName: string, descendants: Array<{uuid: string, name: string, depth: number}>, mode: string - ): { uuid: string; name: string; exact: boolean } | null { - const candidates = this.propertyNameToNodeNames(propName); - - // 1. 完全一致 - for (const candidate of candidates) { - const matches = descendants - .filter(d => d.name === candidate) - .sort((a, b) => a.depth - b.depth); - if (matches.length > 0) { - return { uuid: matches[0].uuid, name: matches[0].name, exact: true }; - } - } - - // 2. fuzzy: case-insensitive - if (mode === "fuzzy") { - const lowerCandidates = candidates.map(c => c.toLowerCase()); - const matches = descendants - .filter(d => lowerCandidates.includes(d.name.toLowerCase())) - .sort((a, b) => a.depth - b.depth); - if (matches.length > 0) { - return { uuid: matches[0].uuid, name: matches[0].name, exact: false }; - } - } - - return null; - } - - /** - * not_found 時に似た名前のノードをサジェストする。 - */ - private getSuggestions(propName: string, descendants: Array<{uuid: string, name: string, depth: number}>): string[] { - const lower = propName.toLowerCase(); - return descendants - .filter(d => d.name.toLowerCase().includes(lower) || lower.includes(d.name.toLowerCase())) - .map(d => d.name) - .slice(0, 5); - } - - /** - * ノードに指定型のコンポーネントが存在するか確認。 - */ - private async nodeHasComponent(nodeUuid: string, propType: string): Promise { - const typeName = propType.replace("cc.", ""); - const info = await this.sceneScript("getNodeInfo", [nodeUuid]); - if (!info?.success || !info?.data?.components) return false; - return info.data.components.some((c: any) => c.type === typeName); - } - - /** - * 配列 @property の自動バインド。 - * プロパティ名 "slots" → "Slots_0", "Slots_1", ... の連番ノードを検索。 - */ - private async autoBindArray( - nodeUuid: string, compIndex: number, propName: string, propDump: any, - descendants: Array<{uuid: string, name: string, depth: number}>, mode: string - ): Promise { - const elementType = propDump.value?.[0]?.type as string | undefined; - if (!elementType) { - return { property: propName, status: "skip", reason: "empty array or unknown element type" }; - } - - const pascal = propName.charAt(0).toUpperCase() + propName.slice(1); - const foundElements: any[] = []; - let index = 0; - - while (true) { - const candidateName = `${pascal}_${index}`; - // 完全一致 or case-insensitive - let match = descendants.find(d => d.name === candidateName); - if (!match && mode === "fuzzy") { - const lower = candidateName.toLowerCase(); - match = descendants.find(d => d.name.toLowerCase() === lower); - } - if (!match) break; - - const elementPath = `__comps__.${compIndex}.${propName}.${index}`; - const dump = await this.buildDumpWithTypeInfo(nodeUuid, elementPath, match.uuid); - const setResult = await this.sceneScript("setPropertyViaEditor", [nodeUuid, elementPath, dump]); - const exact = match.name === candidateName; - foundElements.push({ index, nodeName: match.name, exact, success: setResult?.success !== false }); - index++; - } - - if (foundElements.length === 0) { - return { property: propName, status: "not_found", type: "Array", candidates: [`${pascal}_0`, `${pascal}_1`, "..."] }; - } - const hasFuzzy = foundElements.some(e => !e.exact); - return { property: propName, status: hasFuzzy ? "fuzzy_bound" : "bound", type: "Array", count: foundElements.length, elements: foundElements }; - } - - /** - * camelCase プロパティ名からノード名の候補を生成。 - * closeButton → ["CloseButton", "closeButton"] - */ - private propertyNameToNodeNames(propName: string): string[] { - const pascal = propName.charAt(0).toUpperCase() + propName.slice(1); - const names = [pascal]; - if (pascal !== propName) names.push(propName); - return names; - } - - private async setProperty(uuid: string, componentType: string, property: string, value: any): Promise { - try { - // コンポーネントのインデックスを取得 - const nodeInfo = await this.sceneScript("getNodeInfo", [uuid]); - if (!nodeInfo?.success || !nodeInfo?.data?.components) { - return err(`Node ${uuid} not found or has no components`); - } - const compName = componentType.replace("cc.", ""); - const compIndex = nodeInfo.data.components.findIndex((c: any) => c.type === compName); - if (compIndex < 0) { - return err(`Component ${componentType} not found on node ${uuid}`); - } - - // scene:set-property でプロパティ変更(Prefab保存時にも反映される) - // パス形式: __comps__.{index}.{property} - const path = `__comps__.${compIndex}.${property}`; - - // プロパティの型情報をquery-nodeから取得して、適切なdump形式を構築 - const dump = await this.buildDumpWithTypeInfo(uuid, path, value); - - const result = await this.sceneScript("setPropertyViaEditor", [uuid, path, dump]); - - // cc.Widget の isAlign* 設定後は _alignFlags を再計算する - // (Editor が isAlign* 変更時に _alignFlags を自動更新しないバグの対処) - if (componentType === "cc.Widget" && property.startsWith("isAlign")) { - await this.recalcWidgetAlignFlags(uuid, compIndex); - } - - return ok({ success: true, path, dump, result }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async setProperties(uuid: string, componentType: string, properties: Array<{property: string, value: any}>): Promise { - try { - if (!componentType) return err("componentType is required"); - if (!properties.length) return err("properties array is empty"); - - // コンポーネントのインデックスを取得(1回だけ) - const nodeInfo = await this.sceneScript("getNodeInfo", [uuid]); - if (!nodeInfo?.success || !nodeInfo?.data?.components) { - return err(`Node ${uuid} not found or has no components`); - } - const compName = componentType.replace("cc.", ""); - const compIndex = nodeInfo.data.components.findIndex((c: any) => c.type === compName); - if (compIndex < 0) { - return err(`Component ${componentType} not found on node ${uuid}`); - } - - const results: any[] = []; - for (const { property, value } of properties) { - const path = `__comps__.${compIndex}.${property}`; - const dump = await this.buildDumpWithTypeInfo(uuid, path, value); - const result = await this.sceneScript("setPropertyViaEditor", [uuid, path, dump]); - results.push({ property, success: result?.success !== false, path }); - } - - const allOk = results.every(r => r.success); - - // cc.Widget の isAlign* 設定後は _alignFlags を再計算する - // (Editor が isAlign* 変更時に _alignFlags を自動更新しないバグの対処) - if (componentType === "cc.Widget" && properties.some(p => p.property.startsWith("isAlign"))) { - await this.recalcWidgetAlignFlags(uuid, compIndex); - } - - return ok({ success: allOk, results }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - /** - * cc.Widget の isAlign* プロパティ現在値から _alignFlags ビットマスクを再計算して設定する。 - * - * CocosCreator Editor は isAlign* を setPropertyViaEditor で変更しても - * _alignFlags を自動更新しないバグがある。このヘルパーで明示的に同期する。 - * - * _alignFlags ビット定義: - * isAlignLeft=1, isAlignRight=2, isAlignTop=4, isAlignBottom=8, - * isAlignHorizontalCenter=16, isAlignVerticalCenter=32 - */ - private async recalcWidgetAlignFlags(uuid: string, wIdx: number): Promise { - const ALIGN_BITS: Record = { - isAlignLeft: 1, isAlignRight: 2, isAlignTop: 4, isAlignBottom: 8, - isAlignHorizontalCenter: 16, isAlignVerticalCenter: 32, - }; - try { - const nodeDump = await (Editor.Message.request as any)("scene", "query-node", uuid); - if (!nodeDump) return; - const wCompDump = nodeDump.__comps__?.[wIdx]; - if (!wCompDump) return; - let alignFlags = 0; - for (const [key, bit] of Object.entries(ALIGN_BITS)) { - if (wCompDump.value?.[key]?.value === true) alignFlags |= bit; - } - const path = `__comps__.${wIdx}._alignFlags`; - await this.sceneScript("setPropertyViaEditor", [uuid, path, { value: alignFlags, type: "Number" }]); - } catch (_e) { - // _alignFlags 再計算の失敗は致命的でないため無視 - } - } - - /** - * プロパティの型情報をEditor APIから取得し、適切なdump形式を構築する。 - * - * UUID文字列が渡された場合、プロパティの型に応じて: - * - Node/Component参照型 → {type: propType, value: {uuid: nodeUuid}} - * - Asset参照型(cc.Prefab等) → {type: propType, value: {uuid: assetUuid}} - * - String型 → {value, type: "String"} - */ - private async buildDumpWithTypeInfo(nodeUuid: string, path: string, value: any): Promise { - // プリミティブ型はそのまま - if (typeof value === "number") return { value, type: "Number" }; - if (typeof value === "boolean") return { value, type: "Boolean" }; - - // オブジェクト形式 {uuid: "xxx", type: "cc.Node"} はそのまま - // type 指定なしの {uuid: "xxx"} はプロパティの実際の型を解決するため文字列扱いに変換する - if (value !== null && typeof value === "object" && typeof value.uuid === "string") { - if (typeof value.type === "string") { - return { type: value.type, value: { uuid: value.uuid } }; - } - // type 未指定: 文字列として処理してプロパティ型から解決 - value = value.uuid; - } - - // @path: プレフィックスの場合: パスからノードUUIDを解決 - if (typeof value === "string" && value.startsWith("@path:")) { - const nodePath = value.slice(6); - const result = await this.sceneScript("findNodeByPath", [nodePath]); - if (result?.success && result.data?.uuid) { - value = result.data.uuid; - } else { - throw new Error(`Node not found at path: ${nodePath}`); - } - } - - // 文字列の場合: プロパティの型情報を取得して判定 - if (typeof value === "string") { - try { - const nodeDump = await (Editor.Message.request as any)("scene", "query-node", nodeUuid); - if (nodeDump) { - const propDump = this.resolveDumpPath(nodeDump, path); - if (propDump?.type) { - const propType = propDump.type as string; - const extendsArr = (propDump.extends || []) as string[]; - const isComponentRef = extendsArr.includes("cc.Component"); - const isNodeRef = propType === "cc.Node"; - const isAssetRef = extendsArr.includes("cc.Asset"); - - if (isComponentRef) { - // コンポーネント参照: ノードUUIDからコンポーネントUUIDを解決 - const compUuid = await this.resolveComponentUuid(value, propType); - return { type: propType, value: { uuid: compUuid || value } }; - } - if (isNodeRef) { - return { type: propType, value: { uuid: value } }; - } - if (isAssetRef) { - return { type: propType, value: { uuid: value } }; - } - } - } - } catch (_e) { - // query-node失敗時はフォールバック - } - return { value, type: "String" }; - } - - // その他のオブジェクト(contentSize, color等の構造体) - if (value !== null && typeof value === "object" && !Array.isArray(value)) { - const wrapped: any = {}; - for (const [k, v] of Object.entries(value)) { - wrapped[k] = { value: v }; - } - return { value: wrapped }; - } - - return { value }; - } - - /** - * query-nodeのdumpからドットパスでプロパティを解決する。 - * 例: "__comps__.2.scrollView" → nodeDump.__comps__[2].value.scrollView - */ - private resolveDumpPath(nodeDump: any, path: string): any { - const parts = path.split("."); - let current = nodeDump; - for (const part of parts) { - if (!current) return null; - if (part === "__comps__") { - current = current.__comps__; - } else if (/^\d+$/.test(part)) { - current = current[parseInt(part)]?.value; - } else { - current = current?.[part]; - } - } - return current; - } - - /** - * ノードUUIDからコンポーネントUUIDを解決する。 - * propType(例: "cc.ScrollView", "MissionListPanel")に一致するコンポーネントを探す。 - */ - private async resolveComponentUuid(nodeUuid: string, propType: string): Promise { - try { - const nodeInfo = await this.sceneScript("getNodeInfo", [nodeUuid]); - if (!nodeInfo?.success || !nodeInfo?.data?.components) return null; - const typeName = propType.replace("cc.", ""); - const comp = nodeInfo.data.components.find((c: any) => c.type === typeName); - return comp?.uuid || null; - } catch (_e) { - return null; - } - } - - private async sceneScript(method: string, args: any[]): Promise { - return Editor.Message.request("scene", "execute-scene-script", { - name: EXT_NAME, - method, - args, - }); - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/debug-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/debug-tools.ts deleted file mode 100644 index cdc43f5..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/debug-tools.ts +++ /dev/null @@ -1,804 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; -import { getGameLogs, clearGameLogs, queueGameCommand, getCommandResult } from "../mcp-server"; -import { parseMaybeJson } from "../utils"; -import { ensureSceneSafeToSwitch } from "./scene-tools"; -import { processImage, takeEditorScreenshot } from "../screenshot"; - -export class DebugTools implements ToolCategory { - readonly categoryName = "debug"; - - getTools(): ToolDefinition[] { - return [ - { - name: "debug_get_editor_info", - description: "Get Cocos Creator editor information (version, platform, language).", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "debug_list_messages", - description: "List available Editor messages for a given extension or built-in module.", - inputSchema: { - type: "object", - properties: { - target: { type: "string", description: "Message target (e.g. 'scene', 'asset-db', 'extension')" }, - }, - required: ["target"], - }, - }, - { - name: "debug_execute_script", - description: "Execute a custom scene script method. The method must be registered in scene.ts.", - inputSchema: { - type: "object", - properties: { - method: { type: "string", description: "Method name from scene.ts" }, - args: { type: "array", description: "Arguments to pass", items: {} }, - }, - required: ["method"], - }, - }, - { - name: "debug_get_console_logs", - description: "Get recent console log entries. Automatically captures scene process logs (console.log/warn/error in scene scripts). Game preview logs can also be captured by sending POST requests to /log endpoint — see README for setup.", - inputSchema: { - type: "object", - properties: { - count: { type: "number", description: "Max number of entries (default 50)" }, - level: { type: "string", description: "Filter by level: 'log', 'warn', or 'error'" }, - source: { type: "string", description: "Filter by source: 'scene' or 'game'. Returns both if omitted." }, - }, - }, - }, - { - name: "debug_clear_console", - description: "Clear the editor console.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "debug_list_extensions", - description: "List all installed extensions.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "debug_get_project_logs", - description: "Read recent project log entries from the log file.", - inputSchema: { - type: "object", - properties: { - lines: { type: "number", description: "Number of lines to read (default 100)" }, - }, - }, - }, - { - name: "debug_search_project_logs", - description: "Search for a pattern in project logs.", - inputSchema: { - type: "object", - properties: { - pattern: { type: "string", description: "Search pattern (regex supported)" }, - }, - required: ["pattern"], - }, - }, - { - name: "debug_get_log_file_info", - description: "Get information about the project log file (size, path, last modified).", - inputSchema: { type: "object", properties: {} }, - }, - // ── 以下、既存MCP未対応のEditor API ── - { - name: "debug_query_devices", - description: "List connected devices (for native debugging).", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "debug_open_url", - description: "Open a URL in the system browser from the editor.", - inputSchema: { - type: "object", - properties: { url: { type: "string", description: "URL to open" } }, - required: ["url"], - }, - }, - { - name: "debug_validate_scene", - description: "Validate the current scene for common issues.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "debug_game_command", - description: "Send a command to the running game preview. Requires GameDebugClient in the game. Commands: 'screenshot' (capture game canvas), 'state' (dump GameDb), 'navigate' (go to a page), 'click' (click a node by name), 'inspect' (get runtime node info: UITransform sizes, Widget, Layout, position). Returns the result from the game.", - inputSchema: { - type: "object", - properties: { - type: { type: "string", description: "Command type: 'screenshot', 'state', 'navigate', 'click', 'inspect'" }, - args: { type: "object", description: "Command arguments (e.g. {page: 'HomePageView'} for navigate, {name: 'ButtonName'} for click)" }, - timeout: { type: "number", description: "Max wait time in ms (default 5000)" }, - maxWidth: { type: "number", description: "Max width for screenshot resize (default: 960, 0 = no resize)" }, - imageFormat: { type: "string", description: "Screenshot output format: 'webp' (default, Q=85) or 'png' (lossless)" }, - }, - required: ["type"], - }, - }, - { - name: "debug_screenshot", - description: "Take a screenshot of the editor window and save to a file. Returns the file path of the saved PNG.", - inputSchema: { - type: "object", - properties: { - savePath: { type: "string", description: "File path to save the PNG (default: temp/screenshots/screenshot_.png)" }, - maxWidth: { type: "number", description: "Max width in pixels for resize (default: 960, 0 = no resize). Aspect ratio is preserved." }, - }, - }, - }, - { - name: "debug_preview", - description: "Start or stop the game preview. Uses Preview in Editor (auto-opens MainScene if needed). Falls back to browser preview if editor preview fails.", - inputSchema: { - type: "object", - properties: { - action: { type: "string", description: "'start' (default) or 'stop'" }, - waitForReady: { type: "boolean", description: "If true, wait until GameDebugClient connects after start (default: false)" }, - waitTimeout: { type: "number", description: "Max wait time in ms for waitForReady (default: 15000)" }, - }, - }, - }, - { - name: "debug_clear_code_cache", - description: "Clear the code cache (equivalent to Developer > Cache > Clear code cache) and soft-reload the scene.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "debug_reload_extension", - description: "Reload the MCP extension itself. Use after npm run build to apply code changes without restarting CocosCreator. Response is sent before reload starts.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "debug_get_extension_info", - description: "Get detailed information about a specific extension.", - inputSchema: { - type: "object", - properties: { - name: { type: "string", description: "Extension name" }, - }, - required: ["name"], - }, - }, - { - name: "debug_record_start", - description: "Start recording the game preview canvas to a video file. Uses MediaRecorder on the game side. Bitrate is auto-calculated from canvas resolution × fps × quality coefficient unless videoBitsPerSecond is set explicitly.", - inputSchema: { - type: "object", - properties: { - fps: { type: "number", description: "Frames per second (default: 30)" }, - quality: { type: "string", description: "'low'/'medium'/'high'/'ultra' (default: medium). Coefficients: 0.15/0.25/0.40/0.60" }, - coefficient: { type: "number", description: "Custom bitrate coefficient (width × height × fps × coefficient). Overrides quality." }, - videoBitsPerSecond: { type: "number", description: "Explicit bitrate in bps. Overrides quality-based calculation." }, - format: { type: "string", description: "'mp4' (default) or 'webm'. mp4 falls back to webm if not supported." }, - savePath: { type: "string", description: "Save directory (project-relative or absolute). Default: temp/recordings" }, - }, - }, - }, - { - name: "debug_record_stop", - description: "Stop recording started by debug_record_start. Returns the saved WebM file path and size. Video is saved to project's temp/recordings/rec_.webm.", - inputSchema: { - type: "object", - properties: { - timeout: { type: "number", description: "Max wait time in ms for file upload (default: 30000)" }, - }, - }, - }, - { - name: "debug_batch_screenshot", - description: "Navigate to multiple pages and take a screenshot of each. Requires game preview running with GameDebugClient. Returns an array of screenshot file paths.", - inputSchema: { - type: "object", - properties: { - pages: { - type: "array", - items: { type: "string" }, - description: "List of page names to screenshot (e.g. ['HomePageView', 'ShopPageView'])", - }, - delay: { type: "number", description: "Delay in ms between navigate and screenshot (default: 1000)" }, - maxWidth: { type: "number", description: "Max width for screenshot resize (default: 960)" }, - }, - required: ["pages"], - }, - }, - { - name: "debug_wait_compile", - description: "Wait for TypeScript compilation to complete. Monitors the packer-driver debug log for 'Target(editor) ends' message. Use after modifying .ts files to ensure changes are compiled before operating on Prefabs. With clean=true, deletes compiled output first to force a fresh recompile (slower but guaranteed).", - inputSchema: { - type: "object", - properties: { - timeout: { type: "number", description: "Max wait time in ms (default: 15000)" }, - clean: { type: "boolean", description: "If true, delete compiled output first to force fresh recompile (default: false)" }, - }, - }, - }, - ]; - } - - async execute(toolName: string, args: Record): Promise { - try { - switch (toolName) { - case "debug_get_editor_info": - return this.getEditorInfo(); - case "debug_list_messages": - return this.listMessages(args.target); - case "debug_execute_script": - return this.executeScript(args.method, args.args || []); - case "debug_get_console_logs": - return this.getConsoleLogs(args.count || 50, args.level, args.source); - case "debug_clear_console": - Editor.Message.send("console", "clear"); - // Clear scene process log buffer - await Editor.Message.request("scene", "execute-scene-script", { - name: "cocos-creator-mcp", - method: "clearConsoleLogs", - args: [], - }).catch(() => {}); - // Clear game preview log buffer - clearGameLogs(); - return ok({ success: true }); - case "debug_list_extensions": - return this.listExtensions(); - case "debug_get_project_logs": - return this.getProjectLogs(args.lines || 100); - case "debug_search_project_logs": - return this.searchProjectLogs(args.pattern); - case "debug_get_log_file_info": - return this.getLogFileInfo(); - case "debug_query_devices": { - const devices = await (Editor.Message.request as any)("device", "query").catch(() => []); - return ok({ success: true, devices }); - } - case "debug_open_url": - await (Editor.Message.request as any)("program", "open-url", args.url); - return ok({ success: true, url: args.url }); - case "debug_game_command": - return this.gameCommand(args.type || args.command, parseMaybeJson(args.args), args.timeout || 5000, args.maxWidth, args.imageFormat); - case "debug_screenshot": - return this.takeScreenshot(args.savePath, args.maxWidth); - case "debug_preview": - return this.handlePreview(args.action || "start", args.waitForReady, args.waitTimeout || 15000); - case "debug_clear_code_cache": - return this.clearCodeCache(); - case "debug_reload_extension": - return this.reloadExtension(); - case "debug_validate_scene": - return this.validateScene(); - case "debug_get_extension_info": - return this.getExtensionInfo(args.name); - case "debug_batch_screenshot": - return this.batchScreenshot(args.pages, args.delay || 1000, args.maxWidth); - case "debug_record_start": - return this.gameCommand("record_start", { fps: args.fps, quality: args.quality, coefficient: args.coefficient, videoBitsPerSecond: args.videoBitsPerSecond, format: args.format, savePath: args.savePath }, 5000); - case "debug_record_stop": - return this.gameCommand("record_stop", undefined, args.timeout || 30000); - case "debug_wait_compile": - return this.waitCompile(args.timeout || 15000, args.clean ?? false); - default: - return err(`Unknown tool: ${toolName}`); - } - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async getEditorInfo(): Promise { - return ok({ - success: true, - version: Editor.App.version, - path: Editor.App.path, - home: Editor.App.home, - language: Editor.I18n?.getLanguage?.() || "unknown", - }); - } - - private async listMessages(target: string): Promise { - try { - const info = await (Editor.Message.request as any)("extension", "query-info", target); - return ok({ success: true, target, info }); - } catch (e: any) { - const knownMessages: Record = { - "scene": [ - "query-node-tree", "create-node", "remove-node", "duplicate-node", - "set-property", "create-prefab", "save-scene", "execute-scene-script", - "query-is-dirty", "query-classes", "soft-reload", "snapshot", - "change-gizmo-tool", "query-gizmo-tool-name", "focus-camera-on-nodes", - ], - "asset-db": [ - "query-assets", "query-asset-info", "query-asset-meta", - "refresh-asset", "save-asset", "create-asset", "delete-asset", - "move-asset", "copy-asset", "open-asset", "reimport-asset", - "query-path", "query-uuid", "query-url", "query-asset-depends", - ], - }; - const messages = knownMessages[target]; - if (messages) { - return ok({ success: true, target, messages, note: "Static list (query failed)" }); - } - return err(e.message || String(e)); - } - } - - private async executeScript(method: string, args: any[]): Promise { - const result = await Editor.Message.request("scene", "execute-scene-script", { - name: "cocos-creator-mcp", - method, - args, - }); - return ok(result); - } - - private async getConsoleLogs(count: number, level?: string, source?: string): Promise { - // 1. Try Editor's native console API first (may be supported in future CocosCreator versions) - if (!source) { - try { - const logs = await (Editor.Message.request as any)("console", "query-last-logs", count); - if (Array.isArray(logs) && logs.length > 0) { - return ok({ success: true, logs, source: "editor-api", note: "Using native Editor console API" }); - } - } catch { /* Not supported in this version — use fallback */ } - } - - // 2. Fallback: collect from scene process buffer + game preview buffer - let sceneLogs: any[] = []; - let gameLogs: any[] = []; - - // 2a. Scene process logs (console wrapper in scene.ts) - if (!source || source === "scene") { - try { - const result = await Editor.Message.request("scene", "execute-scene-script", { - name: "cocos-creator-mcp", - method: "getConsoleLogs", - args: [count * 2, level], // request more, will trim after merge - }); - if (result?.logs) { - sceneLogs = result.logs.map((l: any) => ({ ...l, source: "scene" })); - } - } catch { /* scene not available */ } - } - - // 2b. Game preview logs (received via POST /log endpoint) - if (!source || source === "game") { - const gameResult = getGameLogs(count * 2, level); - gameLogs = gameResult.logs.map((l: any) => ({ ...l, source: "game" })); - } - - // Merge and sort by timestamp, take last `count` - const merged = [...sceneLogs, ...gameLogs] - .sort((a, b) => a.timestamp.localeCompare(b.timestamp)) - .slice(-count); - - return ok({ - success: true, - logs: merged, - total: { scene: sceneLogs.length, game: (source === "scene" ? 0 : gameLogs.length) }, - }); - } - - private async listExtensions(): Promise { - try { - const list = await (Editor.Message.request as any)("extension", "query-all"); - return ok({ success: true, extensions: list }); - } catch { - return ok({ success: true, extensions: [], note: "Extension query not supported" }); - } - } - - private async getExtensionInfo(name: string): Promise { - try { - const info = await (Editor.Message.request as any)("extension", "query-info", name); - return ok({ success: true, name, info }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async getProjectLogs(lines: number): Promise { - try { - const fs = require("fs"); - const path = require("path"); - const logPath = path.join(Editor.Project.tmpDir, "logs", "project.log"); - if (!fs.existsSync(logPath)) return ok({ success: true, logs: [], note: "Log file not found" }); - const content = fs.readFileSync(logPath, "utf-8"); - const allLines = content.split("\n"); - const recent = allLines.slice(-lines); - return ok({ success: true, lines: recent.length, logs: recent }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async searchProjectLogs(pattern: string): Promise { - try { - const fs = require("fs"); - const path = require("path"); - const logPath = path.join(Editor.Project.tmpDir, "logs", "project.log"); - if (!fs.existsSync(logPath)) return ok({ success: true, matches: [] }); - const content = fs.readFileSync(logPath, "utf-8"); - const regex = new RegExp(pattern, "gi"); - const matches = content.split("\n").filter((line: string) => regex.test(line)); - return ok({ success: true, pattern, count: matches.length, matches: matches.slice(0, 100) }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async getLogFileInfo(): Promise { - try { - const fs = require("fs"); - const path = require("path"); - const logPath = path.join(Editor.Project.tmpDir, "logs", "project.log"); - if (!fs.existsSync(logPath)) return ok({ success: true, exists: false }); - const stat = fs.statSync(logPath); - return ok({ success: true, exists: true, path: logPath, size: stat.size, modified: stat.mtime }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async handlePreview(action: string, waitForReady?: boolean, waitTimeout?: number): Promise { - if (action === "stop") { - return this.stopPreview(); - } - const result = await this.startPreview(); - if (waitForReady) { - const resultData = JSON.parse(result.content[0].text); - if (resultData.success) { - const ready = await this.waitForGameReady(waitTimeout || 15000); - resultData.gameReady = ready; - if (!ready) { - resultData.note = (resultData.note || "") + " GameDebugClient did not connect within timeout."; - } - return ok(resultData); - } - } - return result; - } - - private async waitForGameReady(timeout: number): Promise { - const start = Date.now(); - while (Date.now() - start < timeout) { - // Check if game has sent any log or command result recently - const gameResult = getGameLogs(1); - if (gameResult.total > 0) return true; - await new Promise(r => setTimeout(r, 500)); - } - return false; - } - - private async startPreview(): Promise { - try { - await this.ensureMainSceneOpen(); - - // ツールバーのVueインスタンス経由でplay()を呼ぶ(UI状態も同期される) - const played = await this.executeOnToolbar("start"); - if (played) { - return ok({ success: true, action: "start", mode: "editor" }); - } - - // フォールバック: 直接API - const isPlaying = await (Editor.Message.request as any)("scene", "editor-preview-set-play", true); - return ok({ success: true, isPlaying, action: "start", mode: "editor", note: "direct API (toolbar UI may not sync)" }); - } catch (e: any) { - try { - const electron = require("electron"); - await electron.shell.openExternal("http://127.0.0.1:7456"); - return ok({ success: true, action: "start", mode: "browser" }); - } catch (e2: any) { - return err(e2.message || String(e2)); - } - } - } - - private async stopPreview(): Promise { - try { - // ツールバー経由で停止(UI同期) - const stopped = await this.executeOnToolbar("stop"); - if (!stopped) { - // フォールバック: 直接API - await (Editor.Message.request as any)("scene", "editor-preview-set-play", false); - } - // scene:preview-stop ブロードキャストでツールバーUI状態をリセット - Editor.Message.broadcast("scene:preview-stop"); - // シーンビューに戻す - await new Promise(r => setTimeout(r, 500)); - await this.ensureMainSceneOpen(); - return ok({ success: true, action: "stop" }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async executeOnToolbar(action: "start" | "stop"): Promise { - try { - const electron = require("electron"); - const allContents = electron.webContents.getAllWebContents(); - for (const wc of allContents) { - try { - // play()をawaitしない — プレビュー完了を待つとタイムアウトするため - if (action === "start") { - const result = await wc.executeJavaScript( - `(function() { if (window.xxx && window.xxx.play && !window.xxx.gameView.isPlay) { window.xxx.play(); return true; } return false; })()` - ); - if (result) return true; - } else { - const result = await wc.executeJavaScript( - `(function() { if (window.xxx && window.xxx.gameView.isPlay) { window.xxx.play(); return true; } return false; })()` - ); - if (result) return true; - } - } catch { /* not the toolbar webContents */ } - } - } catch { /* electron API not available */ } - return false; - } - - private async ensureMainSceneOpen(): Promise { - const hierarchy = await Editor.Message.request("scene", "execute-scene-script", { - name: "cocos-creator-mcp", - method: "getSceneHierarchy", - args: [false], - }).catch(() => null); - - if (!hierarchy?.sceneName || hierarchy.sceneName === "scene-2d") { - // プロジェクト設定のStart Sceneを参照 - let sceneUuid: string | null = null; - try { - sceneUuid = await (Editor as any).Profile.getConfig("preview", "general.start_scene", "local"); - } catch { /* ignore */ } - - // Start Sceneが未設定 or "current_scene" の場合、最初のシーンを使う - if (!sceneUuid || sceneUuid === "current_scene") { - const scenes = await Editor.Message.request("asset-db", "query-assets", { - ccType: "cc.SceneAsset", - pattern: "db://assets/**/*", - }); - if (Array.isArray(scenes) && scenes.length > 0) { - sceneUuid = scenes[0].uuid; - } - } - - if (sceneUuid) { - // debug_preview 内部の自動遷移は preview を優先して force=true - // (dialog 出るより preview 開始を優先する運用) - await ensureSceneSafeToSwitch(true); - await (Editor.Message.request as any)("scene", "open-scene", sceneUuid); - await new Promise(r => setTimeout(r, 1500)); - } - } - } - - private async clearCodeCache(): Promise { - try { - const electron = require("electron"); - const menu = electron.Menu.getApplicationMenu(); - if (!menu) return err("Application menu not found"); - - const findMenuItem = (items: any[], path: string[]): any => { - for (const item of items) { - if (item.label === path[0]) { - if (path.length === 1) return item; - if (item.submenu?.items) return findMenuItem(item.submenu.items, path.slice(1)); - } - } - return null; - }; - - const cacheItem = findMenuItem(menu.items, ["Developer", "Cache", "Clear code cache"]); - if (!cacheItem) return err("Menu item 'Developer > Cache > Clear code cache' not found"); - - cacheItem.click(); - await new Promise(r => setTimeout(r, 1000)); - return ok({ success: true, note: "Code cache cleared via menu" }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async gameCommand(type: string, args: any, timeout: number, maxWidth?: number, imageFormat?: string): Promise { - const cmdId = queueGameCommand(type, args); - - // Poll for result - const start = Date.now(); - while (Date.now() - start < timeout) { - const result = getCommandResult(); - if (result && result.id === cmdId) { - // If screenshot, save to file and return path - if (type === "screenshot" && result.success && result.data?.dataUrl) { - try { - const fs = require("fs"); - const path = require("path"); - const dir = path.join(Editor.Project.tmpDir, "screenshots"); - if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); - const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); - const base64 = result.data.dataUrl.replace(/^data:image\/png;base64,/, ""); - const pngBuffer = Buffer.from(base64, "base64"); - const effectiveMaxWidth = maxWidth !== undefined ? maxWidth : 960; - const electron = require("electron"); - const origImage = electron.nativeImage.createFromBuffer(pngBuffer); - const originalSize = origImage.getSize(); - const { buffer, width, height, format } = await processImage(pngBuffer, effectiveMaxWidth, imageFormat); - const ext = format === "webp" ? "webp" : format === "jpeg" ? "jpg" : "png"; - const filePath = path.join(dir, `game_${timestamp}.${ext}`); - fs.writeFileSync(filePath, buffer); - return ok({ - success: true, path: filePath, size: buffer.length, format, - originalSize: `${originalSize.width}x${originalSize.height}`, - savedSize: `${width}x${height}`, - }); - } catch (e: any) { - return ok({ success: true, note: "Screenshot captured but file save failed", error: e.message }); - } - } - return ok(result); - } - await new Promise(r => setTimeout(r, 200)); - } - return err(`Game did not respond within ${timeout}ms. Is GameDebugClient running in the preview?`); - } - - private async takeScreenshot(savePath?: string, maxWidth?: number): Promise { - try { - const result = await takeEditorScreenshot(savePath, maxWidth); - return ok(result); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async reloadExtension(): Promise { - // Schedule reload after response is sent - setTimeout(async () => { - try { - await (Editor.Message.request as any)("extension", "reload", "cocos-creator-mcp"); - } catch (e: any) { - console.error("[MCP] Extension reload failed:", e.message); - } - }, 500); - return ok({ success: true, note: "Extension reload scheduled. MCP server will restart in ~1s. NOTE: Adding new tool definitions or modifying scene.ts requires a full CocosCreator restart (reload is not sufficient)." }); - } - - private async batchScreenshot(pages: string[], delay: number, maxWidth?: number): Promise { - const results: any[] = []; - const timeout = 10000; - - for (const page of pages) { - // Navigate - const navResult = await this.gameCommand("navigate", { page }, timeout, maxWidth); - const navData = JSON.parse(navResult.content[0].text); - if (!navData.success) { - results.push({ page, success: false, error: "navigate failed" }); - continue; - } - - // Wait for page to render - await new Promise(r => setTimeout(r, delay)); - - // Screenshot - const ssResult = await this.gameCommand("screenshot", {}, timeout, maxWidth); - const ssData = JSON.parse(ssResult.content[0].text); - results.push({ - page, - success: ssData.success || false, - path: ssData.path, - error: ssData.success ? undefined : (ssData.error || ssData.message), - }); - } - - const succeeded = results.filter(r => r.success).length; - return ok({ - success: true, - total: pages.length, - succeeded, - failed: pages.length - succeeded, - results, - }); - } - - private async validateScene(): Promise { - try { - const tree = await Editor.Message.request("scene", "query-node-tree"); - const issues: string[] = []; - const checkNodes = (nodes: any[]) => { - if (!nodes) return; - for (const node of nodes) { - if (!node.name) issues.push(`Node ${node.uuid} has no name`); - if (node.children) checkNodes(node.children); - } - }; - if (Array.isArray(tree)) checkNodes(tree); - return ok({ success: true, issueCount: issues.length, issues }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - /** - * TypeScript コンパイル完了を待つ。 - * packer-driver の debug.log に "Target(editor) ends" が現れるのを監視する。 - * 既にコンパイル済み(直近数秒以内に完了ログあり)なら即座に返す。 - */ - private async waitCompile(timeout: number, clean: boolean): Promise { - try { - const fs = require("fs"); - const path = require("path"); - const logPath = path.join(Editor.Project.path, "temp", "programming", "packer-driver", "logs", "debug.log"); - const chunksDir = path.join(Editor.Project.path, "temp", "programming", "packer-driver", "targets", "editor", "chunks"); - - if (!fs.existsSync(logPath)) { - return err(`Compile log not found: ${logPath}`); - } - - const MARKER = "Target(editor) ends"; - - // clean モード: コードキャッシュクリア + soft-reload で再コンパイルを強制 - if (clean) { - // Developer > Cache > Clear code cache をクリック - try { - const electron = require("electron"); - const menu = electron.Menu.getApplicationMenu(); - const findMenuItem = (items: any[], labels: string[]): any => { - for (const item of items) { - if (item.label === labels[0]) { - if (labels.length === 1) return item; - if (item.submenu?.items) return findMenuItem(item.submenu.items, labels.slice(1)); - } - } - return null; - }; - const cacheItem = menu ? findMenuItem(menu.items, ["Developer", "Cache", "Clear code cache"]) : null; - if (cacheItem) cacheItem.click(); - } catch (_e) { /* ignore */ } - await new Promise(r => setTimeout(r, 500)); - // soft-reload でシーンを再読み込み → コンパイルトリガー - await (Editor.Message.request as any)("scene", "soft-reload").catch(() => {}); - } - - // refresh-asset でファイル変更を CC に通知してコンパイルをトリガー - await (Editor.Message.request as any)("asset-db", "refresh-asset", "db://assets").catch(() => {}); - - const initialSize = fs.statSync(logPath).size; - const startTime = Date.now(); - const POLL_INTERVAL = 200; - const DETECT_GRACE_MS = 2000; // CC がファイル変更を検知するまでの猶予 - - while (Date.now() - startTime < timeout) { - await new Promise(r => setTimeout(r, POLL_INTERVAL)); - - const currentSize = fs.statSync(logPath).size; - - // ログが成長していない - if (currentSize <= initialSize) { - // clean モードでは必ずコンパイルが走るので猶予判定しない - if (clean) continue; - // 猶予期間内はまだ待つ (CC の検知が遅い可能性) - if (Date.now() - startTime < DETECT_GRACE_MS) continue; - // 猶予期間を過ぎてもログが成長しない → コンパイル不要 - return ok({ success: true, compiled: true, waitedMs: Date.now() - startTime, note: "No compilation triggered (no changes detected)" }); - } - - // ログが成長した → 新しい部分にマーカーがあるか確認 - const fd = fs.openSync(logPath, "r"); - const newBytes = currentSize - initialSize; - const buffer = Buffer.alloc(newBytes); - fs.readSync(fd, buffer, 0, newBytes, initialSize); - fs.closeSync(fd); - const newContent = buffer.toString("utf8"); - - if (newContent.includes(MARKER)) { - return ok({ success: true, compiled: true, waitedMs: Date.now() - startTime }); - } - } - - return ok({ success: true, compiled: false, timeout: true, waitedMs: timeout }); - } catch (e: any) { - return err(e.message || String(e)); - } - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/node-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/node-tools.ts deleted file mode 100644 index 3ae56b2..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/node-tools.ts +++ /dev/null @@ -1,624 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; -import { parseMaybeJson } from "../utils"; -import { resolveNodeUuid } from "../node-resolve"; -import { takeEditorScreenshot } from "../screenshot"; - -const EXT_NAME = "cocos-creator-mcp"; - -export class NodeTools implements ToolCategory { - readonly categoryName = "node"; - - getTools(): ToolDefinition[] { - return [ - { - name: "node_create", - description: "Create a new node in the scene.", - inputSchema: { - type: "object", - properties: { - name: { type: "string", description: "Node name" }, - parent: { type: "string", description: "Parent node UUID (optional, defaults to scene root)" }, - components: { - type: "array", - items: { type: "string" }, - description: "Component class names to add (e.g. ['cc.Label', 'cc.Sprite'])", - }, - }, - required: ["name"], - }, - }, - { - name: "node_get_info", - description: "Get detailed information about a node by UUID, including components.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "node_find_by_name", - description: "Find all nodes matching a given name.", - inputSchema: { - type: "object", - properties: { - name: { type: "string", description: "Node name to search" }, - }, - required: ["name"], - }, - }, - { - name: "node_set_property", - description: "Set a property on a node (name, active, position, rotation, scale, etc.).", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - property: { type: "string", description: "Property name (e.g. 'name', 'active', 'position')" }, - value: { description: "Value to set. For position/rotation/scale use {x,y,z}." }, - }, - required: ["uuid", "property", "value"], - }, - }, - { - name: "node_set_transform", - description: "Set position, rotation, and/or scale of a node at once.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - position: { - type: "object", - properties: { x: { type: "number" }, y: { type: "number" }, z: { type: "number" } }, - description: "Position {x,y,z}", - }, - rotation: { - type: "object", - properties: { x: { type: "number" }, y: { type: "number" }, z: { type: "number" } }, - description: "Euler rotation {x,y,z}", - }, - scale: { - type: "object", - properties: { x: { type: "number" }, y: { type: "number" }, z: { type: "number" } }, - description: "Scale {x,y,z}", - }, - }, - required: ["uuid"], - }, - }, - { - name: "node_delete", - description: "Delete a node by UUID.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "node_move", - description: "Move a node to a new parent.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - parentUuid: { type: "string", description: "New parent node UUID" }, - }, - required: ["uuid", "parentUuid"], - }, - }, - { - name: "node_duplicate", - description: "Duplicate a node.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID to duplicate" }, - }, - required: ["uuid"], - }, - }, - { - name: "node_get_all", - description: "Get a flat list of all nodes in the current scene.", - inputSchema: { - type: "object", - properties: {}, - }, - }, - { - name: "node_set_active", - description: "Set a node's active (visible) state.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - active: { type: "boolean", description: "Whether the node is active" }, - }, - required: ["uuid", "active"], - }, - }, - { - name: "node_set_layer", - description: "Set a node's layer.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - layer: { type: "number", description: "Layer value" }, - }, - required: ["uuid", "layer"], - }, - }, - { - name: "node_detect_type", - description: "Detect node type (2D, 3D, or regular Node) based on its components.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "node_create_tree", - description: "Create a full node tree from a JSON spec in one call. Much faster than creating nodes one by one. Spec format: { name, components?: ['cc.UITransform'], properties?: {'cc.UITransform.contentSize': {width:720,height:1280}}, widget?: {top:0, bottom:0, left:0, right:0}, active?: bool, position?: {x,y,z}, children?: [...] }", - inputSchema: { - type: "object", - properties: { - parent: { type: "string", description: "Parent node UUID" }, - spec: { description: "Node tree specification (JSON object with name, components, properties, children)" }, - }, - required: ["parent", "spec"], - }, - }, - { - name: "node_set_layout", - description: "Set UITransform (contentSize, anchorPoint) and Widget (margins) on a node in one call. Much faster than calling component_set_property multiple times for layout adjustments. Set screenshot=true to capture the editor after changes.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID (either uuid or nodeName required)" }, - nodeName: { type: "string", description: "Node name to find (alternative to uuid)" }, - contentSize: { - type: "object", - properties: { width: { type: "number" }, height: { type: "number" } }, - description: "UITransform contentSize {width, height}", - }, - anchorPoint: { - type: "object", - properties: { x: { type: "number" }, y: { type: "number" } }, - description: "UITransform anchorPoint {x, y} (0-1)", - }, - widget: { - type: "object", - properties: { - top: { type: "number" }, bottom: { type: "number" }, - left: { type: "number" }, right: { type: "number" }, - horizontalCenter: { type: "number" }, verticalCenter: { type: "number" }, - isAlignTop: { type: "boolean" }, isAlignBottom: { type: "boolean" }, - isAlignLeft: { type: "boolean" }, isAlignRight: { type: "boolean" }, - isAlignHorizontalCenter: { type: "boolean" }, isAlignVerticalCenter: { type: "boolean" }, - }, - description: "Widget alignment margins. Setting a value (e.g. top:0) automatically enables the corresponding alignment (isAlignTop:true).", - }, - color: { - type: "object", - properties: { r: { type: "number" }, g: { type: "number" }, b: { type: "number" }, a: { type: "number" } }, - description: "Node color {r,g,b,a} (0-255)", - }, - opacity: { type: "number", description: "Node opacity (0-255)" }, - screenshot: { type: "boolean", description: "If true, capture editor screenshot after changes (default: false)" }, - }, - }, - }, - ]; - } - - async execute(toolName: string, args: Record): Promise { - const rejected = await this.rejectIfPreviewRunning(toolName); - if (rejected) return rejected; - - switch (toolName) { - case "node_create": - return this.createNode(args.name, args.parent, args.components); - case "node_get_info": - return this.getNodeInfo(args.uuid); - case "node_find_by_name": - return this.findByName(args.name); - case "node_set_property": - return this.setProperty(args.uuid, args.property, parseMaybeJson(args.value)); - case "node_set_transform": - return this.setTransform(args.uuid, args.position, args.rotation, args.scale); - case "node_delete": - return this.deleteNode(args.uuid); - case "node_move": - return this.moveNode(args.uuid, args.parentUuid); - case "node_duplicate": - return this.duplicateNode(args.uuid); - case "node_get_all": - return this.getAllNodes(); - case "node_set_active": - return this.setProperty(args.uuid, "active", args.active); - case "node_set_layer": - return this.setProperty(args.uuid, "layer", args.layer); - case "node_create_tree": - return this.createNodeTree(args.parent, parseMaybeJson(args.spec)); - case "node_set_layout": - return this.setLayout(args); - case "node_detect_type": { - try { - const info = await this.sceneScript("getNodeInfo", [args.uuid]); - if (!info.success) return ok(info); - const comps = info.data?.components || []; - const compTypes = comps.map((c: any) => c.type); - let nodeType = "Node"; - if (compTypes.includes("UITransform")) nodeType = "2D"; - else if (compTypes.includes("MeshRenderer") || compTypes.includes("Camera")) nodeType = "3D"; - return ok({ success: true, uuid: args.uuid, nodeType, components: compTypes }); - } catch (e: any) { return err(e.message || String(e)); } - } - default: - return err(`Unknown tool: ${toolName}`); - } - } - - /** Scene editing tools that must not run during preview */ - private static readonly SCENE_EDIT_TOOLS = new Set([ - "node_create", "node_delete", "node_move", "node_duplicate", - "node_set_property", "node_set_transform", "node_set_active", "node_set_layer", - "node_create_tree", "node_set_layout", - ]); - - private async rejectIfPreviewRunning(toolName: string): Promise { - if (!NodeTools.SCENE_EDIT_TOOLS.has(toolName)) return null; - try { - const state = await Editor.Message.request("preview", "query-info"); - if (state && (state as any).running) { - return err(`"${toolName}" はプレビュー中に実行できません。先にプレビューを停止してください。`); - } - } catch { /* query failed — allow execution */ } - return null; - } - - private async createNode(name: string, parent?: string, components?: string[]): Promise { - try { - // Use Editor API to create node - const uuid = await Editor.Message.request("scene", "create-node", { - parent: parent || undefined, - name, - assetUuid: undefined, - }); - - // Wait until the node is queryable in the scene process - await this.waitForNode(uuid); - - // Add components if specified - if (components && components.length > 0) { - for (const comp of components) { - await this.sceneScript("addComponentToNode", [uuid, comp]); - // Wait until the component is reflected in query-node - await this.waitForComponent(uuid, comp); - } - } - - return ok({ success: true, uuid, name }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - /** - * Wait until a node becomes queryable in the scene process. - * Editor.Message.request("scene", "create-node") returns before the node - * is fully registered in the scene hierarchy, so subsequent scene script - * calls (findNode) may fail without this wait. - */ - private async waitForNode(uuid: string, maxRetries = 10, intervalMs = 100): Promise { - for (let i = 0; i < maxRetries; i++) { - try { - const result = await this.sceneScript("getNodeInfo", [uuid]); - if (result?.success) return; - } catch { /* not ready yet */ } - await new Promise(resolve => setTimeout(resolve, intervalMs)); - } - // Don't throw — let the caller proceed and get a more specific error if needed - } - - /** - * Wait until a component added via addComponentToNode is reflected in query-node. - * sceneScript returns before the Editor API (query-node) reflects the change, - * so polling is needed to avoid race conditions in subsequent tool calls. - */ - private async waitForComponent(nodeUuid: string, componentType: string, maxRetries = 10, intervalMs = 100): Promise { - const normalizedType = componentType.startsWith("cc.") ? componentType.substring(3) : componentType; - for (let i = 0; i < maxRetries; i++) { - try { - const nodeDump = await (Editor.Message.request as any)("scene", "query-node", nodeUuid); - const comps: any[] = nodeDump?.__comps__ || []; - const found = comps.some((c) => - c.type === componentType || c.type === `cc.${normalizedType}` || c.type === normalizedType - ); - if (found) return; - } catch { /* not ready yet */ } - await new Promise(resolve => setTimeout(resolve, intervalMs)); - } - // Don't throw — component may still work; let caller get a specific error if needed - } - - private async createNodeTree(parentUuid: string, spec: any): Promise { - try { - const result = await this.sceneScript("buildNodeTree", [parentUuid, spec]); - if (!result?.success) return err(result?.error || "buildNodeTree failed"); - return ok(result); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async getNodeInfo(uuid: string): Promise { - try { - const result = await this.sceneScript("getNodeInfo", [uuid]); - return ok(result); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async findByName(name: string): Promise { - try { - const result = await this.sceneScript("findNodesByName", [name]); - return ok(result); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async setProperty(uuid: string, property: string, value: any): Promise { - try { - const result = await this.sceneScript("setNodeProperty", [uuid, property, value]); - return ok(result); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async setTransform(uuid: string, position?: any, rotation?: any, scale?: any): Promise { - try { - const results: any[] = []; - if (position) { - results.push(await this.sceneScript("setNodeProperty", [uuid, "position", position])); - } - if (rotation) { - results.push(await this.sceneScript("setNodeProperty", [uuid, "rotation", rotation])); - } - if (scale) { - results.push(await this.sceneScript("setNodeProperty", [uuid, "scale", scale])); - } - const anyFailed = results.find((r) => !r.success); - if (anyFailed) return ok(anyFailed); - return ok({ success: true, uuid }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async deleteNode(uuid: string): Promise { - try { - await Editor.Message.request("scene", "remove-node", { uuid }); - return ok({ success: true, uuid }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async moveNode(uuid: string, parentUuid: string): Promise { - try { - await (Editor.Message.request as any)("scene", "set-property", { - uuid, - path: "parent", - dump: { type: "cc.Node", value: { uuid: parentUuid } }, - }); - return ok({ success: true, uuid, parentUuid }); - } catch (e: any) { - // Fallback: try scene script - try { - const result = await this.sceneScript("moveNode", [uuid, parentUuid]); - return ok(result); - } catch (e2: any) { - return err(e.message || String(e)); - } - } - } - - private async duplicateNode(uuid: string): Promise { - try { - const result = await Editor.Message.request("scene", "duplicate-node", uuid); - // duplicate-node returns an array of UUIDs - const newUuid = Array.isArray(result) ? result[0] : result; - return ok({ success: true, sourceUuid: uuid, newUuid }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async getAllNodes(): Promise { - try { - const result = await this.sceneScript("getAllNodes", []); - return ok(result); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - /** - * UITransform + Widget + color/opacity をまとめて設定する。 - * Widget の値を指定すると、対応する isAlign* フラグを自動で true にする。 - */ - private async setLayout(args: Record): Promise { - try { - // nodeName → uuid 解決 - let uuid = args.uuid; - if (!uuid && args.nodeName) { - const resolved = await resolveNodeUuid({ nodeName: args.nodeName }); - uuid = resolved.uuid; - } - if (!uuid) return err("Either 'uuid' or 'nodeName' is required"); - - const results: any[] = []; - - // UITransform の設定 - const contentSize = parseMaybeJson(args.contentSize); - const anchorPoint = parseMaybeJson(args.anchorPoint); - if (contentSize || anchorPoint) { - const nodeInfo = await this.sceneScript("getNodeInfo", [uuid]); - if (!nodeInfo?.success) return err(`Node ${uuid} not found`); - const comps = nodeInfo.data?.components || []; - const utIdx = comps.findIndex((c: any) => c.type === "UITransform"); - if (utIdx < 0) return err("Node has no UITransform component"); - - if (contentSize) { - const path = `__comps__.${utIdx}.contentSize`; - const dump = { value: { width: { value: contentSize.width }, height: { value: contentSize.height } } }; - const r = await this.sceneScript("setPropertyViaEditor", [uuid, path, dump]); - results.push({ property: "contentSize", success: r?.success !== false }); - } - if (anchorPoint) { - const path = `__comps__.${utIdx}.anchorPoint`; - const dump = { value: { x: { value: anchorPoint.x }, y: { value: anchorPoint.y } } }; - const r = await this.sceneScript("setPropertyViaEditor", [uuid, path, dump]); - results.push({ property: "anchorPoint", success: r?.success !== false }); - } - } - - // Widget の設定 - const widget = parseMaybeJson(args.widget); - if (widget) { - // Widget コンポーネントを探す(なければ追加) - let nodeInfo = await this.sceneScript("getNodeInfo", [uuid]); - if (!nodeInfo?.success) return err(`Node ${uuid} not found`); - let comps = nodeInfo.data?.components || []; - let wIdx = comps.findIndex((c: any) => c.type === "Widget"); - if (wIdx < 0) { - await this.sceneScript("addComponentToNode", [uuid, "cc.Widget"]); - // 再取得 - nodeInfo = await this.sceneScript("getNodeInfo", [uuid]); - comps = nodeInfo.data?.components || []; - wIdx = comps.findIndex((c: any) => c.type === "Widget"); - if (wIdx < 0) return err("Failed to add Widget component"); - results.push({ property: "Widget", action: "added" }); - } - - // isAlign* を自動設定(値があれば true にする) - const alignMap: Record = { - top: "isAlignTop", bottom: "isAlignBottom", - left: "isAlignLeft", right: "isAlignRight", - horizontalCenter: "isAlignHorizontalCenter", - verticalCenter: "isAlignVerticalCenter", - }; - - for (const [key, value] of Object.entries(widget)) { - // isAlign* を明示指定した場合はそのまま設定 - const path = `__comps__.${wIdx}.${key}`; - if (typeof value === "boolean") { - const dump = { value, type: "Boolean" }; - await this.sceneScript("setPropertyViaEditor", [uuid, path, dump]); - results.push({ property: `Widget.${key}`, success: true }); - } else if (typeof value === "number") { - // まず対応する isAlign* を true にする - const alignKey = alignMap[key]; - if (alignKey && widget[alignKey] === undefined) { - const alignPath = `__comps__.${wIdx}.${alignKey}`; - await this.sceneScript("setPropertyViaEditor", [uuid, alignPath, { value: true, type: "Boolean" }]); - } - const dump = { value, type: "Number" }; - await this.sceneScript("setPropertyViaEditor", [uuid, path, dump]); - results.push({ property: `Widget.${key}`, success: true }); - } - } - - // _alignFlags を isAlign* 現在値から再計算して設定 - // (Editor が isAlign* 変更時に _alignFlags を自動更新しないバグの対処) - try { - const ALIGN_BITS: Record = { - isAlignLeft: 1, isAlignRight: 2, isAlignTop: 4, isAlignBottom: 8, - isAlignHorizontalCenter: 16, isAlignVerticalCenter: 32, - }; - const nodeDump = await (Editor.Message.request as any)("scene", "query-node", uuid); - if (nodeDump) { - const wCompDump = nodeDump.__comps__?.[wIdx]; - if (wCompDump) { - let alignFlags = 0; - for (const [key, bit] of Object.entries(ALIGN_BITS)) { - if (wCompDump.value?.[key]?.value === true) alignFlags |= bit; - } - const flagPath = `__comps__.${wIdx}._alignFlags`; - await this.sceneScript("setPropertyViaEditor", [uuid, flagPath, { value: alignFlags, type: "Number" }]); - results.push({ property: "Widget._alignFlags", value: alignFlags }); - } - } - } catch (_e) { - // _alignFlags 再計算の失敗は致命的でないため無視 - } - } - - // color - const color = parseMaybeJson(args.color); - if (color) { - const r = await this.sceneScript("setNodeProperty", [uuid, "color", color]); - results.push({ property: "color", success: r?.success !== false }); - } - - // opacity - if (args.opacity !== undefined) { - // cc.UIOpacity を使う(なければ color.a で設定) - const nodeInfo = await this.sceneScript("getNodeInfo", [uuid]); - const comps = nodeInfo?.data?.components || []; - const opIdx = comps.findIndex((c: any) => c.type === "UIOpacity"); - if (opIdx >= 0) { - const path = `__comps__.${opIdx}.opacity`; - await this.sceneScript("setPropertyViaEditor", [uuid, path, { value: args.opacity, type: "Number" }]); - results.push({ property: "UIOpacity.opacity", success: true }); - } else { - // UIOpacity がない場合は color.a を直接設定 - const currentColor = nodeInfo?.data?.color || { r: 255, g: 255, b: 255, a: 255 }; - currentColor.a = args.opacity; - const r = await this.sceneScript("setNodeProperty", [uuid, "color", currentColor]); - results.push({ property: "color.a", success: r?.success !== false }); - } - } - - const allOk = results.every(r => r.success !== false); - let response: any = { success: allOk, uuid, results }; - - // screenshot - if (args.screenshot) { - try { - const ss = await takeEditorScreenshot(); - response.screenshot = { path: ss.path, size: ss.savedSize }; - } catch (ssErr: any) { - response.screenshotError = ssErr.message || String(ssErr); - } - } - - return ok(response); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - /** Call a scene script method */ - private async sceneScript(method: string, args: any[]): Promise { - return Editor.Message.request("scene", "execute-scene-script", { - name: EXT_NAME, - method, - args, - }); - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/prefab-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/prefab-tools.ts deleted file mode 100644 index 60d85da..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/prefab-tools.ts +++ /dev/null @@ -1,680 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; -import { ensureSceneSafeToSwitch, safeSaveScene } from "./scene-tools"; -import { parseMaybeJson } from "../utils"; -import type { ComponentTools } from "./component-tools"; -import fs from "fs"; -import path from "path"; -import crypto from "crypto"; - -const EXT_NAME = "cocos-creator-mcp"; - -/** prefab_instantiate で配置したネスト Prefab 情報を記憶 */ -interface NestedPrefabEntry { - nodeUuid: string; - prefabAssetUuid: string; - parentUuid: string; -} - -export class PrefabTools implements ToolCategory { - readonly categoryName = "prefab"; - private _pendingNestedPrefabs: NestedPrefabEntry[] = []; - /** prefab_open で開いた Prefab アセット UUID */ - private _currentPrefabUuid: string | null = null; - private _componentTools: ComponentTools | null; - - constructor(componentTools?: ComponentTools) { - this._componentTools = componentTools ?? null; - } - - getTools(): ToolDefinition[] { - return [ - { - name: "prefab_list", - description: "List all prefab files in the project.", - inputSchema: { - type: "object", - properties: {}, - }, - }, - { - name: "prefab_create", - description: "Create a prefab from an existing node in the scene. The node remains in the scene.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID to create prefab from" }, - path: { type: "string", description: "db:// path for the prefab (e.g. 'db://assets/prefabs/MyPrefab.prefab')" }, - }, - required: ["uuid", "path"], - }, - }, - { - name: "prefab_instantiate", - description: "Instantiate a prefab into the scene.", - inputSchema: { - type: "object", - properties: { - prefabUuid: { type: "string", description: "Prefab asset UUID" }, - parent: { type: "string", description: "Parent node UUID (optional, defaults to scene root)" }, - }, - required: ["prefabUuid"], - }, - }, - { - name: "prefab_get_info", - description: "Get information about a prefab asset (name, path, UUID).", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Prefab asset UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "prefab_update", - description: "Update (re-save) a prefab from its instance node in the scene.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID of the prefab instance in the scene" }, - }, - required: ["uuid"], - }, - }, - { - name: "prefab_duplicate", - description: "Duplicate a prefab asset to a new path.", - inputSchema: { - type: "object", - properties: { - source: { type: "string", description: "Source prefab db:// path" }, - destination: { type: "string", description: "Destination db:// path" }, - }, - required: ["source", "destination"], - }, - }, - { - name: "prefab_validate", - description: "Validate a prefab for missing references or broken links.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Prefab asset UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "prefab_revert", - description: "Revert a prefab instance node to its original prefab state.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID of the prefab instance" }, - }, - required: ["uuid"], - }, - }, - { - name: "prefab_create_and_replace", - description: "Create a prefab from a node AND replace the original node with a prefab instance. This is the recommended way to extract a nested prefab — one command instead of create → delete → instantiate.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID to create prefab from" }, - path: { type: "string", description: "db:// path for the prefab (e.g. 'db://assets/prefabs/MyPrefab.prefab')" }, - }, - required: ["uuid", "path"], - }, - }, - { - name: "prefab_open", - description: "Open a prefab in editing mode. Equivalent to double-clicking the prefab in CocosCreator. Returns an error if the current scene is dirty and untitled (to avoid modal save dialog); pass force=true to bypass.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Prefab asset UUID" }, - path: { type: "string", description: "Prefab db:// path (alternative to uuid)" }, - force: { type: "boolean", description: "Skip dirty-scene preflight check (may trigger modal save dialog)" }, - }, - }, - }, - { - name: "prefab_close", - description: "Save and close the current prefab editing mode, then return to the main scene. Returns an error if the current prefab is dirty and untitled (to avoid modal save dialog); pass force=true to bypass.", - inputSchema: { - type: "object", - properties: { - save: { type: "boolean", description: "Save prefab before closing (default true)" }, - sceneUuid: { type: "string", description: "Scene UUID to return to (default: project's start scene or first scene)" }, - force: { type: "boolean", description: "Skip dirty-scene preflight check (may trigger modal save dialog)" }, - }, - }, - }, - { - name: "prefab_create_from_spec", - description: "Create a prefab from a JSON spec in one call. Combines node_create_tree + component_auto_bind + prefab_create into a single operation. Spec extends node_create_tree format with optional autoBind field. Example: { name: 'MyPopup', components: ['cc.UITransform', 'MyPopupView'], autoBind: 'MyPopupView', children: [{ name: 'CloseButton', components: ['cc.Button'] }] }", - inputSchema: { - type: "object", - properties: { - path: { type: "string", description: "db:// path for the prefab (e.g. 'db://assets/prefabs/MyPrefab.prefab')" }, - spec: { description: "Node tree specification with optional autoBind field (string for component type to auto-bind)" }, - autoBindMode: { type: "string", enum: ["fuzzy", "strict"], description: "Auto-bind matching mode (default: fuzzy)" }, - }, - required: ["path", "spec"], - }, - }, - ]; - } - - async execute(toolName: string, args: Record): Promise { - switch (toolName) { - case "prefab_list": - return this.listPrefabs(); - case "prefab_create": - return this.createPrefab(args.uuid, args.path); - case "prefab_instantiate": - return this.instantiatePrefab(args.prefabUuid, args.parent); - case "prefab_get_info": - return this.getPrefabInfo(args.uuid); - case "prefab_update": - return this.updatePrefab(args.uuid); - case "prefab_duplicate": { - try { - await (Editor.Message.request as any)("asset-db", "copy-asset", args.source, args.destination); - return ok({ success: true, source: args.source, destination: args.destination }); - } catch (e: any) { return err(e.message || String(e)); } - } - case "prefab_validate": { - try { - const info = await (Editor.Message.request as any)("asset-db", "query-asset-info", args.uuid); - const deps = await (Editor.Message.request as any)("asset-db", "query-depends", args.uuid).catch(() => []); - return ok({ success: true, uuid: args.uuid, info, dependencies: deps, valid: !!info }); - } catch (e: any) { return err(e.message || String(e)); } - } - case "prefab_revert": - return this.revertPrefab(args.uuid); - case "prefab_create_and_replace": - return this.createAndReplace(args.uuid, args.path); - case "prefab_open": - return this.openPrefab(args.uuid, args.path, !!args.force); - case "prefab_close": - return this.closePrefab(args.save !== false, args.sceneUuid, !!args.force); - case "prefab_create_from_spec": - return this.createFromSpec(args.path, parseMaybeJson(args.spec), args.autoBindMode ?? "fuzzy"); - default: - return err(`Unknown tool: ${toolName}`); - } - } - - private async listPrefabs(): Promise { - try { - const results = await Editor.Message.request("asset-db", "query-assets", { - pattern: "db://assets/**/*.prefab", - }); - const prefabs = (results || []).map((a: any) => ({ - uuid: a.uuid, - path: a.path || a.url, - name: a.name, - })); - return ok({ success: true, prefabs }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async createPrefab(nodeUuid: string, path: string): Promise { - try { - // 既存Prefabがある場合は警告を返す(上書きダイアログでタイムアウトするため) - const existing = await this.assetExists(path); - if (existing) { - return err( - `Prefab already exists at "${path}". Use prefab_update instead to update an existing prefab. ` + - `Workflow: 1) prefab_instantiate to place in scene, 2) modify properties, 3) prefab_update to save.` - ); - } - const result = await (Editor.Message.request as any)("scene", "create-prefab", nodeUuid, path); - return ok({ success: true, nodeUuid, path, result }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async assetExists(path: string): Promise { - try { - const pattern = path.replace(/\.prefab$/, "") + ".*"; - const results = await Editor.Message.request("asset-db", "query-assets", { pattern }); - return (results || []).length > 0; - } catch { - try { - const info = await (Editor.Message.request as any)("asset-db", "query-asset-info", path); - return !!info; - } catch { - return false; - } - } - } - - private async instantiatePrefab(prefabUuid: string, parent?: string): Promise { - try { - const nodeUuid = await Editor.Message.request("scene", "create-node", { - parent: parent || undefined, - assetUuid: prefabUuid, - }); - - // Prefab 編集モード中の場合、ネスト Prefab 情報を記憶 - // prefab_update 時に JSON 後処理で asset/instance/nestedPrefabInstanceRoots を設定 - if (parent) { - this._pendingNestedPrefabs.push({ - nodeUuid, - prefabAssetUuid: prefabUuid, - parentUuid: parent, - }); - } - - return ok({ success: true, nodeUuid, prefabUuid }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - - private async updatePrefab(nodeUuid: string): Promise { - try { - const result = await (Editor.Message.request as any)("scene", "apply-prefab", nodeUuid); - - // ネスト Prefab の JSON 後処理 - if (this._pendingNestedPrefabs.length > 0) { - await this._fixNestedPrefabJson(nodeUuid); - } - - return ok({ success: true, nodeUuid, result }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - /** - * prefab_update 後に Prefab JSON を後処理して、ネスト Prefab 参照を正しく設定する. - */ - private async _fixNestedPrefabJson(_rootNodeUuid: string): Promise { - if (this._pendingNestedPrefabs.length === 0) return; - - try { - // シーンを保存して Prefab JSON を書き出す - // 現在シーンが untitled (scene-2d) の場合、save-scene はダイアログを出すので - // safeSaveScene でスキップする。untitled でシーンにインスタンスが居るケースは - // 本来 prefab_open モードでのみ発生するので save が効く想定だが、 - // テスト等で直接呼ばれた場合の保護として skip する。 - const saved = await safeSaveScene(); - if (!saved) { - console.warn( - "[PrefabTools] _fixNestedPrefabJson: save-scene skipped (untitled scene). " + - "Nested prefab JSON post-processing may be incomplete." - ); - return; - } - await new Promise(r => setTimeout(r, 1500)); - - // prefab_open で記憶した UUID からファイルパスを取得 - if (!this._currentPrefabUuid) return; - - const prefabPath = await (Editor.Message.request as any)( - "asset-db", "query-path", this._currentPrefabUuid - ); - if (!prefabPath) return; - if (!fs.existsSync(prefabPath)) return; - - const data = JSON.parse(fs.readFileSync(prefabPath, "utf-8")); - - // 各ネスト Prefab エントリを処理 - for (const entry of this._pendingNestedPrefabs) { - // fileId でノードを検索(nodeUuid はシーン内 UUID、Prefab JSON 内では fileId) - let flpNodeIdx = -1; - for (let i = 0; i < data.length; i++) { - if (data[i].__type__ === "cc.PrefabInfo" && data[i].fileId === entry.nodeUuid) { - // この PrefabInfo を持つノードを探す - for (let j = 0; j < data.length; j++) { - if (data[j]._prefab?.__id__ === i) { - flpNodeIdx = j; - break; - } - } - break; - } - } - - // fileId で見つからない場合、ノード名で検索 - if (flpNodeIdx < 0) { - // Prefab アセット名を取得 - const assetInfo = await (Editor.Message.request as any)("asset-db", "query-asset-info", entry.prefabAssetUuid); - const assetName = assetInfo?.name?.replace(".prefab", "") || ""; - for (let i = 0; i < data.length; i++) { - if (data[i].__type__ === "cc.Node" && (data[i]._name === assetName || data[i]._name === undefined)) { - const prefabIdx = data[i]._prefab?.__id__; - if (prefabIdx != null && data[prefabIdx]?.asset?.__id__ === 0 && !data[prefabIdx]?.instance) { - flpNodeIdx = i; - break; - } - } - } - } - - if (flpNodeIdx < 0) continue; - - const prefabInfoIdx = data[flpNodeIdx]._prefab?.__id__; - if (prefabInfoIdx == null) continue; - - // PrefabInfo を修正 - const prefabInfo = data[prefabInfoIdx]; - prefabInfo.root = { __id__: flpNodeIdx }; - prefabInfo.asset = { - __uuid__: entry.prefabAssetUuid, - __expectedType__: "cc.Prefab", - }; - - // PrefabInstance を追加 - if (!prefabInfo.instance) { - const instanceIdx = data.length; - data.push({ - __type__: "cc.PrefabInstance", - fileId: crypto.randomBytes(16).toString("base64").replace(/[+/=]/g, "").substring(0, 22), - prefabRootNode: { __id__: 1 }, // Prefab 編集モードのルート - mountedChildren: [], - mountedComponents: [], - propertyOverrides: [], - removedComponents: [], - }); - prefabInfo.instance = { __id__: instanceIdx }; - } - - // 子ノード・コンポーネントをクリア(Prefab アセットから復元される) - data[flpNodeIdx]._children = []; - data[flpNodeIdx]._components = []; - - // ルートの nestedPrefabInstanceRoots に追加 - const rootPrefabIdx = data[1]._prefab?.__id__; - if (rootPrefabIdx != null) { - const rootPrefab = data[rootPrefabIdx]; - if (!rootPrefab.nestedPrefabInstanceRoots) { - rootPrefab.nestedPrefabInstanceRoots = []; - } - const alreadyNested = rootPrefab.nestedPrefabInstanceRoots.some( - (r: any) => r?.__id__ === flpNodeIdx - ); - if (!alreadyNested) { - rootPrefab.nestedPrefabInstanceRoots.push({ __id__: flpNodeIdx }); - } - } - } - - fs.writeFileSync(prefabPath, JSON.stringify(data, null, 2), "utf-8"); - this._pendingNestedPrefabs = []; - } catch (e: any) { - console.warn("[PrefabTools] _fixNestedPrefabJson failed:", e.message); - } - } - - private async revertPrefab(nodeUuid: string): Promise { - try { - const result = await (Editor.Message.request as any)("scene", "revert-prefab", nodeUuid); - return ok({ success: true, nodeUuid, result }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async getPrefabInfo(uuid: string): Promise { - try { - const info = await (Editor.Message.request as any)("asset-db", "query-asset-info", uuid); - return ok({ success: true, info }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async createAndReplace(nodeUuid: string, path: string): Promise { - try { - // 1. Check if prefab already exists - const existing = await this.assetExists(path); - if (existing) { - return err( - `Prefab already exists at "${path}". Delete it first or use a different path.` - ); - } - - // 2. Get node info (parent, sibling index, transform) before creating prefab - const nodeInfo = await this.sceneScript("getNodeInfo", [nodeUuid]); - if (!nodeInfo?.success) { - return err(`Node ${nodeUuid} not found`); - } - const parentUuid = nodeInfo.data?.parent; - - // 3. Create prefab from the node - const prefabAssetUuid = await (Editor.Message.request as any)("scene", "create-prefab", nodeUuid, path); - if (!prefabAssetUuid) { - return err("create-prefab returned no asset UUID"); - } - - // 4. Delete the original node - await (Editor.Message.request as any)("scene", "remove-node", { uuid: nodeUuid }); - - // 5. Instantiate the prefab at the same parent - const newNodeUuid = await Editor.Message.request("scene", "create-node", { - parent: parentUuid || undefined, - assetUuid: prefabAssetUuid, - }); - - return ok({ - success: true, - prefabAssetUuid, - prefabPath: path, - originalNodeUuid: nodeUuid, - newInstanceUuid: newNodeUuid, - }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async openPrefab(uuid?: string, path?: string, force: boolean = false): Promise { - try { - // Prefab を開くときも内部的にシーン切替が発生するので dirty untitled チェック - await ensureSceneSafeToSwitch(force); - - // Resolve UUID from path if needed - let assetUuid = uuid; - if (!assetUuid && path) { - const info = await (Editor.Message.request as any)("asset-db", "query-asset-info", path); - assetUuid = info?.uuid; - } - if (!assetUuid) { - return err("Either uuid or path is required"); - } - - // Open prefab in editing mode (equivalent to double-click) - await (Editor.Message.request as any)("asset-db", "open-asset", assetUuid); - // Wait for prefab editing mode to initialize - await new Promise(r => setTimeout(r, 1000)); - - this._currentPrefabUuid = assetUuid; - this._pendingNestedPrefabs = []; - - return ok({ success: true, uuid: assetUuid, mode: "prefab-edit" }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async closePrefab(save: boolean, sceneUuid?: string, force: boolean = false): Promise { - try { - // 1. Save prefab if requested - // prefab edit モード中は "current scene" = 編集中 Prefab なので save-scene は Prefab を保存する。 - // ただし untitled フォールバックに当たった場合はダイアログ回避のためスキップする。 - if (save) { - await safeSaveScene(); - await new Promise(r => setTimeout(r, 500)); - } - - // 2. Determine which scene to return to - let targetScene = sceneUuid; - if (!targetScene) { - // Try project's start scene - try { - targetScene = await (Editor as any).Profile.getConfig("preview", "general.start_scene", "local"); - } catch { /* ignore */ } - - // Fallback to first scene - if (!targetScene || targetScene === "current_scene") { - const scenes = await Editor.Message.request("asset-db", "query-assets", { - ccType: "cc.SceneAsset", - pattern: "db://assets/**/*", - }); - if (Array.isArray(scenes) && scenes.length > 0) { - targetScene = scenes[0].uuid; - } - } - } - - // 3. Open the scene - if (targetScene) { - // prefab edit モードから戻る遷移もダイアログが出うる - await ensureSceneSafeToSwitch(force); - await (Editor.Message.request as any)("scene", "open-scene", targetScene); - await new Promise(r => setTimeout(r, 1000)); - } - - return ok({ success: true, returnedToScene: targetScene }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async createFromSpec(prefabPath: string, spec: any, autoBindMode: string): Promise { - try { - // 1. 既存 Prefab チェック - const existing = await this.assetExists(prefabPath); - if (existing) { - return err( - `Prefab already exists at "${prefabPath}". Delete it first or use a different path.` - ); - } - - // 2. シーンの最初の cc.Node UUID を取得(Scene UUID ではなく Canvas 等) - const hier = await this.sceneScript("getSceneHierarchy", [false]); - const hierarchy = hier?.hierarchy || []; - const firstNode = hierarchy[0]; - if (!firstNode?.uuid) return err("Could not find a node in the current scene to use as parent"); - const parentUuid = firstNode.uuid; - - // 3. ノードツリーを構築 - const autoBind = spec.autoBind; - const cleanSpec = { ...spec }; - delete cleanSpec.autoBind; - - const treeResult = await this.sceneScript("buildNodeTree", [parentUuid, cleanSpec]); - if (!treeResult?.success) return err(treeResult?.error || "buildNodeTree failed"); - const nodeUuid = treeResult.data?.uuid; - if (!nodeUuid) return err("buildNodeTree returned no root node UUID"); - - // 4. フォント・SpriteFrame を Editor API 経由で設定(アセット依存追跡のため) - await this._applyDefaultAssets(nodeUuid); - - // 5. autoBind 実行 (旧4) - let autoBindResult: any = null; - if (autoBind) { - if (!this._componentTools) { - return err("autoBind requires ComponentTools dependency (internal configuration error)"); - } - const bindToolResult = await this._componentTools.execute("component_auto_bind", { - uuid: nodeUuid, - componentType: autoBind, - force: false, - mode: autoBindMode, - }); - try { - autoBindResult = JSON.parse(bindToolResult.content[0].text); - } catch { autoBindResult = bindToolResult; } - } - - // 6. Prefab 作成 - const prefabAssetUuid = await (Editor.Message.request as any)( - "scene", "create-prefab", nodeUuid, prefabPath - ); - if (!prefabAssetUuid) { - await (Editor.Message.request as any)("scene", "remove-node", { uuid: nodeUuid }); - return err("create-prefab returned no asset UUID"); - } - - // 7. 一時ノードを削除 - await (Editor.Message.request as any)("scene", "remove-node", { uuid: nodeUuid }); - - return ok({ - success: true, - prefabAssetUuid, - path: prefabPath, - nodeTree: treeResult.data, - autoBind: autoBindResult, - }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - /** - * buildNodeTree で作成したノードツリーの Label にプロジェクトフォントを設定する。 - * Editor API (scene:set-property) 経由で設定することでアセット依存が正しく追跡される。 - */ - private async _applyDefaultAssets(rootUuid: string): Promise { - // プロジェクトのデフォルトフォントを検索(resources/fonts/ 配下の TTFFont) - let fontUuid: string | null = null; - try { - const assets = await Editor.Message.request("asset-db", "query-assets", { - pattern: "db://assets/resources/fonts/**", - ccType: "cc.TTFFont", - }); - if (Array.isArray(assets) && assets.length > 0) { - fontUuid = assets[0].uuid; - } - } catch { /* ignore */ } - - if (!fontUuid) return; - - // 全子孫ノードを取得 - const descendants = await this.sceneScript("getAllDescendants", [rootUuid]); - if (!descendants?.success) return; - const allNodes = [{ uuid: rootUuid, name: "root" }, ...descendants.data]; - - for (const node of allNodes) { - try { - const nodeDump = await (Editor.Message.request as any)("scene", "query-node", node.uuid); - if (!nodeDump) continue; - const comps = nodeDump.__comps__ || []; - for (let i = 0; i < comps.length; i++) { - const compType = comps[i].type || ""; - // Label にフォント設定 - if (compType === "cc.Label") { - const fontDump = comps[i].value?.font; - if (!fontDump?.value?.uuid) { - await (Editor.Message.request as any)("scene", "set-property", { - uuid: node.uuid, - path: `__comps__.${i}.font`, - dump: { type: "cc.TTFFont", value: { uuid: fontUuid } }, - }); - } - } - } - } catch { /* skip nodes that can't be queried */ } - } - } - - private async sceneScript(method: string, args: any[]): Promise { - return Editor.Message.request("scene", "execute-scene-script", { - name: "cocos-creator-mcp", - method, - args, - }); - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/preferences-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/preferences-tools.ts deleted file mode 100644 index 72d6c7f..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/preferences-tools.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; - -export class PreferencesTools implements ToolCategory { - readonly categoryName = "preferences"; - - getTools(): ToolDefinition[] { - return [ - { - name: "preferences_get", - description: "Get a preference value by key.", - inputSchema: { - type: "object", - properties: { - protocol: { type: "string", description: "Protocol name (e.g. 'general', 'builder', 'engine')" }, - key: { type: "string", description: "Preference key" }, - }, - required: ["protocol", "key"], - }, - }, - { - name: "preferences_set", - description: "Set a preference value.", - inputSchema: { - type: "object", - properties: { - protocol: { type: "string", description: "Protocol name" }, - key: { type: "string", description: "Preference key" }, - value: { description: "Value to set" }, - }, - required: ["protocol", "key", "value"], - }, - }, - { - name: "preferences_get_all", - description: "Get all preferences for a given protocol.", - inputSchema: { - type: "object", - properties: { - protocol: { type: "string", description: "Protocol name (e.g. 'general')" }, - }, - required: ["protocol"], - }, - }, - { - name: "preferences_reset", - description: "Reset a preference to its default value.", - inputSchema: { - type: "object", - properties: { - protocol: { type: "string", description: "Protocol name" }, - key: { type: "string", description: "Preference key to reset" }, - }, - required: ["protocol", "key"], - }, - }, - ]; - } - - async execute(toolName: string, args: Record): Promise { - try { - switch (toolName) { - case "preferences_get": { - const value = Editor.Profile.getConfig(args.protocol, args.key); - return ok({ success: true, protocol: args.protocol, key: args.key, value }); - } - case "preferences_set": - Editor.Profile.setConfig(args.protocol, args.key, args.value); - return ok({ success: true, protocol: args.protocol, key: args.key }); - case "preferences_get_all": { - const config = Editor.Profile.getConfig(args.protocol); - return ok({ success: true, protocol: args.protocol, config }); - } - case "preferences_reset": - Editor.Profile.removeConfig(args.protocol, args.key); - return ok({ success: true, protocol: args.protocol, key: args.key }); - default: - return err(`Unknown tool: ${toolName}`); - } - } catch (e: any) { - return err(e.message || String(e)); - } - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/project-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/project-tools.ts deleted file mode 100644 index 779cf1f..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/project-tools.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; - -export class ProjectTools implements ToolCategory { - readonly categoryName = "project"; - - getTools(): ToolDefinition[] { - return [ - { - name: "project_get_info", - description: "Get project information (name, path, engine version).", - inputSchema: { - type: "object", - properties: {}, - }, - }, - { - name: "project_refresh_assets", - description: "Refresh the asset database to detect file changes.", - inputSchema: { - type: "object", - properties: {}, - }, - }, - { - name: "project_get_asset_info", - description: "Get information about an asset by UUID.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Asset UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "project_find_asset", - description: "Find assets by name pattern (glob). Returns matching asset paths and UUIDs.", - inputSchema: { - type: "object", - properties: { - pattern: { type: "string", description: "Glob pattern (e.g. 'db://assets/**/*.ts', 'db://assets/**/Button*')" }, - }, - required: ["pattern"], - }, - }, - { - name: "project_get_settings", - description: "Get project settings for a given protocol.", - inputSchema: { - type: "object", - properties: { - protocol: { type: "string", description: "Settings protocol (e.g. 'general', 'engine')" }, - }, - }, - }, - { - name: "project_set_settings", - description: "Set a project setting.", - inputSchema: { - type: "object", - properties: { - protocol: { type: "string" }, - key: { type: "string" }, - value: {}, - }, - required: ["protocol", "key", "value"], - }, - }, - { - name: "project_get_engine_info", - description: "Get engine version and path information.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "project_query_scripts", - description: "Query all script plugins in the project.", - inputSchema: { type: "object", properties: {} }, - }, - ]; - } - - async execute(toolName: string, args: Record): Promise { - switch (toolName) { - case "project_get_info": - return this.getInfo(); - case "project_refresh_assets": - return this.refreshAssets(); - case "project_get_asset_info": - return this.getAssetInfo(args.uuid); - case "project_find_asset": - return this.findAsset(args.pattern); - case "project_get_settings": { - try { - const config = await (Editor.Message.request as any)("project", "query-config", args.protocol || "general"); - return ok({ success: true, config }); - } catch (e: any) { return err(e.message || String(e)); } - } - case "project_set_settings": { - try { - await (Editor.Message.request as any)("project", "set-config", args.protocol, args.key, args.value); - return ok({ success: true }); - } catch (e: any) { return err(e.message || String(e)); } - } - case "project_get_engine_info": { - try { - const info = await (Editor.Message.request as any)("engine", "query-info"); - return ok({ success: true, info }); - } catch (e: any) { return err(e.message || String(e)); } - } - case "project_query_scripts": { - try { - const scripts = await (Editor.Message.request as any)("programming", "query-sorted-plugins"); - return ok({ success: true, scripts }); - } catch (e: any) { return err(e.message || String(e)); } - } - default: - return err(`Unknown tool: ${toolName}`); - } - } - - private async getInfo(): Promise { - try { - return ok({ - success: true, - name: Editor.Project.name, - path: Editor.Project.path, - tmpDir: Editor.Project.tmpDir, - }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async refreshAssets(): Promise { - try { - await Editor.Message.request("asset-db", "refresh-asset", "db://assets"); - return ok({ success: true }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async getAssetInfo(uuid: string): Promise { - try { - const info = await (Editor.Message.request as any)("asset-db", "query-asset-info", uuid); - return ok({ success: true, info }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async findAsset(pattern: string): Promise { - try { - const results = await Editor.Message.request("asset-db", "query-assets", { pattern }); - const assets = (results || []).map((a: any) => ({ - uuid: a.uuid, - path: a.path || a.url, - name: a.name, - type: a.type, - })); - return ok({ success: true, assets }); - } catch (e: any) { - return err(e.message || String(e)); - } - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/reference-image-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/reference-image-tools.ts deleted file mode 100644 index f46c1bf..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/reference-image-tools.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; - -export class ReferenceImageTools implements ToolCategory { - readonly categoryName = "referenceImage"; - - getTools(): ToolDefinition[] { - return [ - { - name: "refimage_add", - description: "Add a reference image to the scene view.", - inputSchema: { - type: "object", - properties: { - path: { type: "string", description: "Image file path or db:// path" }, - }, - required: ["path"], - }, - }, - { - name: "refimage_remove", - description: "Remove a reference image by index.", - inputSchema: { - type: "object", - properties: { - index: { type: "number", description: "Image index" }, - }, - required: ["index"], - }, - }, - { - name: "refimage_list", - description: "List all reference images in the scene view.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "refimage_clear_all", - description: "Remove all reference images.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "refimage_switch", - description: "Switch to a specific reference image by index.", - inputSchema: { - type: "object", - properties: { - index: { type: "number", description: "Image index to switch to" }, - }, - required: ["index"], - }, - }, - { - name: "refimage_set_position", - description: "Set the position of the current reference image.", - inputSchema: { - type: "object", - properties: { - x: { type: "number" }, - y: { type: "number" }, - }, - required: ["x", "y"], - }, - }, - { - name: "refimage_set_scale", - description: "Set the scale of the current reference image.", - inputSchema: { - type: "object", - properties: { - scale: { type: "number", description: "Scale factor" }, - }, - required: ["scale"], - }, - }, - { - name: "refimage_set_opacity", - description: "Set the opacity of the current reference image.", - inputSchema: { - type: "object", - properties: { - opacity: { type: "number", description: "Opacity (0-255)" }, - }, - required: ["opacity"], - }, - }, - { - name: "refimage_query_config", - description: "Get the current reference image configuration.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "refimage_query_current", - description: "Get info about the currently active reference image.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "refimage_refresh", - description: "Refresh the reference image display.", - inputSchema: { type: "object", properties: {} }, - }, - ]; - } - - async execute(toolName: string, args: Record): Promise { - try { - switch (toolName) { - case "refimage_add": - await (Editor.Message.request as any)("scene", "add-reference-image", args.path); - return ok({ success: true, path: args.path }); - case "refimage_remove": - await (Editor.Message.request as any)("scene", "remove-reference-image", args.index); - return ok({ success: true, index: args.index }); - case "refimage_list": { - const config = await (Editor.Message.request as any)("scene", "query-reference-image-config").catch(() => null); - return ok({ success: true, config }); - } - case "refimage_clear_all": - await (Editor.Message.request as any)("scene", "clear-all-reference-images"); - return ok({ success: true }); - case "refimage_switch": - await (Editor.Message.request as any)("scene", "switch-reference-image", args.index); - return ok({ success: true, index: args.index }); - case "refimage_set_position": - await (Editor.Message.request as any)("scene", "set-reference-image-position", args.x, args.y); - return ok({ success: true, x: args.x, y: args.y }); - case "refimage_set_scale": - await (Editor.Message.request as any)("scene", "set-reference-image-scale", args.scale); - return ok({ success: true, scale: args.scale }); - case "refimage_set_opacity": - await (Editor.Message.request as any)("scene", "set-reference-image-opacity", args.opacity); - return ok({ success: true, opacity: args.opacity }); - case "refimage_query_config": { - const config = await (Editor.Message.request as any)("scene", "query-reference-image-config").catch(() => null); - return ok({ success: true, config }); - } - case "refimage_query_current": { - const current = await (Editor.Message.request as any)("scene", "query-current-reference-image").catch(() => null); - return ok({ success: true, current }); - } - case "refimage_refresh": - await (Editor.Message.request as any)("scene", "refresh-reference-image"); - return ok({ success: true }); - default: - return err(`Unknown tool: ${toolName}`); - } - } catch (e: any) { - return err(e.message || String(e)); - } - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/scene-advanced-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/scene-advanced-tools.ts deleted file mode 100644 index 1f88d6e..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/scene-advanced-tools.ts +++ /dev/null @@ -1,635 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; -import { ensureSceneSafeToSwitch } from "./scene-tools"; - -const EXT_NAME = "cocos-creator-mcp"; - -export class SceneAdvancedTools implements ToolCategory { - readonly categoryName = "sceneAdvanced"; - - getTools(): ToolDefinition[] { - return [ - { - name: "scene_execute_script", - description: "Execute a scene script method by name with arguments.", - inputSchema: { - type: "object", - properties: { - method: { type: "string", description: "Scene script method name" }, - args: { type: "array", description: "Arguments to pass", items: {} }, - }, - required: ["method"], - }, - }, - { - name: "scene_snapshot", - description: "Take a snapshot of the current scene state (for undo).", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "scene_query_dirty", - description: "Check if the current scene has unsaved changes.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "scene_query_classes", - description: "Query all available component classes in the scene.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "scene_query_components", - description: "Query available components for a given node.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "scene_query_node_tree", - description: "Query the raw node tree from the editor (alternative to scene_get_hierarchy).", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "scene_query_nodes_by_asset", - description: "Find all nodes that reference a given asset UUID.", - inputSchema: { - type: "object", - properties: { - assetUuid: { type: "string", description: "Asset UUID to search for" }, - }, - required: ["assetUuid"], - }, - }, - { - name: "scene_soft_reload", - description: "Soft reload the current scene without losing state.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "scene_reset_node_transform", - description: "Reset a node's transform to default (position 0,0,0 / rotation 0,0,0 / scale 1,1,1).", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "scene_copy_node", - description: "Copy a node to clipboard.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node UUID" }, - }, - required: ["uuid"], - }, - }, - { - name: "scene_paste_node", - description: "Paste node from clipboard under a parent.", - inputSchema: { - type: "object", - properties: { - parentUuid: { type: "string", description: "Parent node UUID" }, - }, - required: ["parentUuid"], - }, - }, - { - name: "scene_create", - description: "Create a new empty 2D scene. If path is omitted, uses the editor's built-in new-scene command (may not work on CC 3.8.x). If path is specified, creates a .scene file via asset-db as a fallback. Returns an error if the current scene is dirty and untitled (to avoid modal save dialog); pass force=true to bypass.", - inputSchema: { - type: "object", - properties: { - path: { type: "string", description: "Scene asset path (e.g. 'db://assets/scenes/NewScene.scene'). If omitted, uses editor's new-scene command." }, - force: { type: "boolean", description: "Skip dirty-scene preflight check (may trigger modal save dialog)" }, - }, - }, - }, - { - name: "scene_cut_node", - description: "Cut a node to clipboard (removes from scene).", - inputSchema: { - type: "object", - properties: { uuid: { type: "string", description: "Node UUID" } }, - required: ["uuid"], - }, - }, - { - name: "scene_reset_property", - description: "Reset a specific property on a node or component to its default value.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node or component UUID" }, - path: { type: "string", description: "Property path (e.g. 'position', 'color')" }, - }, - required: ["uuid", "path"], - }, - }, - { - name: "scene_reset_component", - description: "Reset a component to its default state.", - inputSchema: { - type: "object", - properties: { uuid: { type: "string", description: "Component UUID" } }, - required: ["uuid"], - }, - }, - { - name: "scene_execute_component_method", - description: "Call a method on a component at edit-time.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Component UUID" }, - method: { type: "string", description: "Method name" }, - args: { type: "array", description: "Method arguments", items: {} }, - }, - required: ["uuid", "method"], - }, - }, - { - name: "scene_move_array_element", - description: "Move an array element to a new position in a property.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node or component UUID" }, - path: { type: "string", description: "Array property path" }, - target: { type: "number", description: "Current index" }, - offset: { type: "number", description: "Move offset (+1 = down, -1 = up)" }, - }, - required: ["uuid", "path", "target", "offset"], - }, - }, - { - name: "scene_remove_array_element", - description: "Remove an element from an array property by index.", - inputSchema: { - type: "object", - properties: { - uuid: { type: "string", description: "Node or component UUID" }, - path: { type: "string", description: "Array property path" }, - index: { type: "number", description: "Index to remove" }, - }, - required: ["uuid", "path", "index"], - }, - }, - { - name: "scene_snapshot_abort", - description: "Abort the current undo snapshot.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "scene_query_ready", - description: "Check if the scene is fully loaded and ready.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "scene_query_component_has_script", - description: "Check if a component has an associated script file.", - inputSchema: { - type: "object", - properties: { name: { type: "string", description: "Component class name" } }, - required: ["name"], - }, - }, - { - name: "scene_restore_prefab", - description: "Restore a prefab node to its original prefab state.", - inputSchema: { - type: "object", - properties: { uuid: { type: "string", description: "Node UUID" } }, - required: ["uuid"], - }, - }, - { - name: "scene_begin_undo", - description: "Begin recording undo operations.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "scene_end_undo", - description: "End undo recording and save the undo step.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "scene_cancel_undo", - description: "Cancel the current undo recording.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "scene_save_as", - description: "Save the current scene to a new file (shows save dialog).", - inputSchema: { type: "object", properties: {} }, - }, - // ── 以下、既存MCP未対応のEditor API ── - { - name: "scene_set_parent", - description: "Reparent node(s) using the official Editor API (alternative to node_move).", - inputSchema: { - type: "object", - properties: { - uuids: { type: "array", items: { type: "string" }, description: "Node UUID(s) to move" }, - parent: { type: "string", description: "New parent node UUID" }, - keepWorldTransform: { type: "boolean", description: "Keep world position (default false)" }, - }, - required: ["uuids", "parent"], - }, - }, - { - name: "scene_query_node", - description: "Get a full property dump of a node (all serialized data).", - inputSchema: { - type: "object", - properties: { uuid: { type: "string", description: "Node UUID" } }, - required: ["uuid"], - }, - }, - { - name: "scene_query_component", - description: "Get a full property dump of a component by its UUID.", - inputSchema: { - type: "object", - properties: { uuid: { type: "string", description: "Component UUID" } }, - required: ["uuid"], - }, - }, - { - name: "scene_query_scene_bounds", - description: "Get the bounding rect of the current scene.", - inputSchema: { type: "object", properties: {} }, - }, - ]; - } - - async execute(toolName: string, args: Record): Promise { - try { - switch (toolName) { - case "scene_execute_script": - return ok(await this.sceneScript(args.method, args.args || [])); - case "scene_snapshot": - return ok(await (Editor.Message.request as any)("scene", "snapshot")); - case "scene_query_dirty": { - const dirty = await (Editor.Message.request as any)("scene", "query-dirty"); - return ok({ success: true, dirty }); - } - case "scene_query_classes": { - const classes = await (Editor.Message.request as any)("scene", "query-classes"); - return ok({ success: true, classes }); - } - case "scene_query_components": { - const comps = await (Editor.Message.request as any)("scene", "query-components", args.uuid); - return ok({ success: true, components: comps }); - } - case "scene_query_node_tree": { - const tree = await Editor.Message.request("scene", "query-node-tree"); - return ok({ success: true, tree }); - } - case "scene_query_nodes_by_asset": { - const nodes = await (Editor.Message.request as any)("scene", "query-nodes-by-asset-uuid", args.assetUuid); - return ok({ success: true, nodes }); - } - case "scene_soft_reload": - await (Editor.Message.request as any)("scene", "soft-reload"); - return ok({ success: true }); - case "scene_reset_node_transform": - return await this.resetTransform(args.uuid); - case "scene_copy_node": - await (Editor.Message.request as any)("scene", "copy-node", args.uuid); - return ok({ success: true, uuid: args.uuid }); - case "scene_paste_node": { - const result = await (Editor.Message.request as any)("scene", "paste-node", args.parentUuid); - return ok({ success: true, result }); - } - case "scene_create": - return this.createScene(args.path, !!args.force); - case "scene_cut_node": - await (Editor.Message.request as any)("scene", "cut-node", args.uuid); - return ok({ success: true, uuid: args.uuid }); - case "scene_reset_property": - await (Editor.Message.request as any)("scene", "reset-property", { uuid: args.uuid, path: args.path }); - return ok({ success: true }); - case "scene_reset_component": - await (Editor.Message.request as any)("scene", "reset-component", { uuid: args.uuid }); - return ok({ success: true }); - case "scene_execute_component_method": { - const result = await (Editor.Message.request as any)("scene", "execute-component-method", { uuid: args.uuid, name: args.method, args: args.args || [] }); - return ok({ success: true, result }); - } - case "scene_move_array_element": - await (Editor.Message.request as any)("scene", "move-array-element", { uuid: args.uuid, path: args.path, target: args.target, offset: args.offset }); - return ok({ success: true }); - case "scene_remove_array_element": - await (Editor.Message.request as any)("scene", "remove-array-element", { uuid: args.uuid, path: args.path, index: args.index }); - return ok({ success: true }); - case "scene_snapshot_abort": - await (Editor.Message.request as any)("scene", "snapshot-abort"); - return ok({ success: true }); - case "scene_query_ready": { - const ready = await (Editor.Message.request as any)("scene", "query-is-ready"); - return ok({ success: true, ready }); - } - case "scene_query_component_has_script": { - const hasScript = await (Editor.Message.request as any)("scene", "query-component-has-script", args.name); - return ok({ success: true, name: args.name, hasScript }); - } - case "scene_restore_prefab": - await (Editor.Message.request as any)("scene", "restore-prefab", { uuid: args.uuid }); - return ok({ success: true, uuid: args.uuid }); - case "scene_begin_undo": - await (Editor.Message.request as any)("scene", "begin-recording"); - return ok({ success: true }); - case "scene_end_undo": - await (Editor.Message.request as any)("scene", "end-recording"); - return ok({ success: true }); - case "scene_cancel_undo": - await (Editor.Message.request as any)("scene", "cancel-recording"); - return ok({ success: true }); - case "scene_save_as": { - const result = await (Editor.Message.request as any)("scene", "save-as-scene"); - return ok({ success: true, result }); - } - case "scene_set_parent": - await (Editor.Message.request as any)("scene", "set-parent", { - parent: args.parent, - uuids: args.uuids, - keepWorldTransform: args.keepWorldTransform || false, - }); - return ok({ success: true }); - case "scene_query_node": { - const dump = await (Editor.Message.request as any)("scene", "query-node", args.uuid); - return ok({ success: true, node: dump }); - } - case "scene_query_component": { - const dump = await (Editor.Message.request as any)("scene", "query-component", args.uuid); - return ok({ success: true, component: dump }); - } - case "scene_query_scene_bounds": { - const bounds = await (Editor.Message.request as any)("scene", "query-scene-bounds"); - return ok({ success: true, bounds }); - } - default: - return err(`Unknown tool: ${toolName}`); - } - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async resetTransform(uuid: string): Promise { - const result = await this.sceneScript("setNodeProperty", [uuid, "position", { x: 0, y: 0, z: 0 }]); - await this.sceneScript("setNodeProperty", [uuid, "rotation", { x: 0, y: 0, z: 0 }]); - await this.sceneScript("setNodeProperty", [uuid, "scale", { x: 1, y: 1, z: 1 }]); - return ok({ success: true, uuid }); - } - - private async createScene(path?: string, force: boolean = false): Promise { - // ダイアログ割り込み防止: 現在シーンが dirty かつ untitled の場合は事前エラー - try { await ensureSceneSafeToSwitch(force); } - catch (e: any) { return err(e.message || String(e)); } - - // まず scene:new-scene を試行(path 未指定時のみ) - if (!path) { - try { - await (Editor.Message.request as any)("scene", "new-scene"); - return ok({ success: true }); - } catch (e: any) { - const msg = e?.message || String(e); - if (msg.includes("Message does not exist") || msg.includes("scene - new-scene")) { - // CC 3.8.x → asset-db fallback にフォール - const fallbackPath = await this.generateAvailableScenePath(); - return this.createSceneViaAssetDb(fallbackPath); - } - return err(msg); - } - } - - // path 指定 → asset-db fallback - return this.createSceneViaAssetDb(path); - } - - private async generateAvailableScenePath(): Promise { - const basePath = "db://assets/NewScene.scene"; - try { - const result = await (Editor.Message.request as any)("asset-db", "generate-available-url", basePath); - if (result) return result; - } catch { /* fallback */ } - return `db://assets/NewScene_${Date.now()}.scene`; - } - - private async createSceneViaAssetDb(path: string): Promise { - try { - if (!path.endsWith(".scene")) path += ".scene"; - - const sceneName = path.split("/").pop()!.replace(".scene", ""); - const uid = () => crypto.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; - const sid = () => { - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - let s = ""; - for (let i = 0; i < 21; i++) s += chars[Math.floor(Math.random() * chars.length)]; - return s; - }; - - const sceneJson = this.buildMinimalSceneJson(sceneName, uid, sid); - const content = JSON.stringify(sceneJson, null, 2); - - await (Editor.Message.request as any)("asset-db", "create-asset", path, content); - - // シーンを開く - try { - // ensureSceneSafeToSwitch は createScene 入口で既に通過済みなのでここでは再チェックしない - const queryResult = await (Editor.Message.request as any)("asset-db", "query-uuid", path); - if (queryResult) { - await (Editor.Message.request as any)("scene", "open-scene", queryResult); - } - } catch { /* open failure is not critical */ } - - return ok({ success: true, path, method: "asset-db-fallback" }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private buildMinimalSceneJson(name: string, uid: () => string, sid: () => string): any[] { - const sceneId = uid(); - const canvasNodeId = sid(); - const cameraNodeId = sid(); - - const vec3 = (x: number, y: number, z: number) => ({ __type__: "cc.Vec3", x, y, z }); - const quat = () => ({ __type__: "cc.Quat", x: 0, y: 0, z: 0, w: 1 }); - - return [ - // [0] SceneAsset - { - __type__: "cc.SceneAsset", - _name: name, - _objFlags: 0, - __editorExtras__: {}, - _native: "", - scene: { __id__: 1 }, - }, - // [1] Scene - { - __type__: "cc.Scene", - _name: name, - _objFlags: 0, - __editorExtras__: {}, - _parent: null, - _children: [{ __id__: 2 }], - _active: true, - _components: [], - _prefab: null, - _lpos: vec3(0, 0, 0), - _lrot: quat(), - _lscale: vec3(1, 1, 1), - _mobility: 0, - _layer: 1073741824, - _euler: vec3(0, 0, 0), - autoReleaseAssets: false, - _globals: { __id__: 10 }, - _id: sceneId, - }, - // [2] Canvas node - { - __type__: "cc.Node", - _name: "Canvas", - _objFlags: 0, - __editorExtras__: {}, - _parent: { __id__: 1 }, - _children: [{ __id__: 3 }], - _active: true, - _components: [{ __id__: 5 }, { __id__: 6 }, { __id__: 7 }], - _prefab: null, - _lpos: vec3(0, 0, 0), - _lrot: quat(), - _lscale: vec3(1, 1, 1), - _mobility: 0, - _layer: 33554432, - _euler: vec3(0, 0, 0), - _id: canvasNodeId, - }, - // [3] Camera node - { - __type__: "cc.Node", - _name: "Camera", - _objFlags: 0, - __editorExtras__: {}, - _parent: { __id__: 2 }, - _children: [], - _active: true, - _components: [{ __id__: 4 }], - _prefab: null, - _lpos: vec3(0, 0, 1000), - _lrot: quat(), - _lscale: vec3(1, 1, 1), - _mobility: 0, - _layer: 1073741824, - _euler: vec3(0, 0, 0), - _id: cameraNodeId, - }, - // [4] Camera component - { - __type__: "cc.Camera", - _name: "", - _objFlags: 0, - __editorExtras__: {}, - node: { __id__: 3 }, - _enabled: true, - _projection: 1, - _priority: 0, - _fov: 45, - _fovAxis: 0, - _orthoHeight: 10, - _near: 1, - _far: 2000, - _color: { __type__: "cc.Color", r: 0, g: 0, b: 0, a: 255 }, - _depth: 1, - _stencil: 0, - _clearFlags: 6, - _rect: { __type__: "cc.Rect", x: 0, y: 0, width: 1, height: 1 }, - _visibility: 1108344832, - _id: "", - }, - // [5] UITransform on Canvas - { - __type__: "cc.UITransform", - _name: "", - _objFlags: 0, - __editorExtras__: {}, - node: { __id__: 2 }, - _enabled: true, - _contentSize: { __type__: "cc.Size", width: 720, height: 1280 }, - _anchorPoint: { __type__: "cc.Vec2", x: 0.5, y: 0.5 }, - _id: "", - }, - // [6] Canvas component - { - __type__: "cc.Canvas", - _name: "", - _objFlags: 0, - __editorExtras__: {}, - node: { __id__: 2 }, - _enabled: true, - _cameraComponent: { __id__: 4 }, - _alignCanvasWithScreen: true, - _id: "", - }, - // [7] Widget on Canvas (fullscreen) - { - __type__: "cc.Widget", - _name: "", - _objFlags: 0, - __editorExtras__: {}, - node: { __id__: 2 }, - _enabled: true, - _alignFlags: 15, - _target: null, - _left: 0, - _right: 0, - _top: 0, - _bottom: 0, - _isAbsLeft: true, - _isAbsRight: true, - _isAbsTop: true, - _isAbsBottom: true, - _originalWidth: 0, - _originalHeight: 0, - _id: "", - }, - // [8] cc.PrefabInfo for scene - // [9] (reserved) - // [10] SceneGlobals - { - __type__: "cc.SceneGlobals", - ambient: { __id__: 11 }, - shadows: { __id__: 12 }, - _skybox: { __id__: 13 }, - fog: { __id__: 14 }, - }, - // [11] AmbientInfo - { __type__: "cc.AmbientInfo", _skyLightingColor: { __type__: "cc.Vec4", x: 0.2, y: 0.2, z: 0.2, w: 1 } }, - // [12] ShadowsInfo - { __type__: "cc.ShadowsInfo" }, - // [13] SkyboxInfo - { __type__: "cc.SkyboxInfo" }, - // [14] FogInfo - { __type__: "cc.FogInfo" }, - ]; - } - - private async sceneScript(method: string, args: any[]): Promise { - return Editor.Message.request("scene", "execute-scene-script", { - name: EXT_NAME, - method, - args, - }); - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/scene-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/scene-tools.ts deleted file mode 100644 index d41fd6d..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/scene-tools.ts +++ /dev/null @@ -1,361 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; - -const EXT_NAME = "cocos-creator-mcp"; - -/** シーン名から「untitled (保存先なし)」かどうかを判定 */ -const UNTITLED_SCENE_NAMES = new Set(["scene-2d", "scene-3d", "Untitled", "NewScene", ""]); - -/** - * 現在のシーンの dirty 状態を取得。 - * CC バージョンによって query-dirty / query-is-dirty のどちらか(または両方)が存在するので両方試行。 - */ -export async function queryCurrentSceneDirty(): Promise { - try { - const r = await (Editor.Message.request as any)("scene", "query-dirty"); - return !!r; - } catch { /* try alternate */ } - try { - const r = await (Editor.Message.request as any)("scene", "query-is-dirty"); - return !!r; - } catch { /* assume clean if both fail */ } - return false; -} - -/** - * 現在のシーン情報(名前・UUID)を取得。 - */ -export async function queryCurrentSceneInfo(): Promise<{ sceneName: string; sceneUuid: string }> { - try { - const result: any = await Editor.Message.request( - "scene", - "execute-scene-script", - { name: EXT_NAME, method: "getSceneHierarchy", args: [false] } - ); - return { sceneName: result?.sceneName || "", sceneUuid: result?.sceneUuid || "" }; - } catch { - return { sceneName: "", sceneUuid: "" }; - } -} - -/** - * 現在のシーンが untitled (scene-2d 等) かを判定。 - */ -export async function isCurrentSceneUntitled(): Promise { - const { sceneName } = await queryCurrentSceneInfo(); - return UNTITLED_SCENE_NAMES.has(sceneName); -} - -/** - * save-scene をダイアログ安全に呼び出す。 - * - * - 現在のシーンが untitled (scene-2d 等) → no-op(ダイアログ防止) - * - それ以外 → save-scene を実行 - * - * 任意の MCP ツール内部で save-scene を呼ぶ場合は必ずこれ経由にする。 - * 直接 `Editor.Message.request("scene", "save-scene")` を呼ぶと、 - * 現在シーンが untitled のときにモーダルダイアログが出て MCP が固まる。 - * - * @returns 実際に保存を実行したか - */ -export async function safeSaveScene(): Promise { - if (await isCurrentSceneUntitled()) { - console.warn( - "[cocos-creator-mcp] safeSaveScene: current scene is untitled, " + - "skipping save-scene to avoid modal dialog." - ); - return false; - } - await (Editor.Message.request as any)("scene", "save-scene"); - return true; -} - -/** - * シーン切替系ツール(scene_open/close/new など)の前処理。 - * - * - clean → OK - * - dirty + 保存先ありのシーン → save-scene で自動保存 - * - dirty + untitled シーン → ダイアログを自動応答("Don't Save")して変更を破棄 - * - * 目的: "Save changes?" ダイアログで MCP がブロックされるのを防ぐ。 - */ -export async function ensureSceneSafeToSwitch(force: boolean = false): Promise { - const isDirty = await queryCurrentSceneDirty(); - if (!isDirty) return; - - const { sceneName } = await queryCurrentSceneInfo(); - const isUntitled = UNTITLED_SCENE_NAMES.has(sceneName); - - if (isUntitled) { - // untitled + dirty: ダイアログが出るとMCPがブロックされるので、 - // Electron dialog をパッチして「保存しない」を自動応答する。 - // パッチは次回のシーン切替(呼び出し元の scene_open 等)でダイアログが出る瞬間に効く。 - patchDialogForDiscard(); - console.warn( - `[cocos-creator-mcp] ensureSceneSafeToSwitch: untitled scene "${sceneName}" ` + - `is dirty — dialog will auto-respond "Don't Save" to avoid blocking MCP.` - ); - return; - } - - try { - // ここに来る時点で untitled ではない(上で判定済み)ので直接 save-scene OK - await (Editor.Message.request as any)("scene", "save-scene"); - } catch (e: any) { - if (force) { - console.warn( - `[cocos-creator-mcp] ensureSceneSafeToSwitch: save-scene failed but force=true — proceeding. ` + - `Error: ${e.message || e}` - ); - return; - } - throw new Error( - `Failed to auto-save dirty scene "${sceneName}" before switch: ${e.message || e}. ` + - `Save manually and retry, or pass force=true to bypass.` - ); - } -} - -/** - * Electron の dialog.showMessageBox(Sync) を一時的にパッチして、 - * 次回のダイアログで「保存しない (Don't Save)」を自動選択する。 - * - * CocosCreator の "Save changes?" ダイアログは通常: - * buttons: ["Save", "Cancel", "Don't Save"] → index 2 = Don't Save - * または: - * buttons: ["Save", "Don't Save", "Cancel"] → index 1 = Don't Save - * - * ボタンテキストから "Don't Save" / "not" / "discard" を探し、 - * 見つからなければ最後のボタン(通常 Don't Save)を選択する。 - */ -function patchDialogForDiscard(): void { - try { - const electron = require("electron"); - const dialog = electron.dialog; - const origSync = dialog.showMessageBoxSync; - const origAsync = dialog.showMessageBox; - - function findDiscardIndex(buttons: string[]): number { - if (!buttons || buttons.length === 0) return 0; - const idx = buttons.findIndex((b: string) => { - const lower = b.toLowerCase(); - return lower.includes("don't save") || lower.includes("not") - || lower.includes("discard") || lower.includes("保存しない"); - }); - return idx >= 0 ? idx : buttons.length - 1; - } - - // Sync version - dialog.showMessageBoxSync = function (...args: any[]) { - dialog.showMessageBoxSync = origSync; // 1回で復元 - const options = args.length > 1 ? args[1] : args[0]; - const buttons = options?.buttons || []; - const result = findDiscardIndex(buttons); - console.warn(`[cocos-creator-mcp] dialog auto-responded: button[${result}]="${buttons[result] || "?"}" (buttons: ${JSON.stringify(buttons)})`); - return result; - }; - - // Async version - dialog.showMessageBox = function (...args: any[]) { - dialog.showMessageBox = origAsync; // 1回で復元 - const options = args.length > 1 ? args[1] : args[0]; - const buttons = options?.buttons || []; - const result = findDiscardIndex(buttons); - console.warn(`[cocos-creator-mcp] dialog auto-responded (async): button[${result}]="${buttons[result] || "?"}" (buttons: ${JSON.stringify(buttons)})`); - return Promise.resolve({ response: result, checkboxChecked: false }); - }; - - // 安全策: 5秒後に未使用なら復元(次のダイアログが出なかったケース) - setTimeout(() => { - if (dialog.showMessageBoxSync !== origSync) { - dialog.showMessageBoxSync = origSync; - } - if (dialog.showMessageBox !== origAsync) { - dialog.showMessageBox = origAsync; - } - }, 5000); - } catch (e: any) { - console.error(`[cocos-creator-mcp] patchDialogForDiscard failed: ${e.message || e}`); - } -} - -export class SceneTools implements ToolCategory { - readonly categoryName = "scene"; - - getTools(): ToolDefinition[] { - return [ - { - name: "scene_get_hierarchy", - description: "Get the node hierarchy of the current scene. Returns tree structure with uuid, name, active, and optionally components.", - inputSchema: { - type: "object", - properties: { - includeComponents: { - type: "boolean", - description: "Include component info for each node", - }, - }, - }, - }, - { - name: "scene_open", - description: "Open a scene by its asset UUID or database path (e.g. 'db://assets/scenes/Main.scene'). If the current scene is dirty and untitled, returns an error instead of triggering a modal save dialog that would block MCP. Pass force=true to bypass this guard.", - inputSchema: { - type: "object", - properties: { - scene: { - type: "string", - description: "Scene UUID or db:// path", - }, - force: { - type: "boolean", - description: "Skip dirty-scene preflight check (may trigger modal save dialog)", - }, - }, - required: ["scene"], - }, - }, - { - name: "scene_save", - description: "Save the currently open scene.", - inputSchema: { - type: "object", - properties: {}, - }, - }, - { - name: "scene_get_list", - description: "List all scene files in the project.", - inputSchema: { - type: "object", - properties: {}, - }, - }, - { - name: "scene_close", - description: "Close the current scene. If the current scene is dirty and untitled, returns an error instead of triggering a modal save dialog. Pass force=true to bypass.", - inputSchema: { - type: "object", - properties: { - force: { - type: "boolean", - description: "Skip dirty-scene preflight check (may trigger modal save dialog)", - }, - }, - }, - }, - { - name: "scene_get_current", - description: "Get the name and UUID of the currently open scene.", - inputSchema: { type: "object", properties: {} }, - }, - ]; - } - - async execute(toolName: string, args: Record): Promise { - switch (toolName) { - case "scene_get_hierarchy": - return this.getHierarchy(args.includeComponents ?? false); - case "scene_open": - return this.openScene(args.scene, !!args.force); - case "scene_save": - return this.saveScene(); - case "scene_get_list": - return this.getSceneList(); - case "scene_close": - try { - await ensureSceneSafeToSwitch(!!args.force); - await (Editor.Message.request as any)("scene", "close-scene"); - return ok({ success: true }); - } catch (e: any) { return err(e.message || String(e)); } - case "scene_get_current": - return this.getHierarchy(false).then((r) => { - const parsed = JSON.parse(r.content[0].text); - return ok({ success: true, sceneName: parsed.sceneName, sceneUuid: parsed.sceneUuid }); - }).catch((e) => err(String(e))); - default: - return err(`Unknown tool: ${toolName}`); - } - } - - private async getHierarchy(includeComponents: boolean): Promise { - try { - const result = await Editor.Message.request( - "scene", - "execute-scene-script", - { - name: EXT_NAME, - method: "getSceneHierarchy", - args: [includeComponents], - } - ); - return ok(result); - } catch (e: any) { - // Fallback: use query-node-tree - try { - const tree = await Editor.Message.request("scene", "query-node-tree"); - return ok({ success: true, hierarchy: tree }); - } catch (e2: any) { - return err(e2.message || String(e2)); - } - } - } - - private async openScene(scene: string, force: boolean): Promise { - try { - await ensureSceneSafeToSwitch(force); - await Editor.Message.request("asset-db", "open-asset", scene); - return ok({ success: true, scene }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async saveScene(): Promise { - try { - // 現在のシーンが保存済みファイルか確認(新規シーンの場合はダイアログが出るのでスキップ) - const scenes = await Editor.Message.request("asset-db", "query-assets", { - pattern: "db://assets/**/*.scene", - }).catch(() => []); - - // シーン名を取得して既存シーンか判定 - const hierarchy = await Editor.Message.request( - "scene", "execute-scene-script", - { name: "cocos-creator-mcp", method: "getSceneHierarchy", args: [false] } - ).catch(() => null); - - const sceneName = hierarchy?.sceneName; - const isNewScene = !sceneName || sceneName === "scene-2d" || sceneName === "Untitled"; - if (isNewScene) { - return ok({ success: true, note: "New/untitled scene, skip save to avoid dialog" }); - } - - // シーンがdirtyでない場合は保存不要 - const isDirty = await (Editor.Message.request as any)("scene", "query-is-dirty").catch(() => true); - if (!isDirty) { - return ok({ success: true, note: "Scene not dirty, skip save" }); - } - - const result = await (Editor.Message.request as any)("scene", "save-scene", false); - return ok({ success: true, result }); - } catch (e: any) { - return err(e.message || String(e)); - } - } - - private async getSceneList(): Promise { - try { - const results = await Editor.Message.request("asset-db", "query-assets", { - pattern: "db://assets/**/*.scene", - }); - const scenes = (results || []).map((a: any) => ({ - uuid: a.uuid, - path: a.path || a.url, - name: a.name, - })); - return ok({ success: true, scenes }); - } catch (e: any) { - return err(e.message || String(e)); - } - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/scene-view-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/scene-view-tools.ts deleted file mode 100644 index 48eb15c..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/scene-view-tools.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; - -export class SceneViewTools implements ToolCategory { - readonly categoryName = "sceneView"; - - getTools(): ToolDefinition[] { - return [ - { - name: "view_change_gizmo_tool", - description: "Change the gizmo tool (move, rotate, scale, rect).", - inputSchema: { - type: "object", - properties: { - tool: { type: "string", enum: ["move", "rotate", "scale", "rect"], description: "Gizmo tool name" }, - }, - required: ["tool"], - }, - }, - { - name: "view_query_gizmo_tool", - description: "Get the currently active gizmo tool.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "view_change_gizmo_pivot", - description: "Change gizmo pivot mode (center or pivot).", - inputSchema: { - type: "object", - properties: { - pivot: { type: "string", enum: ["center", "pivot"], description: "Pivot mode" }, - }, - required: ["pivot"], - }, - }, - { - name: "view_query_gizmo_pivot", - description: "Get the current gizmo pivot mode.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "view_change_gizmo_coordinate", - description: "Change gizmo coordinate system (local or global).", - inputSchema: { - type: "object", - properties: { - coordinate: { type: "string", enum: ["local", "global"], description: "Coordinate system" }, - }, - required: ["coordinate"], - }, - }, - { - name: "view_query_gizmo_coordinate", - description: "Get the current gizmo coordinate system.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "view_change_mode_2d_3d", - description: "Switch between 2D and 3D view mode.", - inputSchema: { - type: "object", - properties: { - mode: { type: "string", enum: ["2d", "3d"], description: "View mode" }, - }, - required: ["mode"], - }, - }, - { - name: "view_query_mode_2d_3d", - description: "Get the current 2D/3D view mode.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "view_set_grid_visible", - description: "Show or hide the scene grid.", - inputSchema: { - type: "object", - properties: { - visible: { type: "boolean", description: "Whether to show the grid" }, - }, - required: ["visible"], - }, - }, - { - name: "view_query_grid_visible", - description: "Check if the scene grid is visible.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "view_focus_on_node", - description: "Focus the scene camera on specific node(s).", - inputSchema: { - type: "object", - properties: { - uuids: { type: "array", items: { type: "string" }, description: "Node UUIDs to focus on" }, - }, - required: ["uuids"], - }, - }, - { - name: "view_get_status", - description: "Get the current scene view status (gizmo tool, pivot, coordinate, grid, etc.).", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "view_set_icon_gizmo_3d", - description: "Toggle 3D icon gizmos on/off.", - inputSchema: { - type: "object", - properties: { enabled: { type: "boolean" } }, - required: ["enabled"], - }, - }, - { - name: "view_query_icon_gizmo_3d", - description: "Check if 3D icon gizmos are enabled.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "view_set_icon_gizmo_size", - description: "Set the icon gizmo size.", - inputSchema: { - type: "object", - properties: { size: { type: "number" } }, - required: ["size"], - }, - }, - { - name: "view_query_icon_gizmo_size", - description: "Get the current icon gizmo size.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "view_align_with_view", - description: "Align the selected node with the current camera view.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "view_align_view_with_node", - description: "Align the camera view with the selected node.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "view_reset", - description: "Reset the scene view to default state.", - inputSchema: { type: "object", properties: {} }, - }, - ]; - } - - async execute(toolName: string, args: Record): Promise { - try { - switch (toolName) { - case "view_change_gizmo_tool": - await (Editor.Message.request as any)("scene", "change-gizmo-tool", args.tool); - return ok({ success: true, tool: args.tool }); - case "view_query_gizmo_tool": { - const tool = await (Editor.Message.request as any)("scene", "query-gizmo-tool-name"); - return ok({ success: true, tool }); - } - case "view_change_gizmo_pivot": - await (Editor.Message.request as any)("scene", "change-gizmo-pivot", args.pivot); - return ok({ success: true, pivot: args.pivot }); - case "view_query_gizmo_pivot": { - const pivot = await (Editor.Message.request as any)("scene", "query-gizmo-pivot"); - return ok({ success: true, pivot }); - } - case "view_change_gizmo_coordinate": - await (Editor.Message.request as any)("scene", "change-gizmo-coordinate", args.coordinate); - return ok({ success: true, coordinate: args.coordinate }); - case "view_query_gizmo_coordinate": { - const coord = await (Editor.Message.request as any)("scene", "query-gizmo-coordinate"); - return ok({ success: true, coordinate: coord }); - } - case "view_change_mode_2d_3d": - await (Editor.Message.request as any)("scene", "change-view-mode-2d-3d", args.mode); - return ok({ success: true, mode: args.mode }); - case "view_query_mode_2d_3d": { - const mode = await (Editor.Message.request as any)("scene", "query-view-mode-2d-3d"); - return ok({ success: true, mode }); - } - case "view_set_grid_visible": - await (Editor.Message.request as any)("scene", "set-grid-visible", args.visible); - return ok({ success: true, visible: args.visible }); - case "view_query_grid_visible": { - const visible = await (Editor.Message.request as any)("scene", "query-grid-visible"); - return ok({ success: true, visible }); - } - case "view_focus_on_node": - await (Editor.Message.request as any)("scene", "focus-camera-on-nodes", args.uuids); - return ok({ success: true, uuids: args.uuids }); - case "view_get_status": { - const [tool, pivot, coord, mode, grid] = await Promise.all([ - (Editor.Message.request as any)("scene", "query-gizmo-tool-name").catch(() => null), - (Editor.Message.request as any)("scene", "query-gizmo-pivot").catch(() => null), - (Editor.Message.request as any)("scene", "query-gizmo-coordinate").catch(() => null), - (Editor.Message.request as any)("scene", "query-view-mode-2d-3d").catch(() => null), - (Editor.Message.request as any)("scene", "query-grid-visible").catch(() => null), - ]); - return ok({ success: true, tool, pivot, coordinate: coord, mode, gridVisible: grid }); - } - case "view_set_icon_gizmo_3d": - await (Editor.Message.request as any)("scene", "set-icon-gizmo-3d", args.enabled); - return ok({ success: true, enabled: args.enabled }); - case "view_query_icon_gizmo_3d": { - const enabled = await (Editor.Message.request as any)("scene", "query-is-icon-gizmo-3d"); - return ok({ success: true, enabled }); - } - case "view_set_icon_gizmo_size": - await (Editor.Message.request as any)("scene", "set-icon-gizmo-size", args.size); - return ok({ success: true, size: args.size }); - case "view_query_icon_gizmo_size": { - const size = await (Editor.Message.request as any)("scene", "query-icon-gizmo-size"); - return ok({ success: true, size }); - } - case "view_align_with_view": - await (Editor.Message.request as any)("scene", "align-with-view"); - return ok({ success: true }); - case "view_align_view_with_node": - await (Editor.Message.request as any)("scene", "align-view-with-node"); - return ok({ success: true }); - case "view_reset": - await (Editor.Message.request as any)("scene", "reset-scene-view"); - return ok({ success: true }); - default: - return err(`Unknown tool: ${toolName}`); - } - } catch (e: any) { - return err(e.message || String(e)); - } - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/server-tools.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/server-tools.ts deleted file mode 100644 index e95f240..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/tools/server-tools.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { ToolCategory, ToolDefinition, ToolResult } from "../types"; -import { ok, err } from "../tool-base"; -import { BUILD_HASH } from "../mcp-server"; - -export class ServerTools implements ToolCategory { - readonly categoryName = "server"; - - getTools(): ToolDefinition[] { - return [ - { - name: "server_query_ip_list", - description: "Get the list of IP addresses the editor server is listening on.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "server_query_port", - description: "Get the port number of the editor's built-in server.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "server_get_status", - description: "Get the editor server status (IP, port, connectivity).", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "server_get_build_hash", - description: "Get the build hash of the MCP server. The hash is derived from the code content, so identical code always produces the same hash.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "server_check_connectivity", - description: "Check if the editor server is reachable.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "server_get_network_interfaces", - description: "Get detailed network interface information.", - inputSchema: { type: "object", properties: {} }, - }, - { - name: "server_check_code_sync", - description: "Check if the running MCP extension code matches the latest build. Compares the runtime BUILD_HASH with the hash of dist/ files. Returns whether extension reload or CC restart is needed.", - inputSchema: { type: "object", properties: {} }, - }, - ]; - } - - async execute(toolName: string, _args: Record): Promise { - try { - switch (toolName) { - case "server_query_ip_list": { - const ips = await (Editor.Message.request as any)("server", "query-ip-list"); - return ok({ success: true, ips }); - } - case "server_query_port": { - const port = await (Editor.Message.request as any)("server", "query-port"); - return ok({ success: true, port }); - } - case "server_get_status": { - const [ips, port] = await Promise.all([ - (Editor.Message.request as any)("server", "query-ip-list").catch(() => []), - (Editor.Message.request as any)("server", "query-port").catch(() => null), - ]); - return ok({ success: true, ips, port, buildId: BUILD_HASH }); - } - case "server_get_build_hash": { - return ok({ success: true, buildHash: BUILD_HASH }); - } - case "server_check_connectivity": { - try { - const port = await (Editor.Message.request as any)("server", "query-port"); - return ok({ success: true, reachable: true, port }); - } catch { - return ok({ success: true, reachable: false }); - } - } - case "server_get_network_interfaces": { - const os = require("os"); - const interfaces = os.networkInterfaces(); - return ok({ success: true, interfaces }); - } - case "server_check_code_sync": { - try { - const fs = require("fs"); - const path = require("path"); - const crypto = require("crypto"); - const extDir = path.join(Editor.Project.path, "extensions", "cocos-creator-mcp", "dist"); - if (!fs.existsSync(extDir)) { - return ok({ success: true, synced: false, note: "Extension dist/ not found", runtimeHash: BUILD_HASH }); - } - const hash = crypto.createHash("sha256"); - const collectJs = (dir: string, prefix = ""): string[] => { - let files: string[] = []; - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const rel = prefix ? `${prefix}/${entry.name}` : entry.name; - if (entry.isDirectory()) files = files.concat(collectJs(path.join(dir, entry.name), rel)); - else if (entry.name.endsWith(".js")) files.push(rel); - } - return files; - }; - for (const file of collectJs(extDir).sort()) { - let content = fs.readFileSync(path.join(extDir, file), "utf8"); - // mcp-server.js の baked hash を postbuild 前と同じプレースホルダに逆置換 - // (postbuild は __BUILD_HASH__ が残った状態でハッシュ計算しているため、同じ入力にする) - if (file === "mcp-server.js") { - content = content.replace(/exports\.BUILD_HASH = "[a-f0-9]{12}"/, 'exports.BUILD_HASH = "__BUILD_HASH__"'); - } - hash.update(content.replace(/__BUILD_HASH__/g, "")); - } - const diskHash = hash.digest("hex").substring(0, 12); - const synced = diskHash === BUILD_HASH; - return ok({ - success: true, - synced, - runtimeHash: BUILD_HASH, - diskHash, - action: synced ? "none" : "Extension reload or CC restart needed", - }); - } catch (e: any) { - return ok({ success: true, synced: false, error: e.message, runtimeHash: BUILD_HASH }); - } - } - default: - return err(`Unknown tool: ${toolName}`); - } - } catch (e: any) { - return err(e.message || String(e)); - } - } -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/types.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/types.ts deleted file mode 100644 index 0d00422..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/types.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** MCP Tool definition (JSON Schema based) */ -export interface ToolDefinition { - name: string; - description: string; - inputSchema: { - type: "object"; - properties: Record; - required?: string[]; - }; -} - -/** Result returned from tool execution */ -export interface ToolResult { - content: Array<{ type: "text"; text: string }>; - isError?: boolean; -} - -/** Interface that all tool categories must implement */ -export interface ToolCategory { - readonly categoryName: string; - getTools(): ToolDefinition[]; - execute(toolName: string, args: Record): Promise; -} - -/** JSON-RPC 2.0 request */ -export interface JsonRpcRequest { - jsonrpc: "2.0"; - id?: string | number; - method: string; - params?: any; -} - -/** JSON-RPC 2.0 response */ -export interface JsonRpcResponse { - jsonrpc: "2.0"; - id?: string | number | null; - result?: any; - error?: { - code: number; - message: string; - data?: any; - }; -} - -/** Server configuration */ -export interface ServerConfig { - port: number; - autoStart: boolean; - autoArchiveRecordings: boolean; -} - -export const DEFAULT_CONFIG: ServerConfig = { - port: 3000, - autoStart: false, - autoArchiveRecordings: false, -}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/utils.ts b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/utils.ts deleted file mode 100644 index 4b2c2b7..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/source/utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * MCP クライアントが JSON オブジェクトを文字列化して送信する問題への共通対策. - * スキーマに type が未宣言のオブジェクト引数は文字列で届く場合がある. - */ -export function parseMaybeJson(value: any): T { - if (typeof value === "string") { - try { return JSON.parse(value); } catch { /* string のまま */ } - } - return value; -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/test/prefab-test.mjs b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/test/prefab-test.mjs deleted file mode 100644 index ae96ee4..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/test/prefab-test.mjs +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Prefab保存テスト — プロパティがPrefabに保持されるか検証 - * - * 前提: CocosCreatorでcocos-creator-mcpサーバーが起動中、MainScene開いてる - */ - -const PORT = process.argv[2] || 3001; -const BASE = `http://127.0.0.1:${PORT}`; -let rpcId = 0; - -async function rpc(method, params = {}) { - const res = await fetch(`${BASE}/mcp`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ jsonrpc: "2.0", id: ++rpcId, method, params }), - }); - return res.json(); -} - -async function callTool(name, args = {}) { - const res = await rpc("tools/call", { name, arguments: args }); - if (res.error) return { _rpcError: res.error }; - const text = res.result?.content?.[0]?.text; - return text ? JSON.parse(text) : res.result; -} - -async function sceneScript(method, args) { - // Call execute-scene-script through a raw JSON-RPC (not via tool) - // Actually we need to go through the MCP server... let's use node_set_property hack - // Or better: add a scene_script tool call - // For now, use the existing setComponentProperty that IS registered in scene.ts - - // We can't call execute-scene-script directly from outside. - // But we CAN call the node_set_property tool which internally calls setNodeProperty scene script. - // For component property, we need a dedicated tool. - - // Workaround: Use curl to call the Editor's Messages API if available - return null; -} - -async function main() { - console.log("\n🧪 Prefab Property Persistence Test\n"); - - // Step 1: Create test node with Label - console.log("1. Creating test node with Label..."); - const created = await callTool("node_create", { - name: "PrefabPropertyTest", - parent: "83A39G7PtBqLLEoVjvUo4h", // Canvas - components: ["cc.Label"], - }); - console.log(` Node UUID: ${created.uuid}`); - const nodeUuid = created.uuid; - - // Step 2: Set position (this should persist) - console.log("2. Setting position to (100, 200, 0)..."); - await callTool("node_set_transform", { - uuid: nodeUuid, - position: { x: 100, y: 200, z: 0 }, - }); - - // Step 3: Verify position was set - const info1 = await callTool("node_get_info", { uuid: nodeUuid }); - console.log(` Position: (${info1.data?.position?.x}, ${info1.data?.position?.y})`); - console.log(` Components: ${info1.data?.components?.map(c => c.type).join(", ")}`); - - // Step 4: Try to create prefab via Editor API - console.log("3. Attempting to create prefab via Editor.Message..."); - - // We'll try the create-prefab scene message - // This requires calling: Editor.Message.request("scene", "create-prefab", nodeUuid, url) - // Since we don't have a tool for this, let's try different approaches: - - // Approach A: Try "create-prefab" message (3 args: nodeUuid, url, ?) - const prefabUrl = "db://assets/test/PrefabPropertyTest.prefab"; - - // We need to add this capability. For now, let's check if we can use - // asset-db to create an asset - console.log(` Target: ${prefabUrl}`); - - // Actually, let me just check the scene script createPrefabFromNode - // which IS registered in package.json scene methods - // But wait - our own MCP's scene.ts doesn't have createPrefabFromNode - - console.log("\n⚠️ Prefab creation tool not yet implemented in our MCP."); - console.log(" This is what needs to be built for v0.5.\n"); - - // Step 5: Test what the Editor API returns for prefab-related messages - console.log("4. Probing available prefab-related Editor Messages..."); - - const messages = [ - ["scene", "create-prefab"], - ["scene", "save-prefab"], - ["asset-db", "create-asset"], - ]; - - for (const [target, msg] of messages) { - try { - const res = await fetch(`${BASE}/mcp`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", id: ++rpcId, method: "tools/call", - params: { name: "node_get_info", arguments: { uuid: "test" } } - }), - }); - console.log(` ${target}:${msg} — needs testing`); - } catch (e) { - console.log(` ${target}:${msg} — error: ${e.message}`); - } - } - - // Cleanup - console.log("\n5. Cleaning up test node..."); - await callTool("node_delete", { uuid: nodeUuid }); - console.log(" Deleted.\n"); - - console.log("═".repeat(50)); - console.log(" Summary:"); - console.log(" - Node creation with components: ✅"); - console.log(" - Position/transform setting: ✅"); - console.log(" - Component property setting: ❓ (needs component tool)"); - console.log(" - Prefab creation: ❓ (needs prefab tool)"); - console.log(" - Property persistence in prefab: ❓ (blocked by above)"); - console.log("═".repeat(50)); - console.log("\n→ v0.5 で component + prefab ツール実装後に再検証"); -} - -main().catch(e => { console.error("Fatal:", e); process.exit(1); }); diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/test/regression.mjs b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/test/regression.mjs deleted file mode 100644 index 8d77068..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/test/regression.mjs +++ /dev/null @@ -1,1642 +0,0 @@ -/** - * 回帰テスト — 全MCPツールの動作確認 - * - * 前提: CocosCreatorでcocos-creator-mcpサーバーが起動中、シーンが開いていること - * 実行: node test/regression.mjs [port] - */ - -import { spawn } from "child_process"; -import path from "path"; -import { fileURLToPath } from "url"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); - -const PORT = process.argv[2] || 3000; -const BASE = `http://127.0.0.1:${PORT}`; - -let passed = 0; -let failed = 0; -let skipped = 0; -let rpcId = 0; - -// ── helpers ── - -async function callMcp(method, params = {}) { - const id = ++rpcId; - const res = await fetch(`${BASE}/mcp`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ jsonrpc: "2.0", id, method, params }), - }); - return res.json(); -} - -async function callTool(name, args = {}) { - const res = await callMcp("tools/call", { name, arguments: args }); - if (res.error) return { _rpcError: res.error }; - const text = res.result?.content?.[0]?.text; - return text ? JSON.parse(text) : res.result; -} - -function assert(condition, label) { - if (condition) { - console.log(` ✅ ${label}`); - passed++; - } else { - console.log(` ❌ ${label}`); - failed++; - } -} - -function skip(label) { - console.log(` ⚠️ ${label}`); - skipped++; -} - -// ── ALL 146 tool names ── -const ALL_TOOLS = [ - "asset_copy", "asset_create", "asset_delete", "asset_generate_available_url", - "asset_get_dependencies", "asset_get_details", "asset_import", "asset_move", - "asset_open_external", "asset_query_missing", "asset_query_path", "asset_query_ready", - "asset_query_url", "asset_query_users", "asset_query_uuid", "asset_reimport", - "asset_save", "asset_save_meta", - "builder_get_settings", "builder_open_panel", "builder_query_tasks", - "builder_run_preview", "builder_stop_preview", - "component_add", "component_auto_bind", "component_get_available", "component_get_components", - "component_get_info", "component_query_enum", "component_remove", "component_set_property", - "debug_clear_console", "debug_execute_script", "debug_get_console_logs", - "debug_get_editor_info", "debug_get_extension_info", "debug_get_log_file_info", - "debug_get_project_logs", "debug_list_extensions", "debug_list_messages", - "debug_open_url", "debug_query_devices", "debug_search_project_logs", "debug_validate_scene", - "debug_batch_screenshot", "debug_clear_code_cache", "debug_game_command", - "debug_preview", "debug_screenshot", - "debug_record_start", "debug_record_stop", "debug_wait_compile", - "node_create", "node_delete", "node_detect_type", "node_duplicate", "node_find_by_name", - "node_get_all", "node_get_info", "node_move", "node_set_active", "node_set_layer", - "node_set_layout", "node_set_property", "node_set_transform", - "prefab_close", "prefab_create", "prefab_create_and_replace", "prefab_create_from_spec", - "prefab_duplicate", "prefab_get_info", "prefab_instantiate", - "prefab_list", "prefab_open", "prefab_revert", "prefab_update", "prefab_validate", - "preferences_get", "preferences_get_all", "preferences_reset", "preferences_set", - "project_find_asset", "project_get_asset_info", "project_get_engine_info", - "project_get_info", "project_get_settings", "project_query_scripts", - "project_refresh_assets", "project_set_settings", - "refimage_add", "refimage_clear_all", "refimage_list", "refimage_query_config", - "refimage_query_current", "refimage_refresh", "refimage_remove", - "refimage_set_opacity", "refimage_set_position", "refimage_set_scale", "refimage_switch", - "scene_begin_undo", "scene_cancel_undo", "scene_close", "scene_copy_node", - "scene_create", "scene_cut_node", "scene_end_undo", - "scene_execute_component_method", "scene_execute_script", "scene_get_current", - "scene_get_hierarchy", "scene_get_list", "scene_move_array_element", - "scene_open", "scene_paste_node", "scene_query_classes", - "scene_query_component", "scene_query_component_has_script", "scene_query_components", - "scene_query_dirty", "scene_query_node", "scene_query_node_tree", - "scene_query_nodes_by_asset", "scene_query_ready", "scene_query_scene_bounds", - "scene_remove_array_element", "scene_reset_component", "scene_reset_node_transform", - "scene_reset_property", "scene_restore_prefab", "scene_save", "scene_save_as", - "scene_set_parent", "scene_snapshot", "scene_snapshot_abort", "scene_soft_reload", - "server_check_code_sync", "server_check_connectivity", "server_get_network_interfaces", - "server_get_status", "server_query_ip_list", "server_query_port", - "view_align_view_with_node", "view_align_with_view", - "view_change_gizmo_coordinate", "view_change_gizmo_pivot", "view_change_gizmo_tool", - "view_change_mode_2d_3d", "view_focus_on_node", "view_get_status", - "view_query_gizmo_coordinate", "view_query_gizmo_pivot", "view_query_gizmo_tool", - "view_query_grid_visible", "view_query_icon_gizmo_3d", "view_query_icon_gizmo_size", - "view_query_mode_2d_3d", "view_reset", - "view_set_grid_visible", "view_set_icon_gizmo_3d", "view_set_icon_gizmo_size", -]; - -// ── tests ── - -async function testHealth() { - console.log("\n── Health Check ──"); - const res = await fetch(`${BASE}/health`); - const data = await res.json(); - assert(data.status === "ok", "health status ok"); - assert(data.tools >= 148, `tool count >= 148 (got ${data.tools})`); -} - -async function testInitialize() { - console.log("\n── MCP Initialize ──"); - const res = await fetch(`${BASE}/mcp`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ jsonrpc: "2.0", id: ++rpcId, method: "initialize", params: {} }), - }); - const data = await res.json(); - assert(data.result?.protocolVersion, `protocol version: ${data.result?.protocolVersion}`); - assert(data.result?.serverInfo?.name === "cocos-creator-mcp", "server name"); - assert(data.result?.serverInfo?.version === "1.0.0", `version: ${data.result?.serverInfo?.version}`); - // Mcp-Session-Id ヘッダーが返されるか - const sessionId = res.headers.get("Mcp-Session-Id"); - assert(!!sessionId, `Mcp-Session-Id header: ${sessionId}`); -} - -async function testToolsList() { - console.log("\n── tools/list ──"); - const res = await callMcp("tools/list", {}); - const tools = res.result?.tools || []; - assert(tools.length >= 148, `tool count >= 148 (got ${tools.length})`); - - const names = tools.map((t) => t.name); - for (const name of ALL_TOOLS) { - assert(names.includes(name), `registered: ${name}`); - } -} - -async function testSceneTools() { - console.log("\n── scene tools ──"); - const hier = await callTool("scene_get_hierarchy", { includeComponents: true }); - assert(hier.success === true, "get_hierarchy success"); - assert(!!hier.sceneName, `scene: ${hier.sceneName}`); - const canvas = hier.hierarchy?.find((n) => n.name === "Canvas"); - assert(!!canvas, "Canvas found"); - - const list = await callTool("scene_get_list"); - assert(list.success === true, "get_list success"); - - const save = await callTool("scene_save"); - assert(save.success === true || !save._rpcError, "save ok"); -} - -async function testNodeCrud() { - console.log("\n── node CRUD ──"); - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - - const created = await callTool("node_create", { name: "V1TestNode", parent: canvasUuid }); - assert(created.success === true, "create"); - const uuid = created.uuid; - - const info = await callTool("node_get_info", { uuid }); - assert(info.data?.name === "V1TestNode", "get_info"); - - const found = await callTool("node_find_by_name", { name: "V1TestNode" }); - assert(found.data?.length >= 1, "find_by_name"); - - await callTool("node_set_property", { uuid, property: "name", value: "V1Renamed" }); - const info2 = await callTool("node_get_info", { uuid }); - assert(info2.data?.name === "V1Renamed", "set_property"); - - await callTool("node_set_transform", { uuid, position: { x: 10, y: 20, z: 0 }, scale: { x: 3, y: 3, z: 1 } }); - const info3 = await callTool("node_get_info", { uuid }); - assert(info3.data?.position?.x === 10, "set_transform"); - - await callTool("node_set_active", { uuid, active: false }); - const info4 = await callTool("node_get_info", { uuid }); - assert(info4.data?.active === false, "set_active"); - - const all = await callTool("node_get_all"); - assert(!!all.data?.find((n) => n.uuid === uuid), "get_all"); - - const duped = await callTool("node_duplicate", { uuid }); - assert(duped.success === true, "duplicate"); - const dupUuid = Array.isArray(duped.newUuid) ? duped.newUuid[0] : duped.newUuid; - - if (dupUuid) { - const moved = await callTool("node_move", { uuid: dupUuid, parentUuid: uuid }); - if (moved.success === true) { - assert(true, "move"); - } else { - skip("move (requires restart)"); - } - await callTool("node_delete", { uuid: dupUuid }); - } - await callTool("node_delete", { uuid }); - assert(true, "delete + cleanup"); -} - -async function testComponentTools() { - console.log("\n── component tools ──"); - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - const created = await callTool("node_create", { name: "CompV1Test", parent: canvasUuid }); - const uuid = created.uuid; - - const added = await callTool("component_add", { uuid, componentType: "cc.Label" }); - assert(added.success === true, "add"); - - const comps = await callTool("component_get_components", { uuid }); - assert(comps.components?.some((c) => c.type === "Label"), "get_components"); - - const set1 = await callTool("component_set_property", { uuid, componentType: "cc.Label", property: "string", value: "v1test" }); - assert(set1.success === true, "set_property string"); - - const set2 = await callTool("component_set_property", { uuid, componentType: "cc.Label", property: "fontSize", value: 64 }); - assert(set2.success === true, "set_property fontSize"); - - const removed = await callTool("component_remove", { uuid, componentType: "cc.Label" }); - assert(removed.success === true, "remove"); - - await callTool("node_delete", { uuid }); -} - -async function testPrefabTools() { - console.log("\n── prefab tools ──"); - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - - const list = await callTool("prefab_list"); - assert(list.success === true, "list"); - - const created = await callTool("node_create", { name: "PrefabV1Test", parent: canvasUuid, components: ["cc.Label"] }); - const uuid = created.uuid; - await callTool("component_set_property", { uuid, componentType: "cc.Label", property: "string", value: "v1prefab" }); - - // テスト用Prefabパスを毎回ユニークにしてoverwriteを避ける - const testPrefabPath = `db://assets/test/V1Test_${Date.now()}.prefab`; - const prefab = await callTool("prefab_create", { uuid, path: testPrefabPath }); - assert(prefab.success === true, "create"); - - if (prefab.result) { - const info = await callTool("prefab_get_info", { uuid: prefab.result }); - assert(info.success === true, "get_info"); - - const inst = await callTool("prefab_instantiate", { prefabUuid: prefab.result, parent: canvasUuid }); - assert(inst.success === true, "instantiate"); - if (inst.nodeUuid) await callTool("node_delete", { uuid: inst.nodeUuid }); - } - - await callTool("node_delete", { uuid }); - - // テスト用Prefabアセットを削除 - if (testPrefabPath) { - await callTool("asset_delete", { path: testPrefabPath }); - } -} - -async function testProjectTools() { - console.log("\n── project tools ──"); - const info = await callTool("project_get_info"); - assert(info.success === true, "get_info"); - - const refresh = await callTool("project_refresh_assets"); - assert(refresh.success === true || !refresh._rpcError, "refresh_assets"); - - const found = await callTool("project_find_asset", { pattern: "db://assets/**/*.scene" }); - assert(found.assets?.length > 0, "find_asset"); - - if (found.assets?.length > 0) { - const ai = await callTool("project_get_asset_info", { uuid: found.assets[0].uuid }); - assert(ai.success === true, "get_asset_info"); - } -} - -async function testAssetTools() { - console.log("\n── asset tools ──"); - // Find any scene asset dynamically (project-independent) - const scenes = await callTool("project_find_asset", { pattern: "db://assets/**/*.scene" }); - const scenePath = scenes.assets?.[0]?.path || scenes.assets?.[0]?.url; - const sceneUuid = scenes.assets?.[0]?.uuid; - - if (scenePath) { - const quuid = await callTool("asset_query_uuid", { path: scenePath }); - assert(quuid.success === true, "query_uuid"); - - const uuid = quuid.uuid || sceneUuid; - if (uuid) { - const qpath = await callTool("asset_query_path", { uuid }); - assert(qpath.success === true, "query_path"); - - const qurl = await callTool("asset_query_url", { uuid }); - assert(qurl.success === true, "query_url"); - - const details = await callTool("asset_get_details", { uuid }); - assert(details.success === true, "get_details"); - - const deps = await callTool("asset_get_dependencies", { uuid }); - assert(deps.success === true || !deps._rpcError, "get_dependencies"); - } - } else { - skip("asset tools (no scene found in project)"); - } -} - -async function testSceneAdvancedTools() { - console.log("\n── scene advanced tools ──"); - const dirty = await callTool("scene_query_dirty"); - assert(dirty.success === true || !dirty._rpcError, "query_dirty"); - - const tree = await callTool("scene_query_node_tree"); - assert(tree.success === true, "query_node_tree"); - - const classes = await callTool("scene_query_classes"); - assert(classes.success === true || !classes._rpcError, "query_classes"); - - // reset_node_transform — create, transform, reset, verify - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - const n = await callTool("node_create", { name: "ResetTest", parent: canvasUuid }); - await callTool("node_set_transform", { uuid: n.uuid, position: { x: 99, y: 99, z: 0 } }); - await callTool("scene_reset_node_transform", { uuid: n.uuid }); - const info = await callTool("node_get_info", { uuid: n.uuid }); - assert(info.data?.position?.x === 0, "reset_node_transform"); - await callTool("node_delete", { uuid: n.uuid }); -} - -async function testSceneViewTools() { - console.log("\n── scene view tools ──"); - const status = await callTool("view_get_status"); - assert(status.success === true, "get_status"); - - const tool = await callTool("view_query_gizmo_tool"); - assert(tool.success === true || !tool._rpcError, "query_gizmo_tool"); - - const pivot = await callTool("view_query_gizmo_pivot"); - assert(pivot.success === true || !pivot._rpcError, "query_gizmo_pivot"); - - const coord = await callTool("view_query_gizmo_coordinate"); - assert(coord.success === true || !coord._rpcError, "query_gizmo_coordinate"); - - const grid = await callTool("view_query_grid_visible"); - assert(grid.success === true || !grid._rpcError, "query_grid_visible"); - - const mode = await callTool("view_query_mode_2d_3d"); - assert(mode.success === true || !mode._rpcError, "query_mode_2d_3d"); -} - -async function testDebugTools() { - console.log("\n── debug tools ──"); - const info = await callTool("debug_get_editor_info"); - assert(!!info.version, `editor version: ${info.version}`); - - const msgs = await callTool("debug_list_messages", { target: "scene" }); - assert(msgs.success === true, "list_messages"); - - const logs = await callTool("debug_get_console_logs", { count: 10 }); - assert(logs.success === true, "get_console_logs"); - - // source filter — verify parameter is accepted (filtering requires CC restart to apply) - const sceneLogs = await callTool("debug_get_console_logs", { count: 10, source: "scene" }); - assert(sceneLogs.success === true, "get_console_logs source=scene accepted"); - - const gameLogs = await callTool("debug_get_console_logs", { count: 10, source: "game" }); - assert(gameLogs.success === true, "get_console_logs source=game accepted"); - - const exts = await callTool("debug_list_extensions"); - assert(exts.success === true, "list_extensions"); -} - -async function testPreferencesTools() { - console.log("\n── preferences tools ──"); - const all = await callTool("preferences_get_all", { protocol: "general" }); - assert(all.success === true || !all._rpcError, "get_all"); -} - -async function testServerTools() { - console.log("\n── server tools ──"); - const status = await callTool("server_get_status"); - assert(status.success === true, "get_status"); - const buildInfo = await callTool("server_get_build_hash"); - assert(!!buildInfo.buildHash && buildInfo.buildHash !== "__BUILD_HASH__", `buildHash: ${buildInfo.buildHash}`); - - const port = await callTool("server_query_port"); - assert(port.success === true, "query_port"); -} - -async function testBuilderTools() { - console.log("\n── builder tools ──"); - const settings = await callTool("builder_get_settings"); - assert(settings.success === true || !settings._rpcError, "get_settings"); - - const tasks = await callTool("builder_query_tasks"); - assert(tasks.success === true || !tasks._rpcError, "query_tasks"); -} - -async function testNewSceneAdvancedTools() { - console.log("\n── new scene advanced tools ──"); - const ready = await callTool("scene_query_ready"); - assert(ready.success === true || !ready._rpcError, "query_ready"); - - const current = await callTool("scene_get_current"); - assert(current.success === true || !current._rpcError, "get_current"); - - const hasScript = await callTool("scene_query_component_has_script", { name: "cc.Label" }); - assert(hasScript.success === true || !hasScript._rpcError, "query_component_has_script"); -} - -async function testNewViewTools() { - console.log("\n── new scene view tools ──"); - const icon3d = await callTool("view_query_icon_gizmo_3d"); - assert(icon3d.success === true || !icon3d._rpcError, "query_icon_gizmo_3d"); - - const iconSize = await callTool("view_query_icon_gizmo_size"); - assert(iconSize.success === true || !iconSize._rpcError, "query_icon_gizmo_size"); -} - -async function testComponentAdvanced() { - console.log("\n── component advanced tools ──"); - const available = await callTool("component_get_available"); - assert(available.success === true || !available._rpcError, "get_available"); -} - -async function testProjectAdvanced() { - console.log("\n── project advanced tools ──"); - const engine = await callTool("project_get_engine_info"); - assert(engine.success === true || !engine._rpcError, "get_engine_info"); - - const settings = await callTool("project_get_settings", { protocol: "general" }); - assert(settings.success === true || !settings._rpcError, "get_settings"); -} - -async function testReferenceImageTools() { - console.log("\n── reference image tools ──"); - const config = await callTool("refimage_query_config"); - assert(config.success === true || !config._rpcError, "query_config"); - - const list = await callTool("refimage_list"); - assert(list.success === true || !list._rpcError, "list"); - - const current = await callTool("refimage_query_current"); - assert(current.success === true || !current._rpcError, "query_current"); -} - -async function testV13Regressions() { - console.log("\n── v1.3 regressions (set-property / prefab guard / scene_save) ──"); - - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - - // 1. scene_save — ダイアログなしで成功するか - const save = await callTool("scene_save"); - assert(save.success === true, "scene_save no dialog"); - - // 2. prefab_create 上書きガード — テスト用Prefabを作って上書きを試みる - const guardNode = await callTool("node_create", { name: "GuardTest", parent: canvasUuid }); - const guardPath = `db://assets/test/GuardTest_${Date.now()}.prefab`; - const guardPrefab = await callTool("prefab_create", { uuid: guardNode.uuid, path: guardPath }); - assert(guardPrefab.success === true, "prefab_create for guard test"); - // 同じパスに再作成 → エラーになるはず - const guardResult = await callTool("prefab_create", { uuid: guardNode.uuid, path: guardPath }); - assert(!!guardResult.error || !!guardResult._rpcError, "prefab_create overwrite guard"); - await callTool("node_delete", { uuid: guardNode.uuid }); - await callTool("asset_delete", { path: guardPath }); - - // 3. set-property + prefab_update — テスト用Prefabを自作してテスト - const prefabNode = await callTool("node_create", { name: "UpdateTestNode", parent: canvasUuid, components: ["cc.Label"] }); - await callTool("component_set_property", { uuid: prefabNode.uuid, componentType: "cc.Label", property: "string", value: "original" }); - const testPrefabPath = `db://assets/test/UpdateTest_${Date.now()}.prefab`; - const created = await callTool("prefab_create", { uuid: prefabNode.uuid, path: testPrefabPath }); - await callTool("node_delete", { uuid: prefabNode.uuid }); - - if (created.result) { - // instantiate → set-property → prefab_update - const inst = await callTool("prefab_instantiate", { prefabUuid: created.result, parent: canvasUuid }); - const instUuid = inst.nodeUuid; - if (instUuid) { - const setProp = await callTool("component_set_property", { - uuid: instUuid, componentType: "cc.Label", property: "fontSize", value: 48 - }); - assert(setProp.success === true, "set-property fontSize via scene:set-property"); - - const updated = await callTool("prefab_update", { uuid: instUuid }); - assert(updated.success === true, "prefab_update after set-property"); - - await callTool("node_delete", { uuid: instUuid }); - } - await callTool("asset_delete", { path: testPrefabPath }); - } else { - skip("set-property + prefab_update (prefab_create failed)"); - } - - // 4. パラメータエイリアス — component でも componentType でも動くか - const aliasNode = await callTool("node_create", { name: "AliasTest", parent: canvasUuid, components: ["cc.Label"] }); - const aliasSet = await callTool("component_set_property", { - uuid: aliasNode.uuid, component: "cc.Label", property: "string", value: "alias" - }); - assert(aliasSet.success === true, "param alias: component → componentType"); - await callTool("node_delete", { uuid: aliasNode.uuid }); -} - -async function testNewEditorAPIs() { - console.log("\n── new Editor APIs (beyond existing MCP) ──"); - - // scene_query_node - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - if (canvasUuid) { - const nodeDump = await callTool("scene_query_node", { uuid: canvasUuid }); - assert(nodeDump.success === true || !nodeDump._rpcError, "query_node"); - } - - // asset_query_ready - const ready = await callTool("asset_query_ready"); - assert(ready.success === true || !ready._rpcError, "asset_query_ready"); - - // asset_generate_available_url - const avail = await callTool("asset_generate_available_url", { url: "db://assets/test/TestGenerated.prefab" }); - assert(avail.success === true || !avail._rpcError, "asset_generate_available_url"); - - // server_check_connectivity - const conn = await callTool("server_check_connectivity"); - assert(conn.success === true, "server_check_connectivity"); - - // server_get_network_interfaces - const net = await callTool("server_get_network_interfaces"); - assert(net.success === true, "server_get_network_interfaces"); - - // node_detect_type - if (canvasUuid) { - const detect = await callTool("node_detect_type", { uuid: canvasUuid }); - assert(detect.success === true, "node_detect_type"); - assert(detect.nodeType === "2D" || detect.nodeType === "Node", `Canvas type: ${detect.nodeType}`); - } - - // debug_get_log_file_info - const logInfo = await callTool("debug_get_log_file_info"); - assert(logInfo.success === true, "debug_get_log_file_info"); - - // debug_validate_scene - const valid = await callTool("debug_validate_scene"); - assert(valid.success === true, "debug_validate_scene"); - - // prefab_validate (use MainScene UUID) - const scenes = await callTool("scene_get_list"); - if (scenes.scenes?.length > 0) { - const pv = await callTool("prefab_validate", { uuid: scenes.scenes[0].uuid }); - assert(pv.success === true || !pv._rpcError, "prefab_validate"); - } -} - -async function testV15NewTools() { - console.log("\n── v1.5 new tools (create_and_replace / batch set_property / prefab_open) ──"); - - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - - // 1. prefab_create_and_replace - const node = await callTool("node_create", { name: "ReplaceTest", parent: canvasUuid, components: ["cc.Label"] }); - await callTool("component_set_property", { uuid: node.uuid, componentType: "cc.Label", property: "string", value: "replace_test" }); - const replacePath = `db://assets/test/ReplaceTest_${Date.now()}.prefab`; - const replaced = await callTool("prefab_create_and_replace", { uuid: node.uuid, path: replacePath }); - assert(replaced.success === true, "prefab_create_and_replace success"); - assert(!!replaced.prefabAssetUuid, "prefab_create_and_replace returns prefabAssetUuid"); - assert(!!replaced.newInstanceUuid, "prefab_create_and_replace returns newInstanceUuid"); - - // Verify original node is gone - const oldNode = await callTool("node_get_info", { uuid: node.uuid }); - assert(!oldNode.data || oldNode.success === false, "original node removed"); - - // Verify new instance exists - if (replaced.newInstanceUuid) { - const newNode = await callTool("node_get_info", { uuid: replaced.newInstanceUuid }); - // Prefabインスタンスの名前はPrefabアセット名になる(CocosCreator仕様) - assert(newNode.data?.name != null, `new instance exists (name: ${newNode.data?.name})`); - await callTool("node_delete", { uuid: replaced.newInstanceUuid }); - } - await callTool("asset_delete", { path: replacePath }); - - // 2. component_set_property batch mode - const batchNode = await callTool("node_create", { name: "BatchTest", parent: canvasUuid, components: ["cc.Label"] }); - const batchResult = await callTool("component_set_property", { - uuid: batchNode.uuid, - componentType: "cc.Label", - properties: [ - { property: "string", value: "batch_test" }, - { property: "fontSize", value: 72 }, - ], - }); - assert(batchResult.success === true, "batch set_property success"); - assert(batchResult.results?.length === 2, "batch set_property 2 results"); - await callTool("node_delete", { uuid: batchNode.uuid }); - - // 3. prefab_create_and_replace overwrite guard - const guardNode2 = await callTool("node_create", { name: "ReplaceGuard", parent: canvasUuid }); - const guardPath2 = `db://assets/test/ReplaceGuard_${Date.now()}.prefab`; - // First create (should succeed via normal create) - const firstPrefab = await callTool("prefab_create", { uuid: guardNode2.uuid, path: guardPath2 }); - assert(firstPrefab.success === true, "first prefab create for replace guard"); - // Try create_and_replace with same path (should fail) - const guardReplace = await callTool("prefab_create_and_replace", { uuid: guardNode2.uuid, path: guardPath2 }); - assert(!!guardReplace.error || !!guardReplace._rpcError, "create_and_replace overwrite guard"); - await callTool("node_delete", { uuid: guardNode2.uuid }); - await callTool("asset_delete", { path: guardPath2 }); -} - -async function testV16NewTools() { - console.log("\n── v1.6 new tools (batch_screenshot / widget / source filter) ──"); - - // 1. node_create_tree with widget - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - if (canvasUuid) { - const tree = await callTool("node_create_tree", { - parent: canvasUuid, - spec: { - name: "WidgetTestNode", - components: ["cc.UITransform"], - widget: { top: 10, left: 20, right: 20 }, - }, - }); - assert(tree.success === true, "create_tree with widget"); - if (tree.data?.uuid) { - await callTool("node_delete", { uuid: tree.data.uuid }); - } - } else { - skip("widget test (no Canvas)"); - } - - // 2. debug_batch_screenshot — verify registered - const toolsList = await callMcp("tools/list", {}); - const batchTool = toolsList.result?.tools?.find((t) => t.name === "debug_batch_screenshot"); - assert(!!batchTool, "debug_batch_screenshot registered"); - - // 3. component_query_enum - if (canvasUuid) { - const enumNode = await callTool("node_create", { name: "EnumTest", parent: canvasUuid, components: ["cc.Layout"] }); - if (enumNode.uuid) { - const enumResult = await callTool("component_query_enum", { uuid: enumNode.uuid, componentType: "cc.Layout", property: "resizeMode" }); - assert(enumResult.success === true, "query_enum success"); - assert(Array.isArray(enumResult.enumList), "query_enum returns enumList"); - if (enumResult.enumList) { - const names = enumResult.enumList.map((e) => e.name); - assert(names.includes("CONTAINER"), "resizeMode has CONTAINER"); - assert(names.includes("CHILDREN"), "resizeMode has CHILDREN"); - } - await callTool("node_delete", { uuid: enumNode.uuid }); - } - } - - // 4. server_check_code_sync - const syncResult = await callTool("server_check_code_sync"); - assert(syncResult.success === true, "check_code_sync success"); - assert(syncResult.runtimeHash != null, `runtimeHash: ${syncResult.runtimeHash}`); - assert(syncResult.diskHash != null, `diskHash: ${syncResult.diskHash}`); - - // 5. component_query_enum registered - const enumTool = toolsList.result?.tools?.find((t) => t.name === "component_query_enum"); - assert(!!enumTool, "component_query_enum registered"); - - // 6. server_check_code_sync registered - const syncTool = toolsList.result?.tools?.find((t) => t.name === "server_check_code_sync"); - assert(!!syncTool, "server_check_code_sync registered"); -} - -async function testV18NewTools() { - console.log("\n── v1.8 new tools (preview recorder) ──"); - - // 1. debug_record_start registered - const toolsList = await callMcp("tools/list", {}); - const startTool = toolsList.result?.tools?.find((t) => t.name === "debug_record_start"); - assert(!!startTool, "debug_record_start registered"); - - // 2. debug_record_stop registered - const stopTool = toolsList.result?.tools?.find((t) => t.name === "debug_record_stop"); - assert(!!stopTool, "debug_record_stop registered"); -} - -async function testV111NewTools() { - console.log("\n── v1.11 new tools (auto_bind / create_from_spec / wait_compile) ──"); - - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - if (!canvasUuid) { skip("v1.11 tests (no Canvas)"); return; } - - // 1. component_auto_bind — built-in コンポーネント(cc.Button)でテスト - { - const tree = await callTool("node_create_tree", { - parent: canvasUuid, - spec: { - name: "AutoBindTest", - components: ["cc.UITransform"], - children: [ - { name: "ClickTarget", components: ["cc.UITransform", "cc.Sprite", "cc.Button"] }, - ], - }, - }); - assert(tree.success === true, "auto_bind setup: create_tree"); - if (tree.data?.uuid) { - // auto_bind は @property を持つスクリプトコンポーネントが必要なので、 - // ここではツール自体が呼べること + not_found が返ることを確認 - // (テスト環境にカスタムスクリプトがないため) - const bindResult = await callTool("component_auto_bind", { - uuid: tree.data.uuid, - componentType: "cc.UITransform", - mode: "strict", - }); - // UITransform にはバインド対象の @property がないので boundCount=0 - assert(bindResult.success === true, "auto_bind returns success"); - assert(bindResult.boundCount === 0, "auto_bind: no bindable props on UITransform"); - await callTool("node_delete", { uuid: tree.data.uuid }); - } - } - - // 2. prefab_create_from_spec — 基本フロー - { - const specPath = `db://assets/test/FromSpec_${Date.now()}.prefab`; - const spec = { - name: "SpecTestNode", - components: ["cc.UITransform"], - children: [ - { name: "Header", components: ["cc.UITransform", "cc.Label"] }, - { name: "Body", components: ["cc.UITransform"] }, - ], - }; - const result = await callTool("prefab_create_from_spec", { path: specPath, spec }); - assert(result.success === true, "create_from_spec success"); - assert(!!result.prefabAssetUuid, "create_from_spec returns prefabAssetUuid"); - assert(result.path === specPath, "create_from_spec returns correct path"); - assert(result.nodeTree?.name === "SpecTestNode", "create_from_spec returns nodeTree"); - assert(result.nodeTree?.children?.length === 2, "create_from_spec nodeTree has 2 children"); - - // 元ノードがシーンに残っていないことを確認 - if (result.nodeTree?.uuid) { - const ghost = await callTool("node_get_info", { uuid: result.nodeTree.uuid }); - assert(!ghost.data || ghost.success === false, "temp node removed from scene"); - } - - // Prefab アセットが作成されたことを確認 - if (result.prefabAssetUuid) { - const info = await callTool("prefab_get_info", { uuid: result.prefabAssetUuid }); - assert(info.success === true, "created prefab asset exists"); - } - - // クリーンアップ - await callTool("asset_delete", { path: specPath }); - } - - // 3. prefab_create_from_spec — 上書きガード - { - // まず通常の Prefab を作成 - const guardNode = await callTool("node_create", { name: "GuardNode", parent: canvasUuid }); - const guardPath = `db://assets/test/SpecGuard_${Date.now()}.prefab`; - await callTool("prefab_create", { uuid: guardNode.uuid, path: guardPath }); - await callTool("node_delete", { uuid: guardNode.uuid }); - - // 同じパスに create_from_spec → エラーになるべき - const dupResult = await callTool("prefab_create_from_spec", { - path: guardPath, - spec: { name: "ShouldFail", components: ["cc.UITransform"] }, - }); - assert(!!dupResult.error || !!dupResult._rpcError, "create_from_spec overwrite guard"); - - await callTool("asset_delete", { path: guardPath }); - } - - // 4. debug_wait_compile — ツール登録確認 - { - const toolsList = await callMcp("tools/list", {}); - const waitTool = toolsList.result?.tools?.find((t) => t.name === "debug_wait_compile"); - assert(!!waitTool, "debug_wait_compile registered"); - } -} - -async function testPrefabEfficiency() { - console.log("\n── prefab efficiency (nodeName / screenshot / node_set_layout) ──"); - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - if (!canvasUuid) { skip("prefab efficiency tests (no Canvas)"); return; } - - // 1. node_set_layout — ツール登録確認 - const toolsList = await callMcp("tools/list", {}); - const layoutTool = toolsList.result?.tools?.find((t) => t.name === "node_set_layout"); - assert(!!layoutTool, "node_set_layout registered"); - - // 2. nodeName でノード作成→プロパティ設定 - const created = await callTool("node_create", { name: "EfficiencyTestNode", parent: canvasUuid, components: ["cc.Label"] }); - const uuid = created.uuid; - - // 2a. component_set_property with nodeName (uuid なし) - const setByName = await callTool("component_set_property", { - nodeName: "EfficiencyTestNode", - componentType: "cc.Label", - property: "string", - value: "byName", - }); - assert(setByName.success === true, "component_set_property by nodeName"); - - // 2b. component_get_components with nodeName - const compsByName = await callTool("component_get_components", { nodeName: "EfficiencyTestNode" }); - assert(compsByName.components?.some((c) => c.type === "Label"), "component_get_components by nodeName"); - - // 3. component_set_property with screenshot=true - const setWithSS = await callTool("component_set_property", { - uuid, - componentType: "cc.Label", - property: "fontSize", - value: 48, - screenshot: true, - }); - assert(setWithSS.success === true, "set_property with screenshot"); - assert(!!setWithSS.screenshot?.path || !!setWithSS.screenshotError, "screenshot result or error returned"); - - // 4. node_set_layout — contentSize + widget - const layoutResult = await callTool("node_set_layout", { - uuid, - contentSize: { width: 300, height: 100 }, - widget: { left: 10, right: 10 }, - }); - assert(layoutResult.success === true, "node_set_layout contentSize + widget"); - assert(layoutResult.results?.length >= 2, "node_set_layout multiple results"); - - // 5. node_set_layout by nodeName - const layoutByName = await callTool("node_set_layout", { - nodeName: "EfficiencyTestNode", - anchorPoint: { x: 0, y: 1 }, - }); - assert(layoutByName.success === true, "node_set_layout by nodeName"); - - // 6. node_set_layout with screenshot - const layoutSS = await callTool("node_set_layout", { - uuid, - contentSize: { width: 200, height: 80 }, - screenshot: true, - }); - assert(layoutSS.success === true, "node_set_layout with screenshot"); - assert(!!layoutSS.screenshot?.path || !!layoutSS.screenshotError, "node_set_layout screenshot result"); - - // Cleanup - await callTool("node_delete", { uuid }); -} - -async function testStringifiedArgs() { - console.log("\n── stringified args prevention ──"); - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - if (!canvasUuid) { skip("stringified args (no Canvas)"); return; } - - // 1. node_set_property — value がJSON文字列で届いても動くか - const n1 = await callTool("node_create", { name: "StrArgTest", parent: canvasUuid }); - const setPosStr = await callTool("node_set_property", { - uuid: n1.uuid, property: "position", value: JSON.stringify({ x: 42, y: 0, z: 0 }), - }); - assert(setPosStr.success === true, "node_set_property with stringified value"); - const info1 = await callTool("node_get_info", { uuid: n1.uuid }); - assert(info1.data?.position?.x === 42, "position.x == 42 after stringified set"); - await callTool("node_delete", { uuid: n1.uuid }); - - // 2. node_create_tree — spec がJSON文字列で届いても動くか - const treeStr = await callTool("node_create_tree", { - parent: canvasUuid, - spec: JSON.stringify({ name: "StrTreeTest", components: ["cc.UITransform"] }), - }); - assert(treeStr.success === true, "node_create_tree with stringified spec"); - if (treeStr.data?.uuid) await callTool("node_delete", { uuid: treeStr.data.uuid }); - - // 3. component_set_property — value がJSON文字列で届いても動くか - const n3 = await callTool("node_create", { name: "CompStrTest", parent: canvasUuid, components: ["cc.UITransform"] }); - const setCS = await callTool("component_set_property", { - uuid: n3.uuid, componentType: "cc.UITransform", - property: "contentSize", value: JSON.stringify({ width: 100, height: 200 }), - }); - assert(setCS.success === true, "component_set_property with stringified value"); - - // 4. component_set_property batch — properties がJSON文字列で届いても動くか - const setBatch = await callTool("component_set_property", { - uuid: n3.uuid, componentType: "cc.UITransform", - properties: JSON.stringify([{ property: "contentSize", value: { width: 300, height: 400 } }]), - }); - assert(setBatch.success === true, "component_set_property batch with stringified properties"); - await callTool("node_delete", { uuid: n3.uuid }); -} - -async function testSceneCreate() { - console.log("\n── scene_create (asset-db fallback) ──"); - - // 元のシーンを記憶 - const origScene = await callTool("scene_get_current"); - const origUuid = origScene.uuid || origScene.data?.uuid; - - // scene-2d は dirty untitled 扱いになり preflight でエラーが出うるので force:true でバイパス - // 1. path 指定での作成 - const testPath = `db://assets/test/SceneCreateTest_${Date.now()}.scene`; - const result = await callTool("scene_create", { path: testPath, force: true }); - assert(result.success === true, `scene_create with path (method: ${result.method})`); - - // 元のシーンに戻してクリーンアップ - if (result.success && origUuid) { - await callTool("scene_open", { uuid: origUuid, force: true }); - await new Promise(r => setTimeout(r, 1000)); - await callTool("asset_delete", { path: testPath }); - } - - // 2. path なしでの作成(scene:new-scene または自動 fallback) - const result2 = await callTool("scene_create", { force: true }); - assert(result2.success === true, `scene_create without path (method: ${result2.method || "new-scene"})`); - - // 元のシーンに戻してクリーンアップ - if (result2.success && origUuid) { - await callTool("scene_open", { uuid: origUuid, force: true }); - await new Promise(r => setTimeout(r, 1000)); - // fallback で作成された場合はファイルを削除 - if (result2.path) { - await callTool("asset_delete", { path: result2.path }); - } - } -} - -// ── dialog prevention (scene-switch guard) ── - -async function testDialogPrevention() { - console.log("\n── dialog prevention (scene-switch guard) ──"); - - // 前提: 保存済みシーン (_regression.scene) 上で実行する必要がある。 - // 他のテストが scene-2d 等に切り替えている可能性があるので冒頭で _regression に戻す。 - // _regression に戻せなかった場合は何も副作用を残さず早期 return する。 - const regInfoPre = await callTool("asset_query_uuid", { path: TEST_SCENE_PATH }); - const regUuidPre = regInfoPre.uuid || regInfoPre.data?.uuid; - if (!regUuidPre) { - skip("dialog prevention: _regression.scene not found"); - return; - } - - await callTool("scene_open", { scene: regUuidPre }); - const switchDeadline = Date.now() + 5000; - let switchedToRegression = false; - while (Date.now() < switchDeadline) { - const now = await callTool("scene_get_current"); - if ((now.sceneName || now.data?.sceneName) === "_regression") { - switchedToRegression = true; - break; - } - await new Promise((r) => setTimeout(r, 200)); - } - if (!switchedToRegression) { - skip("dialog prevention: failed to switch to _regression.scene, avoiding side effects"); - return; - } - - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - if (!canvasUuid) { - skip("dialog prevention: Canvas not found"); - return; - } - - // 1. 現在のシーンを dirty にする - const n = await callTool("node_create", { name: `DialogGuardTest_${Date.now()}`, parent: canvasUuid }); - assert(n.success === true, "setup: created temp node (scene becomes dirty)"); - const tempNodeUuid = n.uuid || n.data?.uuid; - - // 2. 現在のシーンが保存済みであることを確認 - const cur = await callTool("scene_get_current"); - const curName = cur.sceneName || cur.data?.sceneName || ""; - const isSaved = !["scene-2d", "scene-3d", "Untitled", "NewScene", ""].includes(curName); - if (!isSaved) { - // setup が正しく機能していれば到達しないはず - console.log(` ⚠️ current scene "${curName}" is untitled, skipping (setup issue?)`); - if (tempNodeUuid) await callTool("node_delete", { uuid: tempNodeUuid }); - return; - } - - // 3. 別シーン (MainScene) に切替 — 保存済みシーンなので preflight が auto-save して成功するはず - const mainInfo = await callTool("asset_query_uuid", { path: "db://assets/MainScene.scene" }); - const mainUuid = mainInfo.uuid || mainInfo.data?.uuid; - if (!mainUuid) { - skip("dialog prevention: MainScene not found"); - if (tempNodeUuid) await callTool("node_delete", { uuid: tempNodeUuid }); - return; - } - - const switchResult = await callTool("scene_open", { scene: mainUuid }); - assert(switchResult.success === true, - `saved dirty → scene_open auto-saves and switches (got: ${JSON.stringify(switchResult).slice(0, 120)})`); - - // 4. スイッチが完了するまで待機 - const SWITCH_TIMEOUT_MS = 5000; - const deadline = Date.now() + SWITCH_TIMEOUT_MS; - let switchedToMain = false; - while (Date.now() < deadline) { - const now = await callTool("scene_get_current"); - if ((now.sceneName || now.data?.sceneName) === "MainScene") { - switchedToMain = true; - break; - } - await new Promise((r) => setTimeout(r, 200)); - } - assert(switchedToMain, "scene actually switched to MainScene after preflight auto-save"); - - // 5. _regression に戻す - const regInfo = await callTool("asset_query_uuid", { path: TEST_SCENE_PATH }); - const regUuid = regInfo.uuid || regInfo.data?.uuid; - if (regUuid) { - await callTool("scene_open", { scene: regUuid }); - // settle - const deadline2 = Date.now() + SWITCH_TIMEOUT_MS; - while (Date.now() < deadline2) { - const now = await callTool("scene_get_current"); - if ((now.sceneName || now.data?.sceneName) === "_regression") break; - await new Promise((r) => setTimeout(r, 200)); - } - } - - // クリーンアップ: 元の temp node は auto-save で _regression.scene に保存されてしまっているので、 - // 新規 load 後は uuid が変わっている。名前ベースで削除を試みる。 - const finalHier = await callTool("scene_get_hierarchy"); - const canvas = finalHier.hierarchy?.find((x) => x.name === "Canvas"); - const leftover = canvas?.children?.find((c) => c.name?.startsWith("DialogGuardTest_")); - if (leftover?.uuid) { - await callTool("node_delete", { uuid: leftover.uuid }); - } - - // 6. untitled + dirty → scene_open でダイアログなしで切替(変更破棄) - // scene_create でuntitledシーンを開き、手動変更を模倣してdirtyにし、scene_openで切替 - const newScene = await callTool("scene_create", {}); - if (newScene.success) { - // 新しいuntitledシーンに切り替わるまで待つ - const createDeadline = Date.now() + 5000; - while (Date.now() < createDeadline) { - const now = await callTool("scene_get_current"); - const name = now.sceneName || now.data?.sceneName || ""; - if (["scene-2d", "scene-3d", "Untitled", "NewScene", ""].includes(name)) break; - await new Promise((r) => setTimeout(r, 200)); - } - - // undo 経由で dirty にする - await callTool("scene_begin_undo"); - const tmpNode = await callTool("node_create", { name: "UntitledDirtyTest" }); - if (tmpNode.uuid) { - await callTool("component_add", { uuid: tmpNode.uuid, componentType: "cc.UITransform" }); - await callTool("component_set_property", { - uuid: tmpNode.uuid, componentType: "cc.UITransform", - property: "contentSize", value: { width: 100, height: 100 }, - }); - } - await callTool("scene_end_undo"); - - const dirtyCheck = await callTool("scene_query_dirty"); - const isDirty = dirtyCheck.dirty || dirtyCheck.data?.dirty; - - if (isDirty) { - // untitled + dirty → scene_open で切替(ダイアログ自動応答で変更破棄) - const untitledSwitch = await callTool("scene_open", { scene: TEST_SCENE_PATH }); - assert(untitledSwitch.success === true, - "untitled dirty → scene_open succeeds (dialog auto-discarded)"); - } else { - // dirty にならなかった場合 — undo 経由でも dirty にならない環境がある - skip("untitled dirty test: scene did not become dirty via MCP (manual edit required)"); - } - - // scene_create で作成されたシーンファイルを削除 - if (newScene.path) { - await callTool("asset_delete", { path: newScene.path }); - } - - // _regression に戻す - if (regUuid) { - await callTool("scene_open", { scene: regUuid }); - const deadline3 = Date.now() + SWITCH_TIMEOUT_MS; - while (Date.now() < deadline3) { - const now = await callTool("scene_get_current"); - if ((now.sceneName || now.data?.sceneName) === "_regression") break; - await new Promise((r) => setTimeout(r, 200)); - } - } - } -} - -async function testUncoveredTools() { - console.log("\n── uncovered tools (minimum 1 call) ──"); - const hier = await callTool("scene_get_hierarchy"); - const canvasUuid = hier.hierarchy?.find((n) => n.name === "Canvas")?.uuid; - - // debug tools (editor-only, no preview required) - const clearRes = await callTool("debug_clear_console"); - assert(clearRes.success === true || !clearRes._rpcError, "debug_clear_console"); - - const extInfo = await callTool("debug_get_extension_info", { name: "cocos-creator-mcp" }); - assert(extInfo.success === true || !extInfo._rpcError, "debug_get_extension_info"); - - const searchLogs = await callTool("debug_search_project_logs", { keyword: "test", count: 5 }); - assert(searchLogs.success === true || !searchLogs._rpcError, "debug_search_project_logs"); - - const devices = await callTool("debug_query_devices"); - assert(devices.success === true || !devices._rpcError, "debug_query_devices"); - - // scene tools (read-only or safe) - const queryComps = await callTool("scene_query_components"); - assert(queryComps.success === true || !queryComps._rpcError, "scene_query_components"); - - const queryBounds = await callTool("scene_query_scene_bounds"); - assert(queryBounds.success === true || !queryBounds._rpcError, "scene_query_scene_bounds"); - - if (canvasUuid) { - const queryComp = await callTool("scene_query_component", { uuid: canvasUuid, componentType: "cc.UITransform" }); - assert(queryComp.success === true || !queryComp._rpcError, "scene_query_component"); - - // scene_set_parent (create two nodes, reparent, cleanup) - const pNode = await callTool("node_create", { name: "ParentTest", parent: canvasUuid }); - const cNode = await callTool("node_create", { name: "ChildTest", parent: canvasUuid }); - if (pNode.uuid && cNode.uuid) { - const sp = await callTool("scene_set_parent", { uuid: cNode.uuid, parentUuid: pNode.uuid }); - assert(sp.success === true || !sp._rpcError, "scene_set_parent"); - await callTool("node_delete", { uuid: pNode.uuid }); - } - - // node_set_layer - const layerNode = await callTool("node_create", { name: "LayerTest", parent: canvasUuid }); - if (layerNode.uuid) { - const sl = await callTool("node_set_layer", { uuid: layerNode.uuid, layer: 1 << 25 }); - assert(sl.success === true || !sl._rpcError, "node_set_layer"); - await callTool("node_delete", { uuid: layerNode.uuid }); - } - - // scene undo (begin + cancel) - const beginUndo = await callTool("scene_begin_undo"); - assert(beginUndo.success === true || !beginUndo._rpcError, "scene_begin_undo"); - const cancelUndo = await callTool("scene_cancel_undo"); - assert(cancelUndo.success === true || !cancelUndo._rpcError, "scene_cancel_undo"); - - // view tools (safe set + restore) - const origGrid = await callTool("view_query_grid_visible"); - const setGrid = await callTool("view_set_grid_visible", { visible: true }); - assert(setGrid.success === true || !setGrid._rpcError, "view_set_grid_visible"); - - const focusRes = await callTool("view_focus_on_node", { uuid: canvasUuid }); - assert(focusRes.success === true || !focusRes._rpcError, "view_focus_on_node"); - - const changeTool = await callTool("view_change_gizmo_tool", { tool: "move" }); - assert(changeTool.success === true || !changeTool._rpcError, "view_change_gizmo_tool"); - - const resetView = await callTool("view_reset"); - assert(resetView.success === true || !resetView._rpcError, "view_reset"); - } - - // project tools - const scripts = await callTool("project_query_scripts"); - assert(scripts.success === true || !scripts._rpcError, "project_query_scripts"); - - // asset tools - const missingAssets = await callTool("asset_query_missing"); - assert(missingAssets.success === true || !missingAssets._rpcError, "asset_query_missing"); - - // preferences tools - const prefGet = await callTool("preferences_get", { protocol: "general", key: "language" }); - assert(prefGet.success === true || !prefGet._rpcError, "preferences_get"); - - // scene_execute_script (read-only expression) - const execScript = await callTool("scene_execute_script", { script: "1 + 1" }); - assert(execScript.success === true || !execScript._rpcError, "scene_execute_script"); - - // debug_execute_script (read-only expression) - const debugExec = await callTool("debug_execute_script", { script: "2 + 2" }); - assert(debugExec.success === true || !debugExec._rpcError, "debug_execute_script"); - - // scene_soft_reload - const softReload = await callTool("scene_soft_reload"); - assert(softReload.success === true || !softReload._rpcError, "scene_soft_reload"); -} - -// テスト失敗等で残った (Missing Node) 等のゴミを一括削除 -async function cleanupOrphanNodes() { - console.log("\n── cleanup: removing orphan nodes ──"); - const hier = await callTool("scene_get_hierarchy"); - if (!hier.success || !Array.isArray(hier.hierarchy)) { - skip("cleanup: hierarchy not available"); - return; - } - const targets = []; - const ORPHAN_PATTERNS = [ - /^TestNode_\d+$/, - /^DialogGuardTest_\d+$/, - /^GuardTest$/, - /^UpdateTestNode$/, - /^ReplaceTest$/, - /^AliasTest$/, - /^ChildTest$/, - ]; - const walk = (nodes) => { - if (!Array.isArray(nodes)) return; - for (const n of nodes) { - if (n.name === "(Missing Node)" || ORPHAN_PATTERNS.some((re) => re.test(n.name))) { - targets.push(n.uuid); - } - if (n.children) walk(n.children); - } - }; - walk(hier.hierarchy); - if (targets.length === 0) { - console.log(" ✅ no orphan nodes found"); - passed++; - return; - } - let deleted = 0; - for (const uuid of targets) { - const r = await callTool("node_delete", { uuid }); - if (r.success) deleted++; - } - console.log(` ✅ cleaned up ${deleted}/${targets.length} orphan nodes`); - passed++; -} - -// ── OAuth dummy endpoints (v1.11.0 — Claude Code VSCode HTTP OAuth bug 回避用) ── - -async function testOAuthEndpoints() { - console.log("\n── OAuth dummy endpoints ──"); - - // RFC 9728 Protected Resource Metadata - { - const res = await fetch(`${BASE}/.well-known/oauth-protected-resource`); - assert(res.status === 200, "protected-resource status 200"); - const data = await res.json(); - assert(data.resource === `${BASE}/mcp`, `resource: ${data.resource}`); - assert(Array.isArray(data.authorization_servers) && data.authorization_servers.length > 0, - "authorization_servers non-empty"); - assert(Array.isArray(data.bearer_methods_supported) && data.bearer_methods_supported.includes("header"), - "bearer_methods_supported includes header"); - } - - // RFC 8414 Authorization Server Metadata - { - const res = await fetch(`${BASE}/.well-known/oauth-authorization-server`); - assert(res.status === 200, "authorization-server status 200"); - const data = await res.json(); - assert(data.issuer === BASE, `issuer: ${data.issuer}`); - assert(data.authorization_endpoint === `${BASE}/oauth/authorize`, - `authorization_endpoint: ${data.authorization_endpoint}`); - assert(data.token_endpoint === `${BASE}/oauth/token`, - `token_endpoint: ${data.token_endpoint}`); - assert(data.registration_endpoint === `${BASE}/oauth/register`, - `registration_endpoint: ${data.registration_endpoint}`); - assert(Array.isArray(data.grant_types_supported) && data.grant_types_supported.includes("authorization_code"), - "grant_types_supported includes authorization_code"); - assert(Array.isArray(data.code_challenge_methods_supported) && data.code_challenge_methods_supported.includes("S256"), - "code_challenge_methods_supported includes S256"); - } - - // RFC 7591 Dynamic Client Registration - { - const res = await fetch(`${BASE}/oauth/register`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - client_name: "regression-test", - redirect_uris: ["http://localhost:54321/callback"], - }), - }); - assert(res.status === 200, "register status 200"); - const data = await res.json(); - assert(typeof data.client_id === "string" && data.client_id.length > 0, - `client_id: ${data.client_id}`); - assert(data.token_endpoint_auth_method === "none", - "token_endpoint_auth_method: none"); - assert(Array.isArray(data.redirect_uris) && data.redirect_uris[0] === "http://localhost:54321/callback", - "redirect_uris echoed"); - } - - // Authorization endpoint — expect 302 with code and state - { - const redirectUri = "http://localhost:54321/cb"; - const state = "test-state-xyz"; - const qs = new URLSearchParams({ - response_type: "code", - client_id: "regtest", - redirect_uri: redirectUri, - state, - code_challenge: "dummy", - code_challenge_method: "S256", - }); - const res = await fetch(`${BASE}/oauth/authorize?${qs}`, { redirect: "manual" }); - assert(res.status === 302, `authorize status 302 (got ${res.status})`); - const location = res.headers.get("location") || ""; - assert(location.startsWith(redirectUri), `redirect back to redirect_uri: ${location}`); - assert(/[?&]code=[^&]+/.test(location), "code param present"); - assert(location.includes(`state=${encodeURIComponent(state)}`), "state param preserved"); - } - - // Authorization endpoint — missing redirect_uri should 400 - { - const res = await fetch(`${BASE}/oauth/authorize?response_type=code&client_id=x`, { redirect: "manual" }); - assert(res.status === 400, `authorize without redirect_uri → 400 (got ${res.status})`); - } - - // Token endpoint - { - const res = await fetch(`${BASE}/oauth/token`, { - method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, - body: "grant_type=authorization_code&code=abc&redirect_uri=http%3A%2F%2Flocalhost%3A54321%2Fcb", - }); - assert(res.status === 200, "token status 200"); - const data = await res.json(); - assert(typeof data.access_token === "string" && data.access_token.length > 0, - `access_token: ${data.access_token}`); - assert(data.token_type === "Bearer", `token_type: ${data.token_type}`); - assert(typeof data.expires_in === "number" && data.expires_in > 0, - `expires_in: ${data.expires_in}`); - } - - // 既存 /mcp エンドポイントが Authorization ヘッダーなしでも従来通り処理されること - { - const res = await fetch(`${BASE}/mcp`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ jsonrpc: "2.0", id: 9999, method: "initialize", params: {} }), - }); - assert(res.status === 200, "/mcp without auth still 200 (no regression)"); - const data = await res.json(); - assert(data.result?.serverInfo?.name === "cocos-creator-mcp", - "/mcp without auth returns initialize result"); - } -} - -// ── stdio bridge (client/stdio-bridge.js) ── - -function runStdioBridge(requests) { - return new Promise((resolve, reject) => { - const bridgePath = path.resolve(__dirname, "..", "client", "stdio-bridge.js"); - const child = spawn("node", [bridgePath], { - stdio: ["pipe", "pipe", "pipe"], - env: { ...process.env, COCOS_MCP_URL: `${BASE}/mcp` }, - }); - - const stdoutChunks = []; - const stderrChunks = []; - child.stdout.on("data", (c) => stdoutChunks.push(c)); - child.stderr.on("data", (c) => stderrChunks.push(c)); - - child.on("error", (e) => reject(e)); - child.on("close", (code) => { - const stdout = Buffer.concat(stdoutChunks).toString("utf8"); - const stderr = Buffer.concat(stderrChunks).toString("utf8"); - const lines = stdout.split("\n").filter((l) => l.trim().length > 0); - const responses = []; - for (const line of lines) { - try { responses.push(JSON.parse(line)); } - catch { /* skip non-JSON */ } - } - resolve({ code, responses, stderr }); - }); - - for (const req of requests) { - child.stdin.write(JSON.stringify(req) + "\n"); - } - child.stdin.end(); - }); -} - -async function testStdioBridge() { - console.log("\n── stdio bridge (client/stdio-bridge.js) ──"); - - // 1. initialize + tools/list — 基本動作 - { - const { code, responses, stderr } = await runStdioBridge([ - { jsonrpc: "2.0", id: 1, method: "initialize", params: { protocolVersion: "2024-11-05", capabilities: {} } }, - { jsonrpc: "2.0", id: 2, method: "tools/list" }, - ]); - assert(code === 0, `bridge exit code 0 (got ${code})`); - assert(responses.length === 2, `bridge returned 2 responses (got ${responses.length})`); - const init = responses.find((r) => r.id === 1); - assert(init?.result?.serverInfo?.name === "cocos-creator-mcp", - "bridge: initialize result"); - const toolsList = responses.find((r) => r.id === 2); - const tools = toolsList?.result?.tools || []; - assert(tools.length >= 148, `bridge: tools/list count >= 148 (got ${tools.length})`); - assert(stderr.includes("session established"), - "bridge: session established logged"); - } - - // 2. tools/call を通した実ツール呼び出し - { - const { responses } = await runStdioBridge([ - { jsonrpc: "2.0", id: 1, method: "initialize", params: {} }, - { jsonrpc: "2.0", id: 2, method: "tools/call", params: { name: "scene_get_current", arguments: {} } }, - ]); - const toolRes = responses.find((r) => r.id === 2); - assert(!!toolRes?.result, "bridge: tools/call returned result"); - const content = toolRes?.result?.content?.[0]?.text; - const parsed = content ? JSON.parse(content) : null; - assert(parsed?.success === true, "bridge: scene_get_current success"); - } - - // 3. 不正 JSON を流したとき parse error を返す - { - const { responses } = await new Promise((resolve, reject) => { - const bridgePath = path.resolve(__dirname, "..", "client", "stdio-bridge.js"); - const child = spawn("node", [bridgePath], { - stdio: ["pipe", "pipe", "pipe"], - env: { ...process.env, COCOS_MCP_URL: `${BASE}/mcp` }, - }); - const out = []; - child.stdout.on("data", (c) => out.push(c)); - child.on("close", () => { - const lines = Buffer.concat(out).toString("utf8").split("\n").filter(Boolean); - const parsed = []; - for (const l of lines) try { parsed.push(JSON.parse(l)); } catch {} - resolve({ responses: parsed }); - }); - child.on("error", reject); - child.stdin.write("this is not json\n"); - child.stdin.end(); - }); - const err = responses[0]; - assert(err?.error?.code === -32700, `bridge: parse error code -32700 (got ${err?.error?.code})`); - } - - // 4. COCOS_MCP_URL 環境変数で向き先を上書きできる(到達不能 URL でエラーが返る) - { - const bridgePath = path.resolve(__dirname, "..", "client", "stdio-bridge.js"); - const { responses } = await new Promise((resolve, reject) => { - const child = spawn("node", [bridgePath], { - stdio: ["pipe", "pipe", "pipe"], - env: { ...process.env, COCOS_MCP_URL: "http://127.0.0.1:9/mcp" }, // discard port - }); - const out = []; - child.stdout.on("data", (c) => out.push(c)); - child.on("close", () => { - const lines = Buffer.concat(out).toString("utf8").split("\n").filter(Boolean); - const parsed = []; - for (const l of lines) try { parsed.push(JSON.parse(l)); } catch {} - resolve({ responses: parsed }); - }); - child.on("error", reject); - child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: 1, method: "initialize", params: {} }) + "\n"); - child.stdin.end(); - }); - const err = responses[0]; - assert(err?.error?.code === -32603, `bridge: HTTP error → -32603 (got ${err?.error?.code})`); - } -} - -// ── test scene setup ── - -/** - * 回帰テスト専用シーンを用意して開く。 - * - * - CC 直後(現在シーンが untitled かつ clean)ならここから scene_open を呼んで遷移 - * - 既に untitled dirty 状態なら abort - * - 既に _regression.scene 上なら何もしない - * - * 目的: テスト中に scene_create / scene_open / prefab_create 等で dirty state が - * CC 標準の「Save changes?」ダイアログを出す問題を回避するため、保存済みシーンに移動する。 - */ -const TEST_SCENE_PATH = "db://assets/test/_regression.scene"; -const SOURCE_SCENE_PATH = "db://assets/MainScene.scene"; // クローン元のテンプレート - -async function ensureCleanTestScene() { - console.log("\n── setup: ensure clean test scene ──"); - - const cur = await callTool("scene_get_current"); - const curName = cur.sceneName || cur.data?.sceneName || ""; - const isUntitled = ["scene-2d", "scene-3d", "Untitled", "NewScene", ""].includes(curName); - - // 既にテスト用シーン上ならそのまま - if (curName === "_regression") { - console.log(` ✅ already on _regression.scene`); - passed++; - return true; - } - - // untitled シーン上の場合: dirty flag が立っていると scene_open で CC 標準の - // 「Save changes?」ダイアログが出て MCP が固まる。ノードを削除しても CC の dirty - // 履歴は消えないので、この状態では CC 再起動以外に回復手段がない。素直に abort する。 - if (isUntitled) { - const dirtyCheck = await callTool("scene_query_dirty"); - const isDirty = dirtyCheck.dirty === true || dirtyCheck.data?.dirty === true; - if (isDirty) { - console.error( - `\n❌ Cannot start regression test: current scene "${curName}" is untitled ` + - `and has been modified (dirty flag set). CC cannot save an untitled scene ` + - `without a modal dialog, and deleting leftover nodes does not clear the dirty ` + - `flag.\n Please restart CocosCreator and run the test again.\n` - ); - failed++; - return false; - } - } - - // _regression.scene の準備: - // scene_create の asset-db fallback が生成する最小 JSON は CC が拒否するため、 - // 既存の MainScene.scene を asset_copy で複製する方式にしている。 - let info = await callTool("asset_query_uuid", { path: TEST_SCENE_PATH }).catch(() => ({})); - let sceneUuid = info.uuid || info.data?.uuid; - - if (!sceneUuid) { - console.log(` cloning ${SOURCE_SCENE_PATH} → ${TEST_SCENE_PATH}...`); - - // ソースの存在確認 - const srcInfo = await callTool("asset_query_uuid", { path: SOURCE_SCENE_PATH }).catch(() => ({})); - const srcUuid = srcInfo.uuid || srcInfo.data?.uuid; - if (!srcUuid) { - console.error(` ❌ source scene ${SOURCE_SCENE_PATH} not found; cannot create test scene`); - failed++; - return false; - } - - const copied = await callTool("asset_copy", { source: SOURCE_SCENE_PATH, destination: TEST_SCENE_PATH }); - if (!copied.success) { - console.error(` ❌ failed to copy test scene: ${JSON.stringify(copied).slice(0, 200)}`); - failed++; - return false; - } - - info = await callTool("asset_query_uuid", { path: TEST_SCENE_PATH }).catch(() => ({})); - sceneUuid = info.uuid || info.data?.uuid; - } - - if (!sceneUuid) { - console.error(` ❌ could not resolve UUID for ${TEST_SCENE_PATH}`); - failed++; - return false; - } - - // scene_open は非同期ロードで response success が実際の切替完了を保証しない。 - // 切替が観測できるまで scene_open + scene_get_current の polling を繰り返す。 - // ここまで来ている時点で current scene は clean が保証されているので force は不要。 - const SWITCH_TIMEOUT_MS = 15000; - const POLL_INTERVAL_MS = 300; - const deadline = Date.now() + SWITCH_TIMEOUT_MS; - let switched = false; - let lastError = ""; - let openAttempts = 0; - - while (Date.now() < deadline) { - const now = await callTool("scene_get_current"); - const name = now.sceneName || now.data?.sceneName || ""; - if (name === "_regression") { - switched = true; - break; - } - // 切替されていなければ scene_open を再試行(idempotent) - openAttempts++; - const opened = await callTool("scene_open", { scene: sceneUuid }); - if (opened.success !== true) { - lastError = JSON.stringify(opened).slice(0, 200); - } - await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS)); - } - - if (!switched) { - console.error( - ` ❌ scene_open did not switch to _regression within ${SWITCH_TIMEOUT_MS}ms ` + - `(attempts: ${openAttempts}, last error: ${lastError || "none"})` - ); - failed++; - return false; - } - - console.log(` ✅ opened and verified ${TEST_SCENE_PATH} (${openAttempts} open attempt(s))`); - passed++; - return true; -} - -// ── runner ── - -async function main() { - console.log(`\n🔧 Cocos Creator MCP v1.0 — Regression Test`); - console.log(` Server: ${BASE}/mcp\n`); - - try { - await fetch(`${BASE}/health`); - } catch { - console.error(`❌ Server not reachable at ${BASE}. Is the MCP server running?`); - process.exit(1); - } - - await testHealth(); - await testInitialize(); - await testToolsList(); - await testOAuthEndpoints(); - await testStdioBridge(); - - // scene 依存テストの前にテスト専用シーンを用意 - const sceneReady = await ensureCleanTestScene(); - if (!sceneReady) { - console.log(`\n${"═".repeat(40)}`); - console.log(` Setup failed — aborting scene-dependent tests.`); - console.log(` Results: ${passed} passed, ${failed} failed, ${skipped} skipped`); - console.log(`${"═".repeat(40)}\n`); - process.exit(1); - } - await testSceneTools(); - await testNodeCrud(); - await testComponentTools(); - await testPrefabTools(); - await testProjectTools(); - await testAssetTools(); - await testSceneAdvancedTools(); - await testSceneViewTools(); - await testDebugTools(); - await testPreferencesTools(); - await testServerTools(); - await testBuilderTools(); - await testNewSceneAdvancedTools(); - await testNewViewTools(); - await testComponentAdvanced(); - await testProjectAdvanced(); - await testReferenceImageTools(); - await testV13Regressions(); - await testV15NewTools(); - await testNewEditorAPIs(); - await testV16NewTools(); - await testV18NewTools(); - await testV111NewTools(); - await testPrefabEfficiency(); - await testStringifiedArgs(); - await testSceneCreate(); - await testDialogPrevention(); - await testUncoveredTools(); - await cleanupOrphanNodes(); - - console.log(`\n${"═".repeat(40)}`); - console.log(` Results: ${passed} passed, ${failed} failed, ${skipped} skipped`); - console.log(`${"═".repeat(40)}\n`); - - process.exit(failed > 0 ? 1 : 0); -} - -main().catch((e) => { - console.error("Fatal:", e); - process.exit(1); -}); diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/test_standalone_server.js b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/test_standalone_server.js deleted file mode 100644 index e10ec1d..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/test_standalone_server.js +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env node -/** - * 独立测试服务器 - 模拟Cocos Creator MCP服务器的最小化实现 - * 用于测试stdio-bridge.js的基本功能 - */ - -const http = require('http'); - -const PORT = 3000; -const MCP_PROTOCOL_VERSION = "2024-11-05"; - -// 模拟的工具列表(从README中提取的164个工具中的几个示例) -const MOCK_TOOLS = [ - { - name: "scene_get_hierarchy", - description: "Get the node tree (with optional component info)", - inputSchema: { - type: "object", - properties: { - includeComponents: { type: "boolean", description: "Include component info in hierarchy" } - } - } - }, - { - name: "node_create", - description: "Create node (with optional components)", - inputSchema: { - type: "object", - properties: { - name: { type: "string", description: "Node name" }, - parentUuid: { type: "string", description: "Parent node UUID" } - }, - required: ["name"] - } - }, - { - name: "scene_save", - description: "Save the current scene", - inputSchema: { - type: "object", - properties: {} - } - } -]; - -const server = http.createServer((req, res) => { - if (req.method === 'POST' && req.url === '/mcp') { - let body = ''; - req.on('data', chunk => body += chunk); - req.on('end', () => { - try { - const request = JSON.parse(body); - console.log('[TEST SERVER] Received:', request.method); - - const response = { - jsonrpc: "2.0", - id: request.id - }; - - switch (request.method) { - case 'initialize': - response.result = { - protocolVersion: MCP_PROTOCOL_VERSION, - capabilities: { - tools: {} - }, - serverInfo: { - name: "cocos-creator-mcp", - version: "1.14.0" - } - }; - break; - - case 'tools/list': - response.result = { - tools: MOCK_TOOLS - }; - break; - - case 'tools/call': - const toolName = request.params.name; - response.result = { - content: [{ - type: "text", - text: JSON.stringify({ - success: true, - tool: toolName, - message: `Called ${toolName} successfully` - }) - }] - }; - break; - - default: - response.error = { - code: -32601, - message: "Method not found" - }; - } - - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify(response)); - } catch (e) { - res.statusCode = 400; - res.end(JSON.stringify({ error: e.message })); - } - }); - } else if (req.method === 'GET' && req.url === '/health') { - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ status: 'ok', tools: MOCK_TOOLS.length })); - } else { - res.statusCode = 404; - res.end('Not found'); - } -}); - -server.listen(PORT, '127.0.0.1', () => { - console.log(`[TEST SERVER] Started on http://127.0.0.1:${PORT}/mcp`); - console.log(`[TEST SERVER] Health check: http://127.0.0.1:${PORT}/health`); -}); - -// 优雅关闭 -process.on('SIGINT', () => { - console.log('[TEST SERVER] Shutting down...'); - server.close(() => { - process.exit(0); - }); -}); \ No newline at end of file diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/tsconfig.json b/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/tsconfig.json deleted file mode 100644 index 2810778..0000000 --- a/cocoscreator_projects/YouleNexus/extensions/cocos-creator-mcp/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2017", - "module": "CommonJS", - "moduleResolution": "node", - "inlineSourceMap": true, - "inlineSources": true, - "esModuleInterop": true, - "skipLibCheck": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "outDir": "./dist", - "rootDir": "./source", - "types": [ - "node", - "@cocos/creator-types/editor" - ] - }, - "exclude": ["client", "test", "dist"] -} diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/INSTALL.md b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/INSTALL.md new file mode 100644 index 0000000..b30e5fa --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/INSTALL.md @@ -0,0 +1,44 @@ +# Cocos Creator MCP Server v1.7.9 + +## 安装说明 + +1. 将整个文件夹复制到 Cocos Creator 项目的 `extensions` 目录 +2. 重启 Cocos Creator 或刷新扩展 (Ctrl+R / Cmd+R) +3. 在扩展菜单中启用 "Cocos MCP Server" +4. 打开扩展面板配置服务器设置 + +## 使用配置 + +### Claude Desktop +```json +{ + "mcpServers": { + "cocos-creator": { + "type": "http", + "url": "http://127.0.0.1:3000/mcp" + } + } +} +``` + +### Cursor +```json +{ + "mcpServers": { + "cocos-creator": { + "url": "http://127.0.0.1:3000/mcp" + } + } +} +``` + +## 技术支持 + +- 📧 邮箱: a549623165@gmail.com +- 💬 微信: lidaxian-AI | 客服微信: vberai-support +- 🐙 GitHub: https://github.com/DaxianLee +- 🛒 购买/续费: https://www.vberai.com/game-engines/cocos + +--- +构建信息: v1.7.9 (production) - 2026-07-18 16:06:14 +作者: 李大仙 (LiDaxian) diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/ar.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/ar.js new file mode 100644 index 0000000..a54afd9 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/ar.js @@ -0,0 +1,243 @@ +"use strict"; +module.exports = { + "extension_name": "خادم Cocos MCP", + "description": "خادم AI MCP لـ Cocos Creator 3.8", + "panel_title": "خادم MCP", + "open_panel": "فتح لوحة MCP", + "start_server": "بدء الخادم", + "stop_server": "إيقاف الخادم", + "server_status": "حالة الخادم", + "port": "المنفذ", + "settings": "الإعدادات", + "connected": "متصل", + "disconnected": "غير متصل", + "server_running": "الخادم يعمل على المنفذ {0}", + "server_stopped": "تم إيقاف الخادم", + "auto_start": "بدء تلقائي", + "debug_log": "سجل التصحيح", + "max_connections": "الحد الأقصى للاتصالات", + "connection_info": "معلومات الاتصال", + "http_url": "رابط HTTP", + "copy": "نسخ", + "save_settings": "حفظ", + "settings_saved": "تم حفظ الإعدادات بنجاح", + "server_started": "تم بدء خادم MCP", + "server_stopped_msg": "تم إيقاف خادم MCP", + "failed_to_start": "فشل في بدء الخادم", + "failed_to_stop": "فشل في إيقاف الخادم", + "failed_to_save": "فشل في حفظ الإعدادات", + "url_copied": "تم نسخ رابط HTTP إلى الحافظة", + "tool_manager": "مدير الأدوات", + "open_tool_manager": "فتح مدير الأدوات", + "create_config": "إنشاء تكوين", + "edit_config": "تعديل التكوين", + "delete_config": "حذف التكوين", + "import_config": "استيراد التكوين", + "export_config": "تصدير التكوين", + "apply_config": "تطبيق التكوين", + "select_all": "تحديد الكل", + "deselect_all": "إلغاء تحديد الكل", + "save_changes": "حفظ التغييرات", + "config_name": "اسم التكوين", + "config_description": "وصف التكوين", + "current_config": "التكوين الحالي", + "tool_management": "إدارة الأدوات", + "total_tools": "إجمالي الأدوات", + "enabled_tools": "مفعّلة", + "disabled_tools": "معطّلة", + "no_config_selected": "لم يتم تحديد تكوين", + "select_config_first": "يرجى تحديد أو إنشاء تكوين أولاً", + "config_created": "تم إنشاء التكوين بنجاح", + "config_updated": "تم تحديث التكوين بنجاح", + "config_deleted": "تم حذف التكوين بنجاح", + "config_applied": "تم تطبيق التكوين بنجاح", + "config_exported": "تم تصدير التكوين بنجاح", + "config_imported": "تم استيراد التكوين بنجاح", + "confirm_delete": "تأكيد الحذف", + "delete_config_confirm": "هل أنت متأكد من حذف التكوين \"{0}\"؟ لا يمكن التراجع عن هذا الإجراء.", + "max_config_slots_reached": "تم الوصول إلى الحد الأقصى لعدد التكوينات ({0})", + "invalid_config_format": "تنسيق التكوين غير صالح", + "invalid_json_format": "تنسيق JSON أو بنية التكوين غير صالحة", + "server_tab": "الخادم", + "tools_tab": "إدارة الأدوات", + "available_tools": "الأدوات المتاحة", + "scene_tools": "أدوات المشهد", + "node_tools": "أدوات العقد", + "component_tools": "أدوات المكونات", + "prefab_tools": "أدوات القوالب", + "project_tools": "أدوات المشروع", + "debug_tools": "أدوات التصحيح", + "preferences_tools": "التفضيلات", + "server_tools": "أدوات الخادم", + "broadcast_tools": "أدوات البث", + "scene_advanced_tools": "أدوات المشهد المتقدمة", + "scene_view_tools": "أدوات عرض المشهد", + "reference_image_tools": "أدوات الصور المرجعية", + "asset_advanced_tools": "أدوات الأصول المتقدمة", + "validation_tools": "أدوات التحقق", + "brand_name": "LiDaxian MCP Series", + "brand_subtitle": "LiDaxian MCP Series", + "contact_email": "البريد الإلكتروني", + "contact_wechat": "WeChat", + "contact_github": "GitHub", + "machine_id_label": "رمز الجهاز", + "purchase_renew": "شراء / تجديد", + "purchase_url": "https://www.vberai.com/game-engines/cocos", + "auth_title": "اختر طريقة تسجيل الدخول", + "auth_subtitle": "أدخل بريدك الإلكتروني ورمز التفعيل", + "email_label": "عنوان البريد الإلكتروني", + "activation_code_label": "رمز التفعيل", + "email_placeholder": "أدخل البريد الإلكتروني", + "code_placeholder": "أدخل الرمز (XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)", + "activate_button": "تفعيل", + "deactivate_button": "إلغاء التفعيل", + "activating": "جارٍ التفعيل...", + "deactivating": "جارٍ إلغاء التفعيل...", + "license_status": "حالة الترخيص", + "license_type": "نوع الترخيص", + "expiry_date": "تاريخ الانتهاء", + "days_remaining": "الأيام المتبقية", + "days_unit": "ي", + "permanent": "دائم", + "expiry_warning_title": "الترخيص على وشك الانتهاء", + "expiry_warning_message": "سينتهي ترخيصك خلال {0} أيام. يرجى التجديد لتجنب انقطاع الخدمة.", + "renew_now": "تجديد الآن", + "i_understand": "فهمت", + "activation_success": "تم تفعيل الترخيص بنجاح!", + "activation_failed": "فشل التفعيل: {0}", + "invalid_code": "تنسيق رمز التفعيل غير صالح", + "purchase_license": "شراء ترخيص", + "renew_license": "تجديد الترخيص", + "contact_support": "الدعم", + "language_switch": "العربية", + "version_label": "الإصدار", + "license_required": "مطلوب ترخيص لبدء الخادم", + "license_check_failed": "فشل التحقق من الترخيص", + + "status_stopped": "متوقف", + "status_running": "يعمل", + "status_error": "فشل الاتصال", + "config_tab": "إعداد سريع", + "status_label": "الحالة", + "connections_count": "الاتصالات", + "transport_protocol": "بروتوكول النقل", + "transport_type": "نوع النقل", + "mcp_url": "رابط MCP", + "port_fallback_title": "المنفذ {0} غير متاح — تم التبديل إلى {1}", + "port_fallback_reason": "المنفذ {0} محجوز من قبل النظام أو محظور بواسطة جدار الحماية/برنامج مكافحة الفيروسات (شائع في نظام Windows المثبت عليه Hyper-V أو WSL2 أو Docker). الخادم يعمل بشكل طبيعي على المنفذ الجديد.", + "port_fallback_resynced": "تم تحديث إعدادات العملاء التالية: {0}. أعد تشغيلها ليصبح المنفذ الجديد ساري المفعول.", + "port_fallback_no_clients": "إذا سبق أن أعددت عملاء الذكاء الاصطناعي، فأعد إعدادهم على المنفذ الجديد من خلال الإعداد بنقرة واحدة.", + "port_fallback_dismiss": "حسنًا", + "port_label": "المنفذ", + "auto_start_label": "بدء تلقائي", + "debug_log_label": "سجل التصحيح", + "max_connections_label": "الحد الأقصى للاتصالات", + "server_settings_title": "إعدادات الخادم", + "server_status_title": "حالة الخادم", + "connection_info_title": "معلومات الاتصال", + "tools_count": "{0} أدوات", + "tools_stats": "{0} أدوات ({1} مفعّلة / {2} معطّلة)", + "quick_config_title": "إعداد سريع لعملاء MCP", + "quick_config_desc": "تكوين خادم MCP تلقائيًا في محررات AI وأدوات CLI", + "server_info": "معلومات الخادم", + "server_name_label": "اسم الخادم", + "server_address": "عنوان الخادم", + "timeout_ms": "مهلة الانتظار (مللي ثانية)", + "auto_config_title": "إعداد تلقائي (IDE + Codex CLI)", + "add_all": "إضافة الكل", + "remove_all": "إزالة الكل", + "auto_config_note": "انقر لتعديل ملفات التكوين تلقائيًا (Cursor, Windsurf, Trae, Codex CLI)", + "configured": "مُعدّ", + "not_configured": "غير مُعدّ", + "auto_config_btn": "إعداد تلقائي", + "delete_btn": "حذف", + "copy_config": "نسخ التكوين", + "open_config": "فتح التكوين", + "manual_config_title": "إعداد يدوي (Claude CLI + Gemini CLI)", + "refresh_commands": "تحديث الأوامر", + "manual_config_note": "أدوات CLI التالية تتطلب تنفيذًا يدويًا في الطرفية", + "config_scope": "نطاق التكوين:", + "scope_user": "مستوى المستخدم", + "scope_project": "مستوى المشروع", + "operation_log": "سجل العمليات", + "no_logs": "لا توجد سجلات", + "copyright_line": "© 2025 LiDaxian - Cocos Creator MCP Server v{0}", + "protocol_line": "مبني على بروتوكول Streamable HTTP (MCP 2025-03-26)", + "tools_copyright_line1": "نظام الأدوات - 13 أداة intent-level، أكثر من 150 عملية محرر، توزيع أكواد الإجراءات", + "config_copyright_line1": "إعداد سريع - يدعم Cursor, Windsurf, Trae, Codex CLI, Claude CLI, Gemini CLI", + "email_required": "يرجى إدخال البريد الإلكتروني ورمز التفعيل", + "permanent_label": "دائم", + "logout_button": "تسجيل الخروج", + "invite_code_label": "رمز الدعوة", + "copy_button": "نسخ", + "change_code_button": "تغيير الرمز", + "change_code_title": "تغيير رمز التفعيل", + "change_code_desc": "أدخل رمز التفعيل الجديد. سيتم إلغاء ربط الجهاز الحالي أولاً.", + "confirm_change": "تأكيد", + "cancel": "إلغاء", + "code_required": "يرجى إدخال رمز التفعيل", + "expiry_inline_warning": "ينتهي قريبًا!", + "store_purchase_hint": "إذا اشتريت هذا الإضافة من Cocos Store، يرجى الاتصال بي للحصول على رمز التفعيل.", + "license_expired_title": "انتهت صلاحية الترخيص", + "license_expired_message": "انتهت صلاحية ترخيصك ولا يمكن بدء الخادم. يرجى التجديد أو إعادة التفعيل للمتابعة.", + "go_to_login": "الذهاب لتسجيل الدخول", + + "log_load_config_failed": "فشل في تحميل حالة التكوين: {0}", + "log_gen_cli_failed": "فشل في إنشاء أوامر CLI: {0}", + "log_add_client_failed": "فشل في الإضافة إلى {0}: {1}", + "log_remove_client_failed": "فشل في الإزالة من {0}: {1}", + "quick_config_dialog_title": "تكوين بنقرة واحدة", + "config_operation_failed": "فشلت عملية التكوين", + "config_file_not_found_changed_path": "فشل التكوين: لم يتم العثور على ملف التكوين. ربما تم تثبيت {0} في مسار مخصص.", + "log_batch_add_failed": "فشل الإضافة الجماعية: {0}", + "log_batch_remove_failed": "فشل الإزالة الجماعية: {0}", + "log_copied_config": "تم نسخ تكوين {0} إلى الحافظة", + "log_gen_config_failed": "فشل في إنشاء التكوين", + "log_copy_config_failed": "فشل في نسخ التكوين: {0}", + "log_copied_command": "تم نسخ أمر {0} إلى الحافظة", + "log_copy_command_failed": "فشل في نسخ الأمر: {0}", + "log_opened_config_file": "تم فتح ملف التكوين: {0}", + "log_open_config_failed": "فشل في فتح ملف التكوين", + "log_open_config_error": "فشل في فتح ملف التكوين: {0}", + + "update_available_title": "تحديث متاح", + "update_new_version": "الإصدار الجديد v{0} متاح!", + "update_now": "التحديث الآن", + "update_not_now": "ليس الآن", + "update_skip_version": "تخطي v{0}", + "check_for_updates": "التحقق من التحديثات", + "update_checking": "جارٍ التحقق...", + "update_no_update": "أنت تستخدم أحدث إصدار", + "update_urgency_critical": "تحديث عاجل", + "update_urgency_high": "تحديث مهم", + "update_current_version": "الإصدار الحالي: v{0}", + "update_changelog": "سجل التغييرات", + "auth_choose_subtitle": "اختر طريقة تسجيل الدخول للمتابعة", + "update_changelog_empty": "لم يتم توفير ملاحظات إصدار لهذه النسخة.", + "subscription_login_title": "حساب اشتراك VberAI", + "subscription_password_label": "كلمة المرور", + "subscription_password_placeholder": "أدخل كلمة المرور", + "subscription_login_button": "تسجيل الدخول باستخدام VberAI", + "subscription_google_button": "تسجيل الدخول باستخدام Google", + "subscription_google_pending": "تم فتح المتصفح. أكمل تفويض Google ثم عد إلى Cocos Creator.", + "subscription_google_pending_button": "في انتظار تفويض Google...", + "subscription_register_button": "إنشاء حساب VberAI", + "subscription_logging_in": "جارٍ تسجيل الدخول...", + "subscription_credentials_required": "يرجى إدخال البريد الإلكتروني وكلمة المرور", + "subscription_login_success": "تم تسجيل الدخول إلى حساب الاشتراك", + "subscription_login_failed": "فشل تسجيل الدخول إلى الاشتراك", + "subscription_not_entitled": "اشتراكك الحالي لا يتضمن Cocos 3.x MCP. قم بالترقية للمتابعة.", + "subscription_upgrade_to": "قم بالترقية إلى «{0}» لاستخدام هذا المنتج", + "subscription_upgrade_action": "ترقية الاشتراك", + "subscription_logged_in": "الحساب المسجّل دخوله", + "subscription_choose_button": "حساب اشتراك VberAI", + "license_choose_button": "رمز التفعيل", + "subscription_method_label": "اشتراك", + "back_to_login_choices": "← اختر طريقة تسجيل دخول أخرى", + "subscription_signed_out_other_device": "تم تسجيل الخروج: تم استخدام حسابك على جهاز آخر.", + "subscription_google_cancelled": "تم إلغاء تسجيل الدخول عبر Google.", + "subscription_request_timeout": "انتهت مهلة الطلب. تحقق من شبكتك وحاول مرة أخرى.", + "subscription_network_error": "فشل الاتصال بالشبكة. تحقق من شبكتك وحاول مرة أخرى.", + "subscription_server_error": "أرجع الخادم استجابة غير صالحة. حاول مرة أخرى لاحقًا.", + "subscription_google_authorization_failed": "لم يكتمل تفويض Google. يرجى المحاولة مرة أخرى." +}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/de.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/de.js new file mode 100644 index 0000000..6e735ea --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/de.js @@ -0,0 +1,243 @@ +"use strict"; +module.exports = { + "extension_name": "Cocos MCP Server", + "description": "AI MCP Server für Cocos Creator 3.8", + "panel_title": "MCP Server", + "open_panel": "MCP-Panel öffnen", + "start_server": "Server starten", + "stop_server": "Server stoppen", + "server_status": "Serverstatus", + "port": "Port", + "settings": "Einstellungen", + "connected": "Verbunden", + "disconnected": "Getrennt", + "server_running": "Server läuft auf Port {0}", + "server_stopped": "Server wurde gestoppt", + "auto_start": "Autostart", + "debug_log": "Debug-Protokoll", + "max_connections": "Max. Verbindungen", + "connection_info": "Verbindungsinfo", + "http_url": "HTTP-URL", + "copy": "Kopieren", + "save_settings": "Speichern", + "settings_saved": "Einstellungen erfolgreich gespeichert", + "server_started": "MCP Server gestartet", + "server_stopped_msg": "MCP Server gestoppt", + "failed_to_start": "Server konnte nicht gestartet werden", + "failed_to_stop": "Server konnte nicht gestoppt werden", + "failed_to_save": "Einstellungen konnten nicht gespeichert werden", + "url_copied": "HTTP-URL in Zwischenablage kopiert", + "tool_manager": "Tool-Manager", + "open_tool_manager": "Tool-Manager öffnen", + "create_config": "Konfiguration erstellen", + "edit_config": "Konfiguration bearbeiten", + "delete_config": "Konfiguration löschen", + "import_config": "Konfiguration importieren", + "export_config": "Konfiguration exportieren", + "apply_config": "Konfiguration anwenden", + "select_all": "Alle auswählen", + "deselect_all": "Alle abwählen", + "save_changes": "Änderungen speichern", + "config_name": "Konfigurationsname", + "config_description": "Konfigurationsbeschreibung", + "current_config": "Aktuelle Konfiguration", + "tool_management": "Tool-Verwaltung", + "total_tools": "Gesamt", + "enabled_tools": "Aktiviert", + "disabled_tools": "Deaktiviert", + "no_config_selected": "Keine Konfiguration ausgewählt", + "select_config_first": "Bitte wählen Sie zuerst eine Konfiguration oder erstellen Sie eine neue", + "config_created": "Konfiguration erfolgreich erstellt", + "config_updated": "Konfiguration erfolgreich aktualisiert", + "config_deleted": "Konfiguration erfolgreich gelöscht", + "config_applied": "Konfiguration erfolgreich angewendet", + "config_exported": "Konfiguration erfolgreich exportiert", + "config_imported": "Konfiguration erfolgreich importiert", + "confirm_delete": "Löschen bestätigen", + "delete_config_confirm": "Möchten Sie die Konfiguration \"{0}\" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.", + "max_config_slots_reached": "Maximale Anzahl von Konfigurationen erreicht ({0})", + "invalid_config_format": "Ungültiges Konfigurationsformat", + "invalid_json_format": "Ungültiges JSON-Format oder Konfigurationsstruktur", + "server_tab": "Server", + "tools_tab": "Tool-Verwaltung", + "available_tools": "Verfügbare Tools", + "scene_tools": "Szenen-Tools", + "node_tools": "Knoten-Tools", + "component_tools": "Komponenten-Tools", + "prefab_tools": "Prefab-Tools", + "project_tools": "Projekt-Tools", + "debug_tools": "Debug-Tools", + "preferences_tools": "Einstellungen", + "server_tools": "Server-Tools", + "broadcast_tools": "Broadcast-Tools", + "scene_advanced_tools": "Erweiterte Szenen-Tools", + "scene_view_tools": "Szenenansicht-Tools", + "reference_image_tools": "Referenzbild-Tools", + "asset_advanced_tools": "Erweiterte Asset-Tools", + "validation_tools": "Validierungs-Tools", + "brand_name": "LiDaxian MCP Series", + "brand_subtitle": "LiDaxian MCP Series", + "contact_email": "E-Mail", + "contact_wechat": "WeChat", + "contact_github": "GitHub", + "machine_id_label": "Maschinencode", + "purchase_renew": "Kaufen / Verlängern", + "purchase_url": "https://www.vberai.com/game-engines/cocos", + "auth_title": "Anmeldemethode auswählen", + "auth_subtitle": "Geben Sie Ihre E-Mail und Ihren Aktivierungscode ein", + "email_label": "E-Mail-Adresse", + "activation_code_label": "Aktivierungscode", + "email_placeholder": "E-Mail eingeben", + "code_placeholder": "Code eingeben (XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)", + "activate_button": "Aktivieren", + "deactivate_button": "Deaktivieren", + "activating": "Aktivierung...", + "deactivating": "Deaktivierung...", + "license_status": "Lizenzstatus", + "license_type": "Lizenztyp", + "expiry_date": "Ablaufdatum", + "days_remaining": "Verbleibende Tage", + "days_unit": "T", + "permanent": "Permanent", + "expiry_warning_title": "Lizenz läuft bald ab", + "expiry_warning_message": "Ihre Lizenz läuft in {0} Tagen ab. Bitte verlängern Sie, um Unterbrechungen zu vermeiden.", + "renew_now": "Jetzt verlängern", + "i_understand": "Verstanden", + "activation_success": "Lizenz erfolgreich aktiviert!", + "activation_failed": "Aktivierung fehlgeschlagen: {0}", + "invalid_code": "Ungültiges Aktivierungscode-Format", + "purchase_license": "Lizenz kaufen", + "renew_license": "Lizenz verlängern", + "contact_support": "Support", + "language_switch": "Deutsch", + "version_label": "Version", + "license_required": "Zum Starten des Servers ist eine Lizenz erforderlich", + "license_check_failed": "Lizenzprüfung fehlgeschlagen", + + "status_stopped": "Gestoppt", + "status_running": "Läuft", + "status_error": "Verbindung fehlgeschlagen", + "config_tab": "Schnellkonfig", + "status_label": "Status", + "connections_count": "Verbindungen", + "transport_protocol": "Transportprotokoll", + "transport_type": "Transporttyp", + "mcp_url": "MCP-URL", + "port_fallback_title": "Port {0} nicht verfügbar – zu {1} gewechselt", + "port_fallback_reason": "Port {0} ist vom System reserviert oder wird von einer Firewall/Antivirensoftware blockiert (häufig unter Windows mit Hyper-V, WSL2 oder Docker). Der Server läuft auf dem neuen Port normal.", + "port_fallback_resynced": "Konfiguration aktualisiert für: {0}. Starten Sie diese Clients neu, damit der neue Port wirksam wird.", + "port_fallback_no_clients": "Falls Sie bereits AI-Clients eingerichtet haben, konfigurieren Sie sie über die Ein-Klick-Konfiguration erneut auf den neuen Port.", + "port_fallback_dismiss": "Verstanden", + "port_label": "Port", + "auto_start_label": "Autostart", + "debug_log_label": "Debug-Protokoll", + "max_connections_label": "Max. Verbindungen", + "server_settings_title": "Servereinstellungen", + "server_status_title": "Serverstatus", + "connection_info_title": "Verbindungsinfo", + "tools_count": "{0} Tools", + "tools_stats": "{0} Tools ({1} aktiviert / {2} deaktiviert)", + "quick_config_title": "MCP-Clients schnell konfigurieren", + "quick_config_desc": "MCP-Server automatisch in AI-Editoren und CLI-Tools konfigurieren", + "server_info": "Serverinfo", + "server_name_label": "Servername", + "server_address": "Serveradresse", + "timeout_ms": "Zeitlimit (ms)", + "auto_config_title": "Auto-Konfig (IDE-Editoren + Codex CLI)", + "add_all": "Alle hinzufügen", + "remove_all": "Alle entfernen", + "auto_config_note": "Klicken Sie, um Konfigurationsdateien automatisch zu ändern (Cursor, Windsurf, Trae, Codex CLI)", + "configured": "Konfiguriert", + "not_configured": "Nicht konfiguriert", + "auto_config_btn": "Auto-Konfig", + "delete_btn": "Löschen", + "copy_config": "Konfig kopieren", + "open_config": "Konfig öffnen", + "manual_config_title": "Manuelle Konfig (Claude CLI + Gemini CLI)", + "refresh_commands": "Befehle aktualisieren", + "manual_config_note": "Die folgenden CLI-Tools erfordern manuelle Ausführung im Terminal", + "config_scope": "Konfig-Bereich:", + "scope_user": "Benutzerebene", + "scope_project": "Projektebene", + "operation_log": "Operationsprotokoll", + "no_logs": "Keine Einträge", + "copyright_line": "© 2025 LiDaxian - Cocos Creator MCP Server v{0}", + "protocol_line": "Basierend auf dem Streamable HTTP Protokoll (MCP 2025-03-26)", + "tools_copyright_line1": "Tool-System - 13 Intent-Level-Tools, 150+ Editor-Operationen, Action-Code-Dispatch", + "config_copyright_line1": "Schnellkonfig - Unterstützt Cursor, Windsurf, Trae, Codex CLI, Claude CLI, Gemini CLI", + "email_required": "Bitte geben Sie E-Mail und Aktivierungscode ein", + "permanent_label": "Permanent", + "logout_button": "Abmelden", + "invite_code_label": "Einladungscode", + "copy_button": "Kopieren", + "change_code_button": "Code ändern", + "change_code_title": "Aktivierungscode ändern", + "change_code_desc": "Geben Sie Ihren neuen Aktivierungscode ein. Die aktuelle Gerätebindung wird zuerst freigegeben.", + "confirm_change": "Bestätigen", + "cancel": "Abbrechen", + "code_required": "Bitte geben Sie einen Aktivierungscode ein", + "expiry_inline_warning": "Läuft bald ab!", + "store_purchase_hint": "Wenn Sie dieses Plugin im Cocos Store gekauft haben, kontaktieren Sie mich bitte, um Ihren Aktivierungscode zu erhalten.", + "license_expired_title": "Lizenz abgelaufen", + "license_expired_message": "Ihre Lizenz ist abgelaufen und der Server kann nicht gestartet werden. Bitte verlängern oder reaktivieren Sie, um fortzufahren.", + "go_to_login": "Zum Login", + + "log_load_config_failed": "Konfigurationsstatus konnte nicht geladen werden: {0}", + "log_gen_cli_failed": "CLI-Befehle konnten nicht generiert werden: {0}", + "log_add_client_failed": "Hinzufügen zu {0} fehlgeschlagen: {1}", + "log_remove_client_failed": "Entfernen von {0} fehlgeschlagen: {1}", + "quick_config_dialog_title": "Ein-Klick-Konfiguration", + "config_operation_failed": "Konfigurationsvorgang fehlgeschlagen", + "config_file_not_found_changed_path": "Konfiguration fehlgeschlagen: Konfigurationsdatei nicht gefunden. {0} wurde möglicherweise in einem benutzerdefinierten Pfad installiert.", + "log_batch_add_failed": "Stapel-Hinzufügen fehlgeschlagen: {0}", + "log_batch_remove_failed": "Stapel-Entfernen fehlgeschlagen: {0}", + "log_copied_config": "{0}-Konfiguration in Zwischenablage kopiert", + "log_gen_config_failed": "Konfiguration konnte nicht generiert werden", + "log_copy_config_failed": "Konfiguration konnte nicht kopiert werden: {0}", + "log_copied_command": "{0}-Befehl in Zwischenablage kopiert", + "log_copy_command_failed": "Befehl konnte nicht kopiert werden: {0}", + "log_opened_config_file": "Konfigurationsdatei geöffnet: {0}", + "log_open_config_failed": "Konfigurationsdatei konnte nicht geöffnet werden", + "log_open_config_error": "Konfigurationsdatei konnte nicht geöffnet werden: {0}", + + "update_available_title": "Update verfügbar", + "update_new_version": "Neue Version v{0} ist verfügbar!", + "update_now": "Jetzt aktualisieren", + "update_not_now": "Später", + "update_skip_version": "v{0} überspringen", + "check_for_updates": "Nach Updates suchen", + "update_checking": "Prüfe...", + "update_no_update": "Sie verwenden die neueste Version", + "update_urgency_critical": "Kritisches Update", + "update_urgency_high": "Wichtiges Update", + "update_current_version": "Aktuelle Version: v{0}", + "update_changelog": "Änderungsprotokoll", + "auth_choose_subtitle": "Wählen Sie eine Anmeldemethode, um fortzufahren", + "update_changelog_empty": "Für diese Version wurden keine Versionshinweise bereitgestellt.", + "subscription_login_title": "VberAI-Abonnementkonto", + "subscription_password_label": "Passwort", + "subscription_password_placeholder": "Passwort eingeben", + "subscription_login_button": "Mit VberAI anmelden", + "subscription_google_button": "Mit Google anmelden", + "subscription_google_pending": "Browser geöffnet. Schließen Sie die Google-Autorisierung ab und kehren Sie dann zu Cocos Creator zurück.", + "subscription_google_pending_button": "Warte auf Google-Autorisierung...", + "subscription_register_button": "VberAI-Konto erstellen", + "subscription_logging_in": "Anmeldung läuft...", + "subscription_credentials_required": "Bitte E-Mail und Passwort eingeben", + "subscription_login_success": "Bei Abonnementkonto angemeldet", + "subscription_login_failed": "Abonnement-Anmeldung fehlgeschlagen", + "subscription_not_entitled": "Ihr aktuelles Abonnement enthält Cocos 3.x MCP nicht. Führen Sie ein Upgrade durch, um fortzufahren.", + "subscription_upgrade_to": "Führen Sie ein Upgrade auf „{0}“ durch, um dieses Produkt zu verwenden", + "subscription_upgrade_action": "Abonnement upgraden", + "subscription_logged_in": "Angemeldetes Konto", + "subscription_choose_button": "VberAI-Abonnementkonto", + "license_choose_button": "Aktivierungscode", + "subscription_method_label": "Abonnement", + "back_to_login_choices": "← Andere Anmeldemethode wählen", + "subscription_signed_out_other_device": "Abgemeldet: Ihr Konto wurde auf einem anderen Gerät verwendet.", + "subscription_google_cancelled": "Google-Anmeldung wurde abgebrochen.", + "subscription_request_timeout": "Zeitüberschreitung der Anfrage. Überprüfen Sie Ihr Netzwerk und versuchen Sie es erneut.", + "subscription_network_error": "Netzwerkverbindung fehlgeschlagen. Überprüfen Sie Ihr Netzwerk und versuchen Sie es erneut.", + "subscription_server_error": "Der Server hat eine ungültige Antwort zurückgegeben. Versuchen Sie es später erneut.", + "subscription_google_authorization_failed": "Die Google-Autorisierung wurde nicht abgeschlossen. Bitte versuchen Sie es erneut." +}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/en.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/en.js new file mode 100644 index 0000000..5a94942 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/en.js @@ -0,0 +1,244 @@ +"use strict"; + +module.exports = { + "extension_name": "Cocos MCP Server", + "description": "AI MCP Server for Cocos Creator 3.8", + "panel_title": "MCP Server", + "open_panel": "Open MCP Panel", + "start_server": "Start Server", + "stop_server": "Stop Server", + "server_status": "Server Status", + "port": "Port", + "settings": "Settings", + "connected": "Connected", + "disconnected": "Disconnected", + "server_running": "Server is running on port {0}", + "server_stopped": "Server has stopped", + "auto_start": "Auto Start", + "debug_log": "Debug Logging", + "max_connections": "Max Connections", + "connection_info": "Connection Info", + "http_url": "HTTP URL", + "copy": "Copy", + "save_settings": "Save Settings", + "settings_saved": "Settings saved successfully", + "server_started": "MCP Server Started", + "server_stopped_msg": "MCP Server Stopped", + "failed_to_start": "Failed to start server", + "failed_to_stop": "Failed to stop server", + "failed_to_save": "Failed to save settings", + "url_copied": "HTTP URL copied to clipboard", + "tool_manager": "Tool Manager", + "open_tool_manager": "Open Tool Manager", + "create_config": "Create Configuration", + "edit_config": "Edit Configuration", + "delete_config": "Delete Configuration", + "import_config": "Import Configuration", + "export_config": "Export Configuration", + "apply_config": "Apply Configuration", + "select_all": "Select All", + "deselect_all": "Deselect All", + "save_changes": "Save Changes", + "config_name": "Configuration Name", + "config_description": "Configuration Description", + "current_config": "Current Configuration", + "tool_management": "Tool Management", + "total_tools": "Total Tools", + "enabled_tools": "Enabled", + "disabled_tools": "Disabled", + "no_config_selected": "No Configuration Selected", + "select_config_first": "Please select a configuration or create a new one first", + "config_created": "Configuration created successfully", + "config_updated": "Configuration updated successfully", + "config_deleted": "Configuration deleted successfully", + "config_applied": "Configuration applied successfully", + "config_exported": "Configuration exported successfully", + "config_imported": "Configuration imported successfully", + "confirm_delete": "Confirm Delete", + "delete_config_confirm": "Are you sure you want to delete configuration \"{0}\"? This action cannot be undone.", + "max_config_slots_reached": "Maximum configuration slots reached ({0})", + "invalid_config_format": "Invalid configuration format", + "invalid_json_format": "Invalid JSON format or configuration structure", + "server_tab": "Server", + "tools_tab": "Tool Management", + "available_tools": "Available Tools", + "scene_tools": "Scene Tools", + "node_tools": "Node Tools", + "component_tools": "Component Tools", + "prefab_tools": "Prefab Tools", + "project_tools": "Project Tools", + "debug_tools": "Debug Tools", + "preferences_tools": "Preferences", + "server_tools": "Server Tools", + "broadcast_tools": "Broadcast Tools", + "scene_advanced_tools": "Advanced Scene Tools", + "scene_view_tools": "Scene View Tools", + "reference_image_tools": "Reference Image Tools", + "asset_advanced_tools": "Advanced Asset Tools", + "validation_tools": "Validation Tools", + "brand_name": "LiDaxian MCP Series", + "brand_subtitle": "LiDaxian MCP Series", + "contact_email": "Email", + "contact_wechat": "WeChat", + "contact_github": "GitHub", + "machine_id_label": "Machine ID", + "purchase_renew": "Purchase / Renew", + "purchase_url": "https://www.vberai.com/game-engines/cocos", + "auth_title": "Choose a sign-in method", + "auth_subtitle": "Enter your email and activation code", + "auth_choose_subtitle": "Choose a sign-in method to continue", + "email_label": "Email Address", + "activation_code_label": "Activation Code", + "email_placeholder": "Enter email", + "code_placeholder": "Enter code (XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)", + "activate_button": "Activate", + "deactivate_button": "Deactivate", + "activating": "Activating...", + "deactivating": "Deactivating...", + "license_status": "License Status", + "license_type": "License Type", + "expiry_date": "Expiry Date", + "days_remaining": "Days Remaining", + "days_unit": "d", + "permanent": "Permanent", + "expiry_warning_title": "License Expiring Soon", + "expiry_warning_message": "Your license will expire in {0} days. Please renew to avoid service interruption.", + "renew_now": "Renew Now", + "i_understand": "I Understand", + "activation_success": "License activated successfully!", + "activation_failed": "Activation failed: {0}", + "invalid_code": "Invalid activation code format", + "purchase_license": "Purchase License", + "renew_license": "Renew License", + "contact_support": "Support", + "language_switch": "English", + "version_label": "Version", + "license_required": "License required to start server", + "license_check_failed": "License check failed", + + "status_stopped": "Stopped", + "status_running": "Running", + "status_error": "Connection Failed", + "config_tab": "Quick Config", + "status_label": "Status", + "connections_count": "Connections", + "transport_protocol": "Transport Protocol", + "transport_type": "Transport Type", + "mcp_url": "MCP URL", + "port_fallback_title": "Port {0} unavailable — switched to {1}", + "port_fallback_reason": "Port {0} is reserved by the system or blocked by a firewall/antivirus (common on Windows with Hyper-V, WSL2 or Docker installed). The server is running normally on the new port.", + "port_fallback_resynced": "Updated the config for: {0}. Restart these clients for the new port to take effect.", + "port_fallback_no_clients": "If you have already configured AI clients, re-run the one-click setup to point them at the new port.", + "port_fallback_dismiss": "Got it", + "port_label": "Port", + "auto_start_label": "Auto Start", + "debug_log_label": "Debug Log", + "max_connections_label": "Max Connections", + "server_settings_title": "Server Settings", + "server_status_title": "Server Status", + "connection_info_title": "Connection Info", + "tools_count": "{0} tools", + "tools_stats": "{0} tools ({1} enabled / {2} disabled)", + "quick_config_title": "Quick Config MCP Clients", + "quick_config_desc": "Auto-configure the current MCP server into mainstream AI editors and CLI tools", + "server_info": "Server Info", + "server_name_label": "Server Name", + "server_address": "Server Address", + "timeout_ms": "Timeout (ms)", + "auto_config_title": "Auto Config (IDE Editors + Codex CLI)", + "add_all": "Add All", + "remove_all": "Remove All", + "auto_config_note": "Click button to auto-modify config files (Cursor, Windsurf, Trae, Codex CLI)", + "configured": "Configured", + "not_configured": "Not Configured", + "auto_config_btn": "Auto Config", + "delete_btn": "Delete", + "copy_config": "Copy Config", + "open_config": "Open Config", + "manual_config_title": "Manual Config (Claude CLI + Gemini CLI)", + "refresh_commands": "Refresh Commands", + "manual_config_note": "The following CLI tools need manual terminal command execution", + "config_scope": "Config Scope:", + "scope_user": "User Level", + "scope_project": "Project Level", + "operation_log": "Operation Log", + "no_logs": "No operation records", + "copyright_line": "© 2025 LiDaxian - Cocos Creator MCP Server v{0}", + "protocol_line": "Based on Streamable HTTP Protocol (MCP 2025-03-26)", + "tools_copyright_line1": "Tool System - 13 intent-level tools, 150+ editor operations, action code dispatch", + "config_copyright_line1": "Quick Config - Supports Cursor, Windsurf, Trae, Codex CLI, Claude CLI, Gemini CLI", + "email_required": "Please enter email and activation code", + "permanent_label": "Permanent", + "logout_button": "Logout", + "invite_code_label": "Invite Code", + "copy_button": "Copy", + "change_code_button": "Change Code", + "change_code_title": "Change Activation Code", + "change_code_desc": "Enter your new activation code. The current device binding will be released first.", + "confirm_change": "Confirm", + "cancel": "Cancel", + "code_required": "Please enter an activation code", + "expiry_inline_warning": "Expiring soon!", + "store_purchase_hint": "If you purchased this plugin from Cocos Store, please contact me to get your activation code.", + "license_expired_title": "License Expired", + "license_expired_message": "Your license has expired and the server cannot be started. Please renew or reactivate to continue.", + "go_to_login": "Go to Login", + + "log_load_config_failed": "Failed to load config status: {0}", + "log_gen_cli_failed": "Failed to generate CLI commands: {0}", + "log_add_client_failed": "Failed to add to {0}: {1}", + "log_remove_client_failed": "Failed to remove from {0}: {1}", + "quick_config_dialog_title": "One-click Configuration", + "config_operation_failed": "Configuration operation failed", + "config_file_not_found_changed_path": "Configuration failed: could not find the config file. {0} may be installed in a custom path.", + "log_batch_add_failed": "Batch add failed: {0}", + "log_batch_remove_failed": "Batch remove failed: {0}", + "log_copied_config": "Copied {0} config to clipboard", + "log_gen_config_failed": "Failed to generate config", + "log_copy_config_failed": "Failed to copy config: {0}", + "log_copied_command": "Copied {0} command to clipboard", + "log_copy_command_failed": "Failed to copy command: {0}", + "log_opened_config_file": "Opened config file: {0}", + "log_open_config_failed": "Failed to open config file", + "log_open_config_error": "Failed to open config file: {0}", + + "update_available_title": "Update Available", + "update_new_version": "New version v{0} is available!", + "update_now": "Update Now", + "update_not_now": "Not Now", + "update_skip_version": "Skip v{0}", + "check_for_updates": "Check for Updates", + "update_checking": "Checking...", + "update_no_update": "You are using the latest version", + "update_urgency_critical": "Critical Update", + "update_urgency_high": "Important Update", + "update_current_version": "Current version: v{0}", + "update_changelog": "Changelog" + ,"update_changelog_empty": "No release notes were provided for this version." + ,"subscription_login_title": "VberAI subscription account" + ,"subscription_password_label": "Password" + ,"subscription_password_placeholder": "Enter password" + ,"subscription_login_button": "Sign in with VberAI" + ,"subscription_google_button": "Sign in with Google" + ,"subscription_google_pending": "Browser opened. Complete Google authorization, then return to Cocos Creator." + ,"subscription_google_pending_button": "Waiting for Google authorization..." + ,"subscription_register_button": "Create a VberAI account" + ,"subscription_logging_in": "Signing in..." + ,"subscription_credentials_required": "Please enter email and password" + ,"subscription_login_success": "Subscription account signed in" + ,"subscription_login_failed": "Subscription sign-in failed" + ,"subscription_not_entitled": "Your current subscription does not include Cocos 3.x MCP. Upgrade to continue." + ,"subscription_upgrade_to": "Upgrade to {0} to use this product" + ,"subscription_upgrade_action": "Upgrade subscription" + ,"subscription_logged_in": "Signed-in account" + ,"subscription_choose_button": "VberAI subscription account" + ,"license_choose_button": "Activation code" + ,"subscription_method_label": "Subscription" + ,"back_to_login_choices": "← Choose another sign-in method" + ,"subscription_signed_out_other_device": "Signed out: your account was used on another device." + ,"subscription_google_cancelled": "Google sign-in was cancelled." + ,"subscription_request_timeout": "The request timed out. Check your network and try again." + ,"subscription_network_error": "Network connection failed. Check your network and try again." + ,"subscription_server_error": "The server returned an invalid response. Try again later." + ,"subscription_google_authorization_failed": "Google authorization was not completed. Please try again." +}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/es.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/es.js new file mode 100644 index 0000000..acc717c --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/es.js @@ -0,0 +1,243 @@ +"use strict"; +module.exports = { + "extension_name": "Cocos MCP Servidor", + "description": "Servidor AI MCP para Cocos Creator 3.8", + "panel_title": "Servidor MCP", + "open_panel": "Abrir panel MCP", + "start_server": "Iniciar servidor", + "stop_server": "Detener servidor", + "server_status": "Estado del servidor", + "port": "Puerto", + "settings": "Ajustes", + "connected": "Conectado", + "disconnected": "Desconectado", + "server_running": "El servidor está ejecutándose en el puerto {0}", + "server_stopped": "El servidor se ha detenido", + "auto_start": "Inicio automático", + "debug_log": "Registro de depuración", + "max_connections": "Conexiones máx.", + "connection_info": "Info de conexión", + "http_url": "URL HTTP", + "copy": "Copiar", + "save_settings": "Guardar", + "settings_saved": "Ajustes guardados correctamente", + "server_started": "Servidor MCP iniciado", + "server_stopped_msg": "Servidor MCP detenido", + "failed_to_start": "Error al iniciar el servidor", + "failed_to_stop": "Error al detener el servidor", + "failed_to_save": "Error al guardar los ajustes", + "url_copied": "URL HTTP copiada al portapapeles", + "tool_manager": "Gestor de herramientas", + "open_tool_manager": "Abrir gestor de herramientas", + "create_config": "Crear configuración", + "edit_config": "Editar configuración", + "delete_config": "Eliminar configuración", + "import_config": "Importar configuración", + "export_config": "Exportar configuración", + "apply_config": "Aplicar configuración", + "select_all": "Seleccionar todo", + "deselect_all": "Deseleccionar todo", + "save_changes": "Guardar cambios", + "config_name": "Nombre de configuración", + "config_description": "Descripción de configuración", + "current_config": "Configuración actual", + "tool_management": "Gestión de herramientas", + "total_tools": "Total de herramientas", + "enabled_tools": "Activadas", + "disabled_tools": "Desactivadas", + "no_config_selected": "Ninguna configuración seleccionada", + "select_config_first": "Por favor, seleccione o cree una configuración primero", + "config_created": "Configuración creada correctamente", + "config_updated": "Configuración actualizada correctamente", + "config_deleted": "Configuración eliminada correctamente", + "config_applied": "Configuración aplicada correctamente", + "config_exported": "Configuración exportada correctamente", + "config_imported": "Configuración importada correctamente", + "confirm_delete": "Confirmar eliminación", + "delete_config_confirm": "¿Está seguro de eliminar la configuración \"{0}\"? Esta acción no se puede deshacer.", + "max_config_slots_reached": "Se alcanzó el máximo de configuraciones ({0})", + "invalid_config_format": "Formato de configuración no válido", + "invalid_json_format": "Formato JSON o estructura de configuración no válidos", + "server_tab": "Servidor", + "tools_tab": "Gestión de herramientas", + "available_tools": "Herramientas disponibles", + "scene_tools": "Herramientas de escena", + "node_tools": "Herramientas de nodo", + "component_tools": "Herramientas de componente", + "prefab_tools": "Herramientas de prefab", + "project_tools": "Herramientas de proyecto", + "debug_tools": "Herramientas de depuración", + "preferences_tools": "Preferencias", + "server_tools": "Herramientas de servidor", + "broadcast_tools": "Herramientas de difusión", + "scene_advanced_tools": "Herramientas de escena avanzadas", + "scene_view_tools": "Herramientas de vista de escena", + "reference_image_tools": "Herramientas de imagen de referencia", + "asset_advanced_tools": "Herramientas de assets avanzadas", + "validation_tools": "Herramientas de validación", + "brand_name": "LiDaxian MCP Series", + "brand_subtitle": "LiDaxian MCP Series", + "contact_email": "Correo", + "contact_wechat": "WeChat", + "contact_github": "GitHub", + "machine_id_label": "Código de máquina", + "purchase_renew": "Comprar / Renovar", + "purchase_url": "https://www.vberai.com/game-engines/cocos", + "auth_title": "Elegir método de inicio de sesión", + "auth_subtitle": "Ingrese su correo y código de activación", + "email_label": "Correo electrónico", + "activation_code_label": "Código de activación", + "email_placeholder": "Ingrese correo", + "code_placeholder": "Ingrese código (XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)", + "activate_button": "Activar", + "deactivate_button": "Desactivar", + "activating": "Activando...", + "deactivating": "Desactivando...", + "license_status": "Estado de licencia", + "license_type": "Tipo de licencia", + "expiry_date": "Fecha de vencimiento", + "days_remaining": "Días restantes", + "days_unit": "d", + "permanent": "Permanente", + "expiry_warning_title": "La licencia expira pronto", + "expiry_warning_message": "Su licencia expirará en {0} días. Por favor, renueve para evitar interrupciones.", + "renew_now": "Renovar ahora", + "i_understand": "Entendido", + "activation_success": "¡Licencia activada correctamente!", + "activation_failed": "Error de activación: {0}", + "invalid_code": "Formato de código de activación no válido", + "purchase_license": "Comprar licencia", + "renew_license": "Renovar licencia", + "contact_support": "Soporte", + "language_switch": "Español", + "version_label": "Versión", + "license_required": "Se requiere licencia para iniciar el servidor", + "license_check_failed": "Error en la verificación de licencia", + + "status_stopped": "Detenido", + "status_running": "En ejecución", + "status_error": "Error de conexión", + "config_tab": "Config rápida", + "status_label": "Estado", + "connections_count": "Conexiones", + "transport_protocol": "Protocolo de transporte", + "transport_type": "Tipo de transporte", + "mcp_url": "URL MCP", + "port_fallback_title": "Puerto {0} no disponible: se cambió a {1}", + "port_fallback_reason": "El puerto {0} está reservado por el sistema o bloqueado por un firewall/antivirus (habitual en Windows con Hyper-V, WSL2 o Docker). El servidor funciona con normalidad en el nuevo puerto.", + "port_fallback_resynced": "Se actualizó la configuración de: {0}. Reinicie estos clientes para que el nuevo puerto surta efecto.", + "port_fallback_no_clients": "Si ya configuró clientes de IA, vuelva a configurarlos en el nuevo puerto desde la configuración con un clic.", + "port_fallback_dismiss": "Entendido", + "port_label": "Puerto", + "auto_start_label": "Inicio auto", + "debug_log_label": "Registro de depuración", + "max_connections_label": "Conexiones máx.", + "server_settings_title": "Ajustes del servidor", + "server_status_title": "Estado del servidor", + "connection_info_title": "Info de conexión", + "tools_count": "{0} herramientas", + "tools_stats": "{0} herramientas ({1} activadas / {2} desactivadas)", + "quick_config_title": "Config rápida de clientes MCP", + "quick_config_desc": "Configurar automáticamente el servidor MCP en editores AI y herramientas CLI", + "server_info": "Info del servidor", + "server_name_label": "Nombre del servidor", + "server_address": "Dirección del servidor", + "timeout_ms": "Tiempo de espera (ms)", + "auto_config_title": "Config auto (IDE + Codex CLI)", + "add_all": "Añadir todo", + "remove_all": "Eliminar todo", + "auto_config_note": "Haga clic para modificar automáticamente los archivos de config (Cursor, Windsurf, Trae, Codex CLI)", + "configured": "Configurado", + "not_configured": "No configurado", + "auto_config_btn": "Config auto", + "delete_btn": "Eliminar", + "copy_config": "Copiar config", + "open_config": "Abrir config", + "manual_config_title": "Config manual (Claude CLI + Gemini CLI)", + "refresh_commands": "Actualizar comandos", + "manual_config_note": "Las siguientes herramientas CLI requieren ejecución manual en terminal", + "config_scope": "Ámbito de config:", + "scope_user": "Nivel de usuario", + "scope_project": "Nivel de proyecto", + "operation_log": "Registro de operaciones", + "no_logs": "Sin registros", + "copyright_line": "© 2025 LiDaxian - Cocos Creator MCP Server v{0}", + "protocol_line": "Basado en el protocolo Streamable HTTP (MCP 2025-03-26)", + "tools_copyright_line1": "Sistema de herramientas - 13 herramientas intent-level, 150+ operaciones de editor, despacho de código de acción", + "config_copyright_line1": "Config rápida - Soporta Cursor, Windsurf, Trae, Codex CLI, Claude CLI, Gemini CLI", + "email_required": "Por favor ingrese correo y código de activación", + "permanent_label": "Permanente", + "logout_button": "Cerrar sesión", + "invite_code_label": "Código de invitación", + "copy_button": "Copiar", + "change_code_button": "Cambiar código", + "change_code_title": "Cambiar código de activación", + "change_code_desc": "Ingrese su nuevo código de activación. El enlace del dispositivo actual se liberará primero.", + "confirm_change": "Confirmar", + "cancel": "Cancelar", + "code_required": "Por favor ingrese un código de activación", + "expiry_inline_warning": "¡Expira pronto!", + "store_purchase_hint": "Si compró este plugin en Cocos Store, contácteme para obtener su código de activación.", + "license_expired_title": "Licencia expirada", + "license_expired_message": "Su licencia ha expirado y el servidor no puede iniciarse. Renueve o reactive para continuar.", + "go_to_login": "Ir a inicio de sesión", + + "log_load_config_failed": "Error al cargar el estado de config: {0}", + "log_gen_cli_failed": "Error al generar comandos CLI: {0}", + "log_add_client_failed": "Error al añadir a {0}: {1}", + "log_remove_client_failed": "Error al eliminar de {0}: {1}", + "quick_config_dialog_title": "Configuración con un clic", + "config_operation_failed": "Error en la operación de configuración", + "config_file_not_found_changed_path": "Error de configuración: no se encontró el archivo de configuración. Es posible que {0} se haya instalado en una ruta personalizada.", + "log_batch_add_failed": "Error en la adición masiva: {0}", + "log_batch_remove_failed": "Error en la eliminación masiva: {0}", + "log_copied_config": "Config de {0} copiada al portapapeles", + "log_gen_config_failed": "Error al generar la config", + "log_copy_config_failed": "Error al copiar la config: {0}", + "log_copied_command": "Comando {0} copiado al portapapeles", + "log_copy_command_failed": "Error al copiar el comando: {0}", + "log_opened_config_file": "Archivo de config abierto: {0}", + "log_open_config_failed": "Error al abrir archivo de config", + "log_open_config_error": "Error al abrir archivo de config: {0}", + + "update_available_title": "Actualización disponible", + "update_new_version": "¡La nueva versión v{0} está disponible!", + "update_now": "Actualizar ahora", + "update_not_now": "Ahora no", + "update_skip_version": "Omitir v{0}", + "check_for_updates": "Buscar actualizaciones", + "update_checking": "Comprobando...", + "update_no_update": "Está usando la última versión", + "update_urgency_critical": "Actualización crítica", + "update_urgency_high": "Actualización importante", + "update_current_version": "Versión actual: v{0}", + "update_changelog": "Registro de cambios", + "auth_choose_subtitle": "Elige un método de inicio de sesión para continuar", + "update_changelog_empty": "No se proporcionaron notas de la versión para esta versión.", + "subscription_login_title": "Cuenta de suscripción de VberAI", + "subscription_password_label": "Contraseña", + "subscription_password_placeholder": "Introduce la contraseña", + "subscription_login_button": "Iniciar sesión con VberAI", + "subscription_google_button": "Iniciar sesión con Google", + "subscription_google_pending": "Navegador abierto. Completa la autorización de Google y vuelve a Cocos Creator.", + "subscription_google_pending_button": "Esperando la autorización de Google...", + "subscription_register_button": "Crear una cuenta de VberAI", + "subscription_logging_in": "Iniciando sesión...", + "subscription_credentials_required": "Introduce el correo electrónico y la contraseña", + "subscription_login_success": "Sesión iniciada en la cuenta de suscripción", + "subscription_login_failed": "Error al iniciar sesión en la suscripción", + "subscription_not_entitled": "Tu suscripción actual no incluye Cocos 3.x MCP. Actualiza para continuar.", + "subscription_upgrade_to": "Actualiza a «{0}» para usar este producto", + "subscription_upgrade_action": "Mejorar suscripción", + "subscription_logged_in": "Cuenta con sesión iniciada", + "subscription_choose_button": "Cuenta de suscripción de VberAI", + "license_choose_button": "Código de activación", + "subscription_method_label": "Suscripción", + "back_to_login_choices": "← Elegir otro método de inicio de sesión", + "subscription_signed_out_other_device": "Sesión cerrada: tu cuenta se usó en otro dispositivo.", + "subscription_google_cancelled": "Se canceló el inicio de sesión con Google.", + "subscription_request_timeout": "Se agotó el tiempo de la solicitud. Comprueba tu red e inténtalo de nuevo.", + "subscription_network_error": "Falló la conexión de red. Comprueba tu red e inténtalo de nuevo.", + "subscription_server_error": "El servidor devolvió una respuesta no válida. Inténtalo más tarde.", + "subscription_google_authorization_failed": "No se completó la autorización de Google. Inténtalo de nuevo." +}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/fr.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/fr.js new file mode 100644 index 0000000..e66e9d8 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/fr.js @@ -0,0 +1,243 @@ +"use strict"; +module.exports = { + "extension_name": "Cocos MCP Serveur", + "description": "Serveur AI MCP pour Cocos Creator 3.8", + "panel_title": "Serveur MCP", + "open_panel": "Ouvrir le panneau MCP", + "start_server": "Démarrer le serveur", + "stop_server": "Arrêter le serveur", + "server_status": "État du serveur", + "port": "Port", + "settings": "Paramètres", + "connected": "Connecté", + "disconnected": "Déconnecté", + "server_running": "Le serveur fonctionne sur le port {0}", + "server_stopped": "Le serveur est arrêté", + "auto_start": "Démarrage automatique", + "debug_log": "Journal de débogage", + "max_connections": "Connexions max", + "connection_info": "Infos de connexion", + "http_url": "URL HTTP", + "copy": "Copier", + "save_settings": "Enregistrer", + "settings_saved": "Paramètres enregistrés avec succès", + "server_started": "Serveur MCP démarré", + "server_stopped_msg": "Serveur MCP arrêté", + "failed_to_start": "Échec du démarrage du serveur", + "failed_to_stop": "Échec de l'arrêt du serveur", + "failed_to_save": "Échec de l'enregistrement", + "url_copied": "URL HTTP copiée dans le presse-papiers", + "tool_manager": "Gestionnaire d'outils", + "open_tool_manager": "Ouvrir le gestionnaire d'outils", + "create_config": "Créer une configuration", + "edit_config": "Modifier la configuration", + "delete_config": "Supprimer la configuration", + "import_config": "Importer la configuration", + "export_config": "Exporter la configuration", + "apply_config": "Appliquer la configuration", + "select_all": "Tout sélectionner", + "deselect_all": "Tout désélectionner", + "save_changes": "Enregistrer les modifications", + "config_name": "Nom de configuration", + "config_description": "Description de configuration", + "current_config": "Configuration actuelle", + "tool_management": "Gestion des outils", + "total_tools": "Total des outils", + "enabled_tools": "Activés", + "disabled_tools": "Désactivés", + "no_config_selected": "Aucune configuration sélectionnée", + "select_config_first": "Veuillez d'abord sélectionner ou créer une configuration", + "config_created": "Configuration créée avec succès", + "config_updated": "Configuration mise à jour avec succès", + "config_deleted": "Configuration supprimée avec succès", + "config_applied": "Configuration appliquée avec succès", + "config_exported": "Configuration exportée avec succès", + "config_imported": "Configuration importée avec succès", + "confirm_delete": "Confirmer la suppression", + "delete_config_confirm": "Voulez-vous vraiment supprimer la configuration \"{0}\" ? Cette action est irréversible.", + "max_config_slots_reached": "Nombre maximal de configurations atteint ({0})", + "invalid_config_format": "Format de configuration invalide", + "invalid_json_format": "Format JSON ou structure de configuration invalide", + "server_tab": "Serveur", + "tools_tab": "Gestion des outils", + "available_tools": "Outils disponibles", + "scene_tools": "Outils de scène", + "node_tools": "Outils de nœud", + "component_tools": "Outils de composant", + "prefab_tools": "Outils de prefab", + "project_tools": "Outils de projet", + "debug_tools": "Outils de débogage", + "preferences_tools": "Préférences", + "server_tools": "Outils serveur", + "broadcast_tools": "Outils de diffusion", + "scene_advanced_tools": "Outils de scène avancés", + "scene_view_tools": "Outils de vue de scène", + "reference_image_tools": "Outils d'image de référence", + "asset_advanced_tools": "Outils d'assets avancés", + "validation_tools": "Outils de validation", + "brand_name": "LiDaxian MCP Series", + "brand_subtitle": "LiDaxian MCP Series", + "contact_email": "E-mail", + "contact_wechat": "WeChat", + "contact_github": "GitHub", + "machine_id_label": "Code machine", + "purchase_renew": "Acheter / Renouveler", + "purchase_url": "https://www.vberai.com/game-engines/cocos", + "auth_title": "Choisir une méthode de connexion", + "auth_subtitle": "Entrez votre e-mail et votre code d'activation", + "email_label": "Adresse e-mail", + "activation_code_label": "Code d'activation", + "email_placeholder": "Entrez l'e-mail", + "code_placeholder": "Entrez le code (XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)", + "activate_button": "Activer", + "deactivate_button": "Désactiver", + "activating": "Activation...", + "deactivating": "Désactivation...", + "license_status": "État de la licence", + "license_type": "Type de licence", + "expiry_date": "Date d'expiration", + "days_remaining": "Jours restants", + "days_unit": "j", + "permanent": "Permanent", + "expiry_warning_title": "Licence bientôt expirée", + "expiry_warning_message": "Votre licence expirera dans {0} jours. Veuillez renouveler pour éviter toute interruption.", + "renew_now": "Renouveler", + "i_understand": "J'ai compris", + "activation_success": "Licence activée avec succès !", + "activation_failed": "Échec de l'activation : {0}", + "invalid_code": "Format de code d'activation invalide", + "purchase_license": "Acheter une licence", + "renew_license": "Renouveler la licence", + "contact_support": "Support", + "language_switch": "Français", + "version_label": "Version", + "license_required": "Une licence est requise pour démarrer le serveur", + "license_check_failed": "Échec de la vérification de licence", + + "status_stopped": "Arrêté", + "status_running": "En cours", + "status_error": "Échec de connexion", + "config_tab": "Config rapide", + "status_label": "État", + "connections_count": "Connexions", + "transport_protocol": "Protocole de transport", + "transport_type": "Type de transport", + "mcp_url": "URL MCP", + "port_fallback_title": "Port {0} indisponible — basculé vers {1}", + "port_fallback_reason": "Le port {0} est réservé par le système ou bloqué par un pare-feu/antivirus (fréquent sous Windows avec Hyper-V, WSL2 ou Docker). Le serveur fonctionne normalement sur le nouveau port.", + "port_fallback_resynced": "Configuration mise à jour pour : {0}. Redémarrez ces clients pour que le nouveau port prenne effet.", + "port_fallback_no_clients": "Si vous avez déjà configuré des clients IA, reconfigurez-les sur le nouveau port via la configuration en un clic.", + "port_fallback_dismiss": "Compris", + "port_label": "Port", + "auto_start_label": "Démarrage auto", + "debug_log_label": "Journal de débogage", + "max_connections_label": "Connexions max", + "server_settings_title": "Paramètres du serveur", + "server_status_title": "État du serveur", + "connection_info_title": "Infos de connexion", + "tools_count": "{0} outils", + "tools_stats": "{0} outils ({1} activés / {2} désactivés)", + "quick_config_title": "Config rapide des clients MCP", + "quick_config_desc": "Configurer automatiquement le serveur MCP dans les éditeurs AI et outils CLI", + "server_info": "Infos serveur", + "server_name_label": "Nom du serveur", + "server_address": "Adresse du serveur", + "timeout_ms": "Délai d'attente (ms)", + "auto_config_title": "Config auto (IDE + Codex CLI)", + "add_all": "Tout ajouter", + "remove_all": "Tout supprimer", + "auto_config_note": "Cliquez pour modifier automatiquement les fichiers de config (Cursor, Windsurf, Trae, Codex CLI)", + "configured": "Configuré", + "not_configured": "Non configuré", + "auto_config_btn": "Config auto", + "delete_btn": "Supprimer", + "copy_config": "Copier la config", + "open_config": "Ouvrir la config", + "manual_config_title": "Config manuelle (Claude CLI + Gemini CLI)", + "refresh_commands": "Actualiser les commandes", + "manual_config_note": "Les outils CLI suivants nécessitent une exécution manuelle dans le terminal", + "config_scope": "Portée de config :", + "scope_user": "Niveau utilisateur", + "scope_project": "Niveau projet", + "operation_log": "Journal des opérations", + "no_logs": "Aucun enregistrement", + "copyright_line": "© 2025 LiDaxian - Cocos Creator MCP Server v{0}", + "protocol_line": "Basé sur le protocole Streamable HTTP (MCP 2025-03-26)", + "tools_copyright_line1": "Système d'outils - 13 outils intent-level, 150+ opérations éditeur, dispatch par code d'action", + "config_copyright_line1": "Config rapide - Supporte Cursor, Windsurf, Trae, Codex CLI, Claude CLI, Gemini CLI", + "email_required": "Veuillez entrer l'e-mail et le code d'activation", + "permanent_label": "Permanent", + "logout_button": "Déconnexion", + "invite_code_label": "Code d'invitation", + "copy_button": "Copier", + "change_code_button": "Changer le code", + "change_code_title": "Changer le code d'activation", + "change_code_desc": "Entrez votre nouveau code d'activation. La liaison de l'appareil actuel sera d'abord libérée.", + "confirm_change": "Confirmer", + "cancel": "Annuler", + "code_required": "Veuillez entrer un code d'activation", + "expiry_inline_warning": "Expiration imminente !", + "store_purchase_hint": "Si vous avez acheté ce plugin sur Cocos Store, veuillez me contacter pour obtenir votre code d'activation.", + "license_expired_title": "Licence expirée", + "license_expired_message": "Votre licence a expiré et le serveur ne peut pas être démarré. Veuillez renouveler ou réactiver pour continuer.", + "go_to_login": "Se connecter", + + "log_load_config_failed": "Échec du chargement de la config : {0}", + "log_gen_cli_failed": "Échec de la génération des commandes CLI : {0}", + "log_add_client_failed": "Échec de l'ajout à {0} : {1}", + "log_remove_client_failed": "Échec de la suppression de {0} : {1}", + "quick_config_dialog_title": "Configuration en un clic", + "config_operation_failed": "Échec de l'opération de configuration", + "config_file_not_found_changed_path": "Échec de la configuration : fichier de configuration introuvable. {0} a peut-être été installé dans un chemin personnalisé.", + "log_batch_add_failed": "Échec de l'ajout groupé : {0}", + "log_batch_remove_failed": "Échec de la suppression groupée : {0}", + "log_copied_config": "Config {0} copiée dans le presse-papiers", + "log_gen_config_failed": "Échec de la génération de la config", + "log_copy_config_failed": "Échec de la copie de la config : {0}", + "log_copied_command": "Commande {0} copiée dans le presse-papiers", + "log_copy_command_failed": "Échec de la copie de la commande : {0}", + "log_opened_config_file": "Fichier de config ouvert : {0}", + "log_open_config_failed": "Échec de l'ouverture du fichier de config", + "log_open_config_error": "Échec de l'ouverture du fichier de config : {0}", + + "update_available_title": "Mise à jour disponible", + "update_new_version": "La nouvelle version v{0} est disponible !", + "update_now": "Mettre à jour", + "update_not_now": "Plus tard", + "update_skip_version": "Ignorer v{0}", + "check_for_updates": "Vérifier les mises à jour", + "update_checking": "Vérification...", + "update_no_update": "Vous utilisez la dernière version", + "update_urgency_critical": "Mise à jour critique", + "update_urgency_high": "Mise à jour importante", + "update_current_version": "Version actuelle : v{0}", + "update_changelog": "Journal des modifications", + "auth_choose_subtitle": "Choisissez une méthode de connexion pour continuer", + "update_changelog_empty": "Aucune note de version n'a été fournie pour cette version.", + "subscription_login_title": "Compte d'abonnement VberAI", + "subscription_password_label": "Mot de passe", + "subscription_password_placeholder": "Saisissez le mot de passe", + "subscription_login_button": "Se connecter avec VberAI", + "subscription_google_button": "Se connecter avec Google", + "subscription_google_pending": "Navigateur ouvert. Terminez l'autorisation Google, puis revenez à Cocos Creator.", + "subscription_google_pending_button": "En attente de l'autorisation Google...", + "subscription_register_button": "Créer un compte VberAI", + "subscription_logging_in": "Connexion en cours...", + "subscription_credentials_required": "Veuillez saisir l'e-mail et le mot de passe", + "subscription_login_success": "Connecté au compte d'abonnement", + "subscription_login_failed": "Échec de la connexion à l'abonnement", + "subscription_not_entitled": "Votre abonnement actuel n'inclut pas Cocos 3.x MCP. Effectuez une mise à niveau pour continuer.", + "subscription_upgrade_to": "Passez à « {0} » pour utiliser ce produit", + "subscription_upgrade_action": "Mettre à niveau l'abonnement", + "subscription_logged_in": "Compte connecté", + "subscription_choose_button": "Compte d'abonnement VberAI", + "license_choose_button": "Code d'activation", + "subscription_method_label": "Abonnement", + "back_to_login_choices": "← Choisir une autre méthode de connexion", + "subscription_signed_out_other_device": "Déconnecté : votre compte a été utilisé sur un autre appareil.", + "subscription_google_cancelled": "La connexion Google a été annulée.", + "subscription_request_timeout": "La requête a expiré. Vérifiez votre réseau et réessayez.", + "subscription_network_error": "Échec de la connexion réseau. Vérifiez votre réseau et réessayez.", + "subscription_server_error": "Le serveur a renvoyé une réponse non valide. Réessayez plus tard.", + "subscription_google_authorization_failed": "L'autorisation Google n'a pas été effectuée. Veuillez réessayer." +}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/ja.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/ja.js new file mode 100644 index 0000000..13369f7 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/ja.js @@ -0,0 +1,243 @@ +"use strict"; +module.exports = { + "extension_name": "Cocos MCP サーバー", + "description": "Cocos Creator 3.8 用 AI MCP サーバー", + "panel_title": "MCP サーバー", + "open_panel": "MCP パネルを開く", + "start_server": "サーバー起動", + "stop_server": "サーバー停止", + "server_status": "サーバー状態", + "port": "ポート", + "settings": "設定", + "connected": "接続済み", + "disconnected": "未接続", + "server_running": "サーバーはポート {0} で実行中", + "server_stopped": "サーバーは停止しました", + "auto_start": "自動起動", + "debug_log": "デバッグログ", + "max_connections": "最大接続数", + "connection_info": "接続情報", + "http_url": "HTTP URL", + "copy": "コピー", + "save_settings": "設定を保存", + "settings_saved": "設定の保存に成功しました", + "server_started": "MCP サーバーが起動しました", + "server_stopped_msg": "MCP サーバーが停止しました", + "failed_to_start": "サーバーの起動に失敗しました", + "failed_to_stop": "サーバーの停止に失敗しました", + "failed_to_save": "設定の保存に失敗しました", + "url_copied": "HTTP URL をクリップボードにコピーしました", + "tool_manager": "ツールマネージャー", + "open_tool_manager": "ツールマネージャーを開く", + "create_config": "設定を作成", + "edit_config": "設定を編集", + "delete_config": "設定を削除", + "import_config": "設定をインポート", + "export_config": "設定をエクスポート", + "apply_config": "設定を適用", + "select_all": "全選択", + "deselect_all": "全解除", + "save_changes": "変更を保存", + "config_name": "設定名", + "config_description": "設定の説明", + "current_config": "現在の設定", + "tool_management": "ツール管理", + "total_tools": "ツール合計", + "enabled_tools": "有効", + "disabled_tools": "無効", + "no_config_selected": "設定が選択されていません", + "select_config_first": "先に設定を選択するか、新しく作成してください", + "config_created": "設定の作成に成功しました", + "config_updated": "設定の更新に成功しました", + "config_deleted": "設定の削除に成功しました", + "config_applied": "設定の適用に成功しました", + "config_exported": "設定のエクスポートに成功しました", + "config_imported": "設定のインポートに成功しました", + "confirm_delete": "削除の確認", + "delete_config_confirm": "設定 \"{0}\" を削除してもよろしいですか?この操作は元に戻せません。", + "max_config_slots_reached": "設定スロットの上限に達しました ({0})", + "invalid_config_format": "無効な設定形式", + "invalid_json_format": "無効なJSON形式または設定構造", + "server_tab": "サーバー", + "tools_tab": "ツール管理", + "available_tools": "利用可能なツール", + "scene_tools": "シーンツール", + "node_tools": "ノードツール", + "component_tools": "コンポーネントツール", + "prefab_tools": "プレハブツール", + "project_tools": "プロジェクトツール", + "debug_tools": "デバッグツール", + "preferences_tools": "環境設定", + "server_tools": "サーバーツール", + "broadcast_tools": "ブロードキャストツール", + "scene_advanced_tools": "高度なシーンツール", + "scene_view_tools": "シーンビューツール", + "reference_image_tools": "参照画像ツール", + "asset_advanced_tools": "高度なアセットツール", + "validation_tools": "検証ツール", + "brand_name": "LiDaxian MCP シリーズ", + "brand_subtitle": "LiDaxian MCP Series", + "contact_email": "メール", + "contact_wechat": "WeChat", + "contact_github": "GitHub", + "machine_id_label": "マシンID", + "purchase_renew": "購入 / 更新", + "purchase_url": "https://www.vberai.com/game-engines/cocos", + "auth_title": "ログイン方法を選択", + "auth_subtitle": "メールアドレスとアクティベーションコードを入力してください", + "email_label": "メールアドレス", + "activation_code_label": "アクティベーションコード", + "email_placeholder": "メールを入力", + "code_placeholder": "コードを入力 (XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)", + "activate_button": "アクティベート", + "deactivate_button": "無効化", + "activating": "アクティベート中...", + "deactivating": "無効化中...", + "license_status": "ライセンス状態", + "license_type": "ライセンス種別", + "expiry_date": "有効期限", + "days_remaining": "残り日数", + "days_unit": "日", + "permanent": "永久", + "expiry_warning_title": "ライセンスの有効期限が近づいています", + "expiry_warning_message": "ライセンスはあと {0} 日で期限切れになります。サービスの中断を防ぐため、更新してください。", + "renew_now": "今すぐ更新", + "i_understand": "了解しました", + "activation_success": "ライセンスのアクティベーションに成功しました!", + "activation_failed": "アクティベーション失敗: {0}", + "invalid_code": "無効なアクティベーションコード形式", + "purchase_license": "ライセンスを購入", + "renew_license": "ライセンスを更新", + "contact_support": "サポート", + "language_switch": "日本語", + "version_label": "バージョン", + "license_required": "サーバーの起動にはライセンスが必要です", + "license_check_failed": "ライセンスチェックに失敗しました", + + "status_stopped": "停止中", + "status_running": "実行中", + "status_error": "接続失敗", + "config_tab": "クイック設定", + "status_label": "状態", + "connections_count": "接続数", + "transport_protocol": "トランスポートプロトコル", + "transport_type": "トランスポートタイプ", + "mcp_url": "MCP URL", + "port_fallback_title": "ポート {0} が使用できないため、{1} に切り替えました", + "port_fallback_reason": "ポート {0} はシステムに予約されているか、ファイアウォール/ウイルス対策ソフトにブロックされています(Hyper-V、WSL2、Docker を導入した Windows でよく起こります)。サーバーは新しいポートで正常に動作しています。", + "port_fallback_resynced": "次のクライアントの設定を更新しました:{0}。新しいポートを有効にするには再起動してください。", + "port_fallback_no_clients": "AI クライアントを設定済みの場合は、「ワンクリック設定」で新しいポートに設定し直してください。", + "port_fallback_dismiss": "了解", + "port_label": "ポート", + "auto_start_label": "自動起動", + "debug_log_label": "デバッグログ", + "max_connections_label": "最大接続数", + "server_settings_title": "サーバー設定", + "server_status_title": "サーバー状態", + "connection_info_title": "接続情報", + "tools_count": "{0} ツール", + "tools_stats": "{0} ツール ({1} 有効 / {2} 無効)", + "quick_config_title": "MCP クライアントのクイック設定", + "quick_config_desc": "現在のMCPサーバーを主要AIエディターやCLIツールに自動設定します", + "server_info": "サーバー情報", + "server_name_label": "サーバー名", + "server_address": "サーバーアドレス", + "timeout_ms": "タイムアウト (ms)", + "auto_config_title": "自動設定 (IDE エディター + Codex CLI)", + "add_all": "すべて追加", + "remove_all": "すべて削除", + "auto_config_note": "ボタンをクリックして設定ファイルを自動変更 (Cursor, Windsurf, Trae, Codex CLI)", + "configured": "設定済み", + "not_configured": "未設定", + "auto_config_btn": "自動設定", + "delete_btn": "削除", + "copy_config": "設定をコピー", + "open_config": "設定を開く", + "manual_config_title": "手動設定 (Claude CLI + Gemini CLI)", + "refresh_commands": "コマンドを更新", + "manual_config_note": "以下のCLIツールはターミナルで手動実行が必要です", + "config_scope": "設定範囲:", + "scope_user": "ユーザーレベル", + "scope_project": "プロジェクトレベル", + "operation_log": "操作ログ", + "no_logs": "操作記録なし", + "copyright_line": "© 2025 LiDaxian - Cocos Creator MCP Server v{0}", + "protocol_line": "Streamable HTTP プロトコル (MCP 2025-03-26) 準拠", + "tools_copyright_line1": "ツールシステム - 13のインテントレベルツール、150+のエディター操作、アクションコードディスパッチ", + "config_copyright_line1": "クイック設定 - Cursor, Windsurf, Trae, Codex CLI, Claude CLI, Gemini CLI 対応", + "email_required": "メールとアクティベーションコードを入力してください", + "permanent_label": "永久", + "logout_button": "ログアウト", + "invite_code_label": "招待コード", + "copy_button": "コピー", + "change_code_button": "コード変更", + "change_code_title": "アクティベーションコードの変更", + "change_code_desc": "新しいアクティベーションコードを入力してください。現在のデバイスバインドは先に解除されます。", + "confirm_change": "確認", + "cancel": "キャンセル", + "code_required": "アクティベーションコードを入力してください", + "expiry_inline_warning": "まもなく期限切れ!", + "store_purchase_hint": "Cocos Store で本プラグインを購入済みの場合は、アクティベーションコードの取得にご連絡ください。", + "license_expired_title": "ライセンスの有効期限切れ", + "license_expired_message": "ライセンスの有効期限が切れたため、サーバーを起動できません。続行するには更新または再アクティベートしてください。", + "go_to_login": "ログインへ", + + "log_load_config_failed": "設定状態の読み込みに失敗: {0}", + "log_gen_cli_failed": "CLIコマンドの生成に失敗: {0}", + "log_add_client_failed": "{0} への追加に失敗: {1}", + "log_remove_client_failed": "{0} からの削除に失敗: {1}", + "quick_config_dialog_title": "ワンクリック設定", + "config_operation_failed": "設定操作に失敗しました", + "config_file_not_found_changed_path": "設定に失敗しました: 設定ファイルが見つかりません。{0} がカスタムパスにインストールされている可能性があります。", + "log_batch_add_failed": "一括追加に失敗: {0}", + "log_batch_remove_failed": "一括削除に失敗: {0}", + "log_copied_config": "{0} の設定をクリップボードにコピーしました", + "log_gen_config_failed": "設定の生成に失敗しました", + "log_copy_config_failed": "設定のコピーに失敗: {0}", + "log_copied_command": "{0} コマンドをクリップボードにコピーしました", + "log_copy_command_failed": "コマンドのコピーに失敗: {0}", + "log_opened_config_file": "設定ファイルを開きました: {0}", + "log_open_config_failed": "設定ファイルを開けませんでした", + "log_open_config_error": "設定ファイルを開けませんでした: {0}", + + "update_available_title": "アップデートがあります", + "update_new_version": "新バージョン v{0} がリリースされました!", + "update_now": "今すぐ更新", + "update_not_now": "後で", + "update_skip_version": "v{0} をスキップ", + "check_for_updates": "アップデートを確認", + "update_checking": "確認中...", + "update_no_update": "最新バージョンです", + "update_urgency_critical": "緊急アップデート", + "update_urgency_high": "重要なアップデート", + "update_current_version": "現在のバージョン: v{0}", + "update_changelog": "変更履歴", + "auth_choose_subtitle": "サインイン方法を選択して続行してください", + "update_changelog_empty": "このバージョンのリリースノートはありません。", + "subscription_login_title": "VberAI サブスクリプションアカウント", + "subscription_password_label": "パスワード", + "subscription_password_placeholder": "パスワードを入力", + "subscription_login_button": "VberAI アカウントでログイン", + "subscription_google_button": "Google でログイン", + "subscription_google_pending": "ブラウザを開きました。Google 認証を完了してから Cocos Creator に戻ってください。", + "subscription_google_pending_button": "Google 認証を待っています...", + "subscription_register_button": "VberAI アカウントを作成", + "subscription_logging_in": "ログイン中...", + "subscription_credentials_required": "メールアドレスとパスワードを入力してください", + "subscription_login_success": "サブスクリプションアカウントにログインしました", + "subscription_login_failed": "サブスクリプションのログインに失敗しました", + "subscription_not_entitled": "現在のサブスクリプションには Cocos 3.x MCP が含まれていません。続行するにはアップグレードしてください。", + "subscription_upgrade_to": "このプロダクトを使用するには「{0}」にアップグレードしてください", + "subscription_upgrade_action": "サブスクリプションをアップグレード", + "subscription_logged_in": "ログイン中のアカウント", + "subscription_choose_button": "VberAI サブスクリプションアカウント", + "license_choose_button": "アクティベーションコード", + "subscription_method_label": "サブスク", + "back_to_login_choices": "← 別のサインイン方法を選択", + "subscription_signed_out_other_device": "サインアウトしました:アカウントが別のデバイスで使用されました。", + "subscription_google_cancelled": "Google ログインがキャンセルされました。", + "subscription_request_timeout": "リクエストがタイムアウトしました。ネットワークを確認して再試行してください。", + "subscription_network_error": "ネットワーク接続に失敗しました。ネットワークを確認して再試行してください。", + "subscription_server_error": "サーバーから無効な応答が返されました。後でもう一度お試しください。", + "subscription_google_authorization_failed": "Google 認証が完了しませんでした。もう一度お試しください。" +}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/ko.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/ko.js new file mode 100644 index 0000000..f94637f --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/ko.js @@ -0,0 +1,243 @@ +"use strict"; +module.exports = { + "extension_name": "Cocos MCP 서버", + "description": "Cocos Creator 3.8용 AI MCP 서버", + "panel_title": "MCP 서버", + "open_panel": "MCP 패널 열기", + "start_server": "서버 시작", + "stop_server": "서버 중지", + "server_status": "서버 상태", + "port": "포트", + "settings": "설정", + "connected": "연결됨", + "disconnected": "연결 안 됨", + "server_running": "서버가 포트 {0}에서 실행 중입니다", + "server_stopped": "서버가 중지되었습니다", + "auto_start": "자동 시작", + "debug_log": "디버그 로그", + "max_connections": "최대 연결 수", + "connection_info": "연결 정보", + "http_url": "HTTP URL", + "copy": "복사", + "save_settings": "설정 저장", + "settings_saved": "설정이 저장되었습니다", + "server_started": "MCP 서버가 시작되었습니다", + "server_stopped_msg": "MCP 서버가 중지되었습니다", + "failed_to_start": "서버 시작 실패", + "failed_to_stop": "서버 중지 실패", + "failed_to_save": "설정 저장 실패", + "url_copied": "HTTP URL이 클립보드에 복사되었습니다", + "tool_manager": "도구 관리자", + "open_tool_manager": "도구 관리자 열기", + "create_config": "구성 생성", + "edit_config": "구성 편집", + "delete_config": "구성 삭제", + "import_config": "구성 가져오기", + "export_config": "구성 내보내기", + "apply_config": "구성 적용", + "select_all": "전체 선택", + "deselect_all": "전체 해제", + "save_changes": "변경 사항 저장", + "config_name": "구성 이름", + "config_description": "구성 설명", + "current_config": "현재 구성", + "tool_management": "도구 관리", + "total_tools": "전체 도구", + "enabled_tools": "활성화", + "disabled_tools": "비활성화", + "no_config_selected": "선택된 구성 없음", + "select_config_first": "먼저 구성을 선택하거나 새로 만들어 주세요", + "config_created": "구성이 생성되었습니다", + "config_updated": "구성이 업데이트되었습니다", + "config_deleted": "구성이 삭제되었습니다", + "config_applied": "구성이 적용되었습니다", + "config_exported": "구성이 내보내기되었습니다", + "config_imported": "구성이 가져오기되었습니다", + "confirm_delete": "삭제 확인", + "delete_config_confirm": "구성 \"{0}\"을(를) 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.", + "max_config_slots_reached": "최대 구성 슬롯에 도달했습니다 ({0})", + "invalid_config_format": "잘못된 구성 형식", + "invalid_json_format": "잘못된 JSON 형식 또는 구성 구조", + "server_tab": "서버", + "tools_tab": "도구 관리", + "available_tools": "사용 가능한 도구", + "scene_tools": "씬 도구", + "node_tools": "노드 도구", + "component_tools": "컴포넌트 도구", + "prefab_tools": "프리팹 도구", + "project_tools": "프로젝트 도구", + "debug_tools": "디버그 도구", + "preferences_tools": "환경설정", + "server_tools": "서버 도구", + "broadcast_tools": "브로드캐스트 도구", + "scene_advanced_tools": "고급 씬 도구", + "scene_view_tools": "씬 뷰 도구", + "reference_image_tools": "참조 이미지 도구", + "asset_advanced_tools": "고급 에셋 도구", + "validation_tools": "검증 도구", + "brand_name": "LiDaxian MCP 시리즈", + "brand_subtitle": "LiDaxian MCP Series", + "contact_email": "이메일", + "contact_wechat": "WeChat", + "contact_github": "GitHub", + "machine_id_label": "머신 코드", + "purchase_renew": "구매 / 갱신", + "purchase_url": "https://www.vberai.com/game-engines/cocos", + "auth_title": "로그인 방식 선택", + "auth_subtitle": "이메일과 활성화 코드를 입력하세요", + "email_label": "이메일 주소", + "activation_code_label": "활성화 코드", + "email_placeholder": "이메일 입력", + "code_placeholder": "코드 입력 (XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)", + "activate_button": "활성화", + "deactivate_button": "비활성화", + "activating": "활성화 중...", + "deactivating": "비활성화 중...", + "license_status": "라이선스 상태", + "license_type": "라이선스 유형", + "expiry_date": "만료일", + "days_remaining": "남은 일수", + "days_unit": "일", + "permanent": "영구", + "expiry_warning_title": "라이선스 만료 임박", + "expiry_warning_message": "라이선스가 {0}일 후 만료됩니다. 서비스 중단을 방지하려면 갱신해 주세요.", + "renew_now": "지금 갱신", + "i_understand": "확인했습니다", + "activation_success": "라이선스 활성화에 성공했습니다!", + "activation_failed": "활성화 실패: {0}", + "invalid_code": "잘못된 활성화 코드 형식", + "purchase_license": "라이선스 구매", + "renew_license": "라이선스 갱신", + "contact_support": "지원", + "language_switch": "한국어", + "version_label": "버전", + "license_required": "서버를 시작하려면 라이선스가 필요합니다", + "license_check_failed": "라이선스 확인 실패", + + "status_stopped": "중지됨", + "status_running": "실행 중", + "status_error": "연결 실패", + "config_tab": "빠른 설정", + "status_label": "상태", + "connections_count": "연결 수", + "transport_protocol": "전송 프로토콜", + "transport_type": "전송 유형", + "mcp_url": "MCP URL", + "port_fallback_title": "포트 {0}을(를) 사용할 수 없어 {1}(으)로 전환했습니다", + "port_fallback_reason": "포트 {0}이(가) 시스템에 예약되어 있거나 방화벽/백신에 의해 차단되었습니다(Hyper-V, WSL2 또는 Docker가 설치된 Windows에서 흔합니다). 서버는 새 포트에서 정상 실행 중입니다.", + "port_fallback_resynced": "다음 클라이언트의 설정을 업데이트했습니다: {0}. 새 포트를 적용하려면 다시 시작하세요.", + "port_fallback_no_clients": "AI 클라이언트를 설정한 적이 있다면 \"원클릭 설정\"에서 새 포트로 다시 설정하세요.", + "port_fallback_dismiss": "확인", + "port_label": "포트", + "auto_start_label": "자동 시작", + "debug_log_label": "디버그 로그", + "max_connections_label": "최대 연결 수", + "server_settings_title": "서버 설정", + "server_status_title": "서버 상태", + "connection_info_title": "연결 정보", + "tools_count": "{0}개 도구", + "tools_stats": "{0}개 도구 ({1} 활성화 / {2} 비활성화)", + "quick_config_title": "MCP 클라이언트 빠른 설정", + "quick_config_desc": "현재 MCP 서버를 주요 AI 에디터 및 CLI 도구에 자동 구성합니다", + "server_info": "서버 정보", + "server_name_label": "서버 이름", + "server_address": "서버 주소", + "timeout_ms": "타임아웃 (ms)", + "auto_config_title": "자동 구성 (IDE 에디터 + Codex CLI)", + "add_all": "전체 추가", + "remove_all": "전체 삭제", + "auto_config_note": "버튼을 클릭하여 구성 파일을 자동 수정합니다 (Cursor, Windsurf, Trae, Codex CLI)", + "configured": "구성됨", + "not_configured": "미구성", + "auto_config_btn": "자동 구성", + "delete_btn": "삭제", + "copy_config": "구성 복사", + "open_config": "구성 열기", + "manual_config_title": "수동 구성 (Claude CLI + Gemini CLI)", + "refresh_commands": "명령 새로고침", + "manual_config_note": "다음 CLI 도구는 터미널에서 수동 실행이 필요합니다", + "config_scope": "구성 범위:", + "scope_user": "사용자 수준", + "scope_project": "프로젝트 수준", + "operation_log": "작업 로그", + "no_logs": "작업 기록 없음", + "copyright_line": "© 2025 LiDaxian - Cocos Creator MCP Server v{0}", + "protocol_line": "Streamable HTTP 프로토콜 (MCP 2025-03-26) 기반", + "tools_copyright_line1": "도구 시스템 - 13개 인텐트 레벨 도구, 150+ 에디터 작업, 액션 코드 디스패치", + "config_copyright_line1": "빠른 설정 - Cursor, Windsurf, Trae, Codex CLI, Claude CLI, Gemini CLI 지원", + "email_required": "이메일과 활성화 코드를 입력하세요", + "permanent_label": "영구", + "logout_button": "로그아웃", + "invite_code_label": "초대 코드", + "copy_button": "복사", + "change_code_button": "코드 변경", + "change_code_title": "활성화 코드 변경", + "change_code_desc": "새 활성화 코드를 입력하세요. 현재 기기 바인딩이 먼저 해제됩니다.", + "confirm_change": "확인", + "cancel": "취소", + "code_required": "활성화 코드를 입력하세요", + "expiry_inline_warning": "곧 만료됩니다!", + "store_purchase_hint": "Cocos Store에서 본 플러그인을 구매하셨다면, 활성화 코드를 받으시려면 연락해 주세요.", + "license_expired_title": "라이선스 만료", + "license_expired_message": "라이선스가 만료되어 서버를 시작할 수 없습니다. 계속 사용하려면 갱신하거나 다시 활성화하세요.", + "go_to_login": "로그인으로 이동", + + "log_load_config_failed": "구성 상태 로드 실패: {0}", + "log_gen_cli_failed": "CLI 명령 생성 실패: {0}", + "log_add_client_failed": "{0}에 추가 실패: {1}", + "log_remove_client_failed": "{0}에서 삭제 실패: {1}", + "quick_config_dialog_title": "원클릭 구성", + "config_operation_failed": "구성 작업 실패", + "config_file_not_found_changed_path": "구성 실패: 구성 파일을 찾을 수 없습니다. {0}이 사용자 지정 경로에 설치되었을 수 있습니다.", + "log_batch_add_failed": "일괄 추가 실패: {0}", + "log_batch_remove_failed": "일괄 삭제 실패: {0}", + "log_copied_config": "{0} 구성을 클립보드에 복사했습니다", + "log_gen_config_failed": "구성 생성 실패", + "log_copy_config_failed": "구성 복사 실패: {0}", + "log_copied_command": "{0} 명령을 클립보드에 복사했습니다", + "log_copy_command_failed": "명령 복사 실패: {0}", + "log_opened_config_file": "구성 파일을 열었습니다: {0}", + "log_open_config_failed": "구성 파일을 열 수 없습니다", + "log_open_config_error": "구성 파일을 열 수 없습니다: {0}", + + "update_available_title": "업데이트 가능", + "update_new_version": "새 버전 v{0}이 출시되었습니다!", + "update_now": "지금 업데이트", + "update_not_now": "나중에", + "update_skip_version": "v{0} 건너뛰기", + "check_for_updates": "업데이트 확인", + "update_checking": "확인 중...", + "update_no_update": "최신 버전입니다", + "update_urgency_critical": "긴급 업데이트", + "update_urgency_high": "중요 업데이트", + "update_current_version": "현재 버전: v{0}", + "update_changelog": "변경 사항", + "auth_choose_subtitle": "계속하려면 로그인 방법을 선택하세요", + "update_changelog_empty": "이 버전에 대한 릴리스 노트가 없습니다.", + "subscription_login_title": "VberAI 구독 계정", + "subscription_password_label": "비밀번호", + "subscription_password_placeholder": "비밀번호 입력", + "subscription_login_button": "VberAI 계정으로 로그인", + "subscription_google_button": "Google로 로그인", + "subscription_google_pending": "브라우저가 열렸습니다. Google 인증을 완료한 후 Cocos Creator로 돌아오세요.", + "subscription_google_pending_button": "Google 인증을 기다리는 중...", + "subscription_register_button": "VberAI 계정 만들기", + "subscription_logging_in": "로그인 중...", + "subscription_credentials_required": "이메일과 비밀번호를 입력하세요", + "subscription_login_success": "구독 계정에 로그인했습니다", + "subscription_login_failed": "구독 로그인에 실패했습니다", + "subscription_not_entitled": "현재 구독에는 Cocos 3.x MCP가 포함되어 있지 않습니다. 계속하려면 업그레이드하세요.", + "subscription_upgrade_to": "이 제품을 사용하려면 「{0}」(으)로 업그레이드하세요", + "subscription_upgrade_action": "구독 업그레이드", + "subscription_logged_in": "로그인된 계정", + "subscription_choose_button": "VberAI 구독 계정", + "license_choose_button": "활성화 코드", + "subscription_method_label": "구독 계정", + "back_to_login_choices": "← 다른 로그인 방법 선택", + "subscription_signed_out_other_device": "로그아웃됨: 계정이 다른 기기에서 사용되었습니다.", + "subscription_google_cancelled": "Google 로그인이 취소되었습니다.", + "subscription_request_timeout": "요청 시간이 초과되었습니다. 네트워크를 확인하고 다시 시도하세요.", + "subscription_network_error": "네트워크 연결에 실패했습니다. 네트워크를 확인하고 다시 시도하세요.", + "subscription_server_error": "서버가 잘못된 응답을 반환했습니다. 나중에 다시 시도하세요.", + "subscription_google_authorization_failed": "Google 인증이 완료되지 않았습니다. 다시 시도하세요." +}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/pt.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/pt.js new file mode 100644 index 0000000..932ee1a --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/pt.js @@ -0,0 +1,243 @@ +"use strict"; +module.exports = { + "extension_name": "Cocos MCP Servidor", + "description": "Servidor AI MCP para Cocos Creator 3.8", + "panel_title": "Servidor MCP", + "open_panel": "Abrir painel MCP", + "start_server": "Iniciar servidor", + "stop_server": "Parar servidor", + "server_status": "Estado do servidor", + "port": "Porta", + "settings": "Configurações", + "connected": "Conectado", + "disconnected": "Desconectado", + "server_running": "O servidor está em execução na porta {0}", + "server_stopped": "O servidor foi parado", + "auto_start": "Início automático", + "debug_log": "Log de depuração", + "max_connections": "Conexões máx.", + "connection_info": "Info de conexão", + "http_url": "URL HTTP", + "copy": "Copiar", + "save_settings": "Salvar", + "settings_saved": "Configurações salvas com sucesso", + "server_started": "Servidor MCP iniciado", + "server_stopped_msg": "Servidor MCP parado", + "failed_to_start": "Falha ao iniciar o servidor", + "failed_to_stop": "Falha ao parar o servidor", + "failed_to_save": "Falha ao salvar configurações", + "url_copied": "URL HTTP copiada para a área de transferência", + "tool_manager": "Gerenciador de ferramentas", + "open_tool_manager": "Abrir gerenciador de ferramentas", + "create_config": "Criar configuração", + "edit_config": "Editar configuração", + "delete_config": "Excluir configuração", + "import_config": "Importar configuração", + "export_config": "Exportar configuração", + "apply_config": "Aplicar configuração", + "select_all": "Selecionar tudo", + "deselect_all": "Desmarcar tudo", + "save_changes": "Salvar alterações", + "config_name": "Nome da configuração", + "config_description": "Descrição da configuração", + "current_config": "Configuração atual", + "tool_management": "Gestão de ferramentas", + "total_tools": "Total de ferramentas", + "enabled_tools": "Ativadas", + "disabled_tools": "Desativadas", + "no_config_selected": "Nenhuma configuração selecionada", + "select_config_first": "Por favor, selecione ou crie uma configuração primeiro", + "config_created": "Configuração criada com sucesso", + "config_updated": "Configuração atualizada com sucesso", + "config_deleted": "Configuração excluída com sucesso", + "config_applied": "Configuração aplicada com sucesso", + "config_exported": "Configuração exportada com sucesso", + "config_imported": "Configuração importada com sucesso", + "confirm_delete": "Confirmar exclusão", + "delete_config_confirm": "Tem certeza de que deseja excluir a configuração \"{0}\"? Esta ação não pode ser desfeita.", + "max_config_slots_reached": "Número máximo de configurações atingido ({0})", + "invalid_config_format": "Formato de configuração inválido", + "invalid_json_format": "Formato JSON ou estrutura de configuração inválidos", + "server_tab": "Servidor", + "tools_tab": "Gestão de ferramentas", + "available_tools": "Ferramentas disponíveis", + "scene_tools": "Ferramentas de cena", + "node_tools": "Ferramentas de nó", + "component_tools": "Ferramentas de componente", + "prefab_tools": "Ferramentas de prefab", + "project_tools": "Ferramentas de projeto", + "debug_tools": "Ferramentas de depuração", + "preferences_tools": "Preferências", + "server_tools": "Ferramentas de servidor", + "broadcast_tools": "Ferramentas de broadcast", + "scene_advanced_tools": "Ferramentas de cena avançadas", + "scene_view_tools": "Ferramentas de visualização de cena", + "reference_image_tools": "Ferramentas de imagem de referência", + "asset_advanced_tools": "Ferramentas de assets avançadas", + "validation_tools": "Ferramentas de validação", + "brand_name": "LiDaxian MCP Series", + "brand_subtitle": "LiDaxian MCP Series", + "contact_email": "E-mail", + "contact_wechat": "WeChat", + "contact_github": "GitHub", + "machine_id_label": "Código da máquina", + "purchase_renew": "Comprar / Renovar", + "purchase_url": "https://www.vberai.com/game-engines/cocos", + "auth_title": "Escolher método de login", + "auth_subtitle": "Digite seu e-mail e código de ativação", + "email_label": "Endereço de e-mail", + "activation_code_label": "Código de ativação", + "email_placeholder": "Digite o e-mail", + "code_placeholder": "Digite o código (XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)", + "activate_button": "Ativar", + "deactivate_button": "Desativar", + "activating": "Ativando...", + "deactivating": "Desativando...", + "license_status": "Estado da licença", + "license_type": "Tipo de licença", + "expiry_date": "Data de expiração", + "days_remaining": "Dias restantes", + "days_unit": "d", + "permanent": "Permanente", + "expiry_warning_title": "Licença expirando em breve", + "expiry_warning_message": "Sua licença expirará em {0} dias. Por favor, renove para evitar interrupções.", + "renew_now": "Renovar agora", + "i_understand": "Entendido", + "activation_success": "Licença ativada com sucesso!", + "activation_failed": "Falha na ativação: {0}", + "invalid_code": "Formato de código de ativação inválido", + "purchase_license": "Comprar licença", + "renew_license": "Renovar licença", + "contact_support": "Suporte", + "language_switch": "Português", + "version_label": "Versão", + "license_required": "É necessária uma licença para iniciar o servidor", + "license_check_failed": "Falha na verificação de licença", + + "status_stopped": "Parado", + "status_running": "Em execução", + "status_error": "Falha na conexão", + "config_tab": "Config rápida", + "status_label": "Estado", + "connections_count": "Conexões", + "transport_protocol": "Protocolo de transporte", + "transport_type": "Tipo de transporte", + "mcp_url": "URL MCP", + "port_fallback_title": "Porta {0} indisponível — alterada para {1}", + "port_fallback_reason": "A porta {0} está reservada pelo sistema ou bloqueada por firewall/antivírus (comum no Windows com Hyper-V, WSL2 ou Docker). O servidor está funcionando normalmente na nova porta.", + "port_fallback_resynced": "Configuração atualizada para: {0}. Reinicie esses clientes para que a nova porta tenha efeito.", + "port_fallback_no_clients": "Se você já configurou clientes de IA, configure-os novamente para a nova porta pela configuração com um clique.", + "port_fallback_dismiss": "Entendi", + "port_label": "Porta", + "auto_start_label": "Início auto", + "debug_log_label": "Log de depuração", + "max_connections_label": "Conexões máx.", + "server_settings_title": "Configurações do servidor", + "server_status_title": "Estado do servidor", + "connection_info_title": "Info de conexão", + "tools_count": "{0} ferramentas", + "tools_stats": "{0} ferramentas ({1} ativadas / {2} desativadas)", + "quick_config_title": "Config rápida de clientes MCP", + "quick_config_desc": "Configurar automaticamente o servidor MCP em editores AI e ferramentas CLI", + "server_info": "Info do servidor", + "server_name_label": "Nome do servidor", + "server_address": "Endereço do servidor", + "timeout_ms": "Tempo limite (ms)", + "auto_config_title": "Config auto (IDE + Codex CLI)", + "add_all": "Adicionar tudo", + "remove_all": "Remover tudo", + "auto_config_note": "Clique para modificar automaticamente os arquivos de config (Cursor, Windsurf, Trae, Codex CLI)", + "configured": "Configurado", + "not_configured": "Não configurado", + "auto_config_btn": "Config auto", + "delete_btn": "Excluir", + "copy_config": "Copiar config", + "open_config": "Abrir config", + "manual_config_title": "Config manual (Claude CLI + Gemini CLI)", + "refresh_commands": "Atualizar comandos", + "manual_config_note": "As seguintes ferramentas CLI requerem execução manual no terminal", + "config_scope": "Escopo de config:", + "scope_user": "Nível de usuário", + "scope_project": "Nível de projeto", + "operation_log": "Log de operações", + "no_logs": "Sem registros", + "copyright_line": "© 2025 LiDaxian - Cocos Creator MCP Server v{0}", + "protocol_line": "Baseado no protocolo Streamable HTTP (MCP 2025-03-26)", + "tools_copyright_line1": "Sistema de ferramentas - 13 ferramentas intent-level, 150+ operações de editor, despacho de código de ação", + "config_copyright_line1": "Config rápida - Suporta Cursor, Windsurf, Trae, Codex CLI, Claude CLI, Gemini CLI", + "email_required": "Por favor, digite e-mail e código de ativação", + "permanent_label": "Permanente", + "logout_button": "Sair", + "invite_code_label": "Código de convite", + "copy_button": "Copiar", + "change_code_button": "Alterar código", + "change_code_title": "Alterar código de ativação", + "change_code_desc": "Digite seu novo código de ativação. A vinculação do dispositivo atual será liberada primeiro.", + "confirm_change": "Confirmar", + "cancel": "Cancelar", + "code_required": "Por favor, digite um código de ativação", + "expiry_inline_warning": "Expirando em breve!", + "store_purchase_hint": "Se você comprou este plugin na Cocos Store, entre em contato comigo para obter seu código de ativação.", + "license_expired_title": "Licença expirada", + "license_expired_message": "Sua licença expirou e o servidor não pode ser iniciado. Renove ou reative para continuar.", + "go_to_login": "Ir para login", + + "log_load_config_failed": "Falha ao carregar estado da config: {0}", + "log_gen_cli_failed": "Falha ao gerar comandos CLI: {0}", + "log_add_client_failed": "Falha ao adicionar a {0}: {1}", + "log_remove_client_failed": "Falha ao remover de {0}: {1}", + "quick_config_dialog_title": "Configuração com um clique", + "config_operation_failed": "Falha na operação de configuração", + "config_file_not_found_changed_path": "Falha na configuração: arquivo de configuração não encontrado. {0} pode ter sido instalado em um caminho personalizado.", + "log_batch_add_failed": "Falha na adição em lote: {0}", + "log_batch_remove_failed": "Falha na remoção em lote: {0}", + "log_copied_config": "Config de {0} copiada para a área de transferência", + "log_gen_config_failed": "Falha ao gerar config", + "log_copy_config_failed": "Falha ao copiar config: {0}", + "log_copied_command": "Comando {0} copiado para a área de transferência", + "log_copy_command_failed": "Falha ao copiar comando: {0}", + "log_opened_config_file": "Arquivo de config aberto: {0}", + "log_open_config_failed": "Falha ao abrir arquivo de config", + "log_open_config_error": "Falha ao abrir arquivo de config: {0}", + + "update_available_title": "Atualização disponível", + "update_new_version": "A nova versão v{0} está disponível!", + "update_now": "Atualizar agora", + "update_not_now": "Agora não", + "update_skip_version": "Pular v{0}", + "check_for_updates": "Verificar atualizações", + "update_checking": "Verificando...", + "update_no_update": "Você está usando a versão mais recente", + "update_urgency_critical": "Atualização crítica", + "update_urgency_high": "Atualização importante", + "update_current_version": "Versão atual: v{0}", + "update_changelog": "Registro de alterações", + "auth_choose_subtitle": "Escolha um método de login para continuar", + "update_changelog_empty": "Nenhuma nota de versão foi fornecida para esta versão.", + "subscription_login_title": "Conta de assinatura VberAI", + "subscription_password_label": "Senha", + "subscription_password_placeholder": "Digite a senha", + "subscription_login_button": "Entrar com VberAI", + "subscription_google_button": "Entrar com o Google", + "subscription_google_pending": "Navegador aberto. Conclua a autorização do Google e volte ao Cocos Creator.", + "subscription_google_pending_button": "Aguardando a autorização do Google...", + "subscription_register_button": "Criar uma conta VberAI", + "subscription_logging_in": "Entrando...", + "subscription_credentials_required": "Digite o e-mail e a senha", + "subscription_login_success": "Conectado à conta de assinatura", + "subscription_login_failed": "Falha ao entrar na assinatura", + "subscription_not_entitled": "Sua assinatura atual não inclui o Cocos 3.x MCP. Faça upgrade para continuar.", + "subscription_upgrade_to": "Faça upgrade para \"{0}\" para usar este produto", + "subscription_upgrade_action": "Fazer upgrade da assinatura", + "subscription_logged_in": "Conta conectada", + "subscription_choose_button": "Conta de assinatura VberAI", + "license_choose_button": "Código de ativação", + "subscription_method_label": "Assinatura", + "back_to_login_choices": "← Escolher outro método de login", + "subscription_signed_out_other_device": "Desconectado: sua conta foi usada em outro dispositivo.", + "subscription_google_cancelled": "O login com o Google foi cancelado.", + "subscription_request_timeout": "A solicitação expirou. Verifique sua rede e tente novamente.", + "subscription_network_error": "Falha na conexão de rede. Verifique sua rede e tente novamente.", + "subscription_server_error": "O servidor retornou uma resposta inválida. Tente novamente mais tarde.", + "subscription_google_authorization_failed": "A autorização do Google não foi concluída. Tente novamente." +}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/ru.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/ru.js new file mode 100644 index 0000000..52ae8d4 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/ru.js @@ -0,0 +1,243 @@ +"use strict"; +module.exports = { + "extension_name": "Cocos MCP Сервер", + "description": "AI MCP сервер для Cocos Creator 3.8", + "panel_title": "MCP Сервер", + "open_panel": "Открыть панель MCP", + "start_server": "Запустить сервер", + "stop_server": "Остановить сервер", + "server_status": "Состояние сервера", + "port": "Порт", + "settings": "Настройки", + "connected": "Подключено", + "disconnected": "Отключено", + "server_running": "Сервер работает на порту {0}", + "server_stopped": "Сервер остановлен", + "auto_start": "Автозапуск", + "debug_log": "Журнал отладки", + "max_connections": "Макс. подключений", + "connection_info": "Информация о подключении", + "http_url": "HTTP URL", + "copy": "Копировать", + "save_settings": "Сохранить", + "settings_saved": "Настройки успешно сохранены", + "server_started": "MCP сервер запущен", + "server_stopped_msg": "MCP сервер остановлен", + "failed_to_start": "Не удалось запустить сервер", + "failed_to_stop": "Не удалось остановить сервер", + "failed_to_save": "Не удалось сохранить настройки", + "url_copied": "HTTP URL скопирован в буфер обмена", + "tool_manager": "Менеджер инструментов", + "open_tool_manager": "Открыть менеджер инструментов", + "create_config": "Создать конфигурацию", + "edit_config": "Редактировать конфигурацию", + "delete_config": "Удалить конфигурацию", + "import_config": "Импортировать конфигурацию", + "export_config": "Экспортировать конфигурацию", + "apply_config": "Применить конфигурацию", + "select_all": "Выбрать все", + "deselect_all": "Снять все", + "save_changes": "Сохранить изменения", + "config_name": "Имя конфигурации", + "config_description": "Описание конфигурации", + "current_config": "Текущая конфигурация", + "tool_management": "Управление инструментами", + "total_tools": "Всего инструментов", + "enabled_tools": "Включено", + "disabled_tools": "Отключено", + "no_config_selected": "Конфигурация не выбрана", + "select_config_first": "Пожалуйста, сначала выберите или создайте конфигурацию", + "config_created": "Конфигурация успешно создана", + "config_updated": "Конфигурация успешно обновлена", + "config_deleted": "Конфигурация успешно удалена", + "config_applied": "Конфигурация успешно применена", + "config_exported": "Конфигурация успешно экспортирована", + "config_imported": "Конфигурация успешно импортирована", + "confirm_delete": "Подтвердить удаление", + "delete_config_confirm": "Вы уверены, что хотите удалить конфигурацию \"{0}\"? Это действие нельзя отменить.", + "max_config_slots_reached": "Достигнуто максимальное количество конфигураций ({0})", + "invalid_config_format": "Недопустимый формат конфигурации", + "invalid_json_format": "Недопустимый формат JSON или структура конфигурации", + "server_tab": "Сервер", + "tools_tab": "Управление инструментами", + "available_tools": "Доступные инструменты", + "scene_tools": "Инструменты сцены", + "node_tools": "Инструменты узлов", + "component_tools": "Инструменты компонентов", + "prefab_tools": "Инструменты префабов", + "project_tools": "Инструменты проекта", + "debug_tools": "Инструменты отладки", + "preferences_tools": "Настройки", + "server_tools": "Серверные инструменты", + "broadcast_tools": "Инструменты трансляции", + "scene_advanced_tools": "Расширенные инструменты сцены", + "scene_view_tools": "Инструменты просмотра сцены", + "reference_image_tools": "Инструменты эталонных изображений", + "asset_advanced_tools": "Расширенные инструменты ресурсов", + "validation_tools": "Инструменты валидации", + "brand_name": "LiDaxian MCP Series", + "brand_subtitle": "LiDaxian MCP Series", + "contact_email": "Эл. почта", + "contact_wechat": "WeChat", + "contact_github": "GitHub", + "machine_id_label": "Код машины", + "purchase_renew": "Купить / Продлить", + "purchase_url": "https://www.vberai.com/game-engines/cocos", + "auth_title": "Выберите способ входа", + "auth_subtitle": "Введите email и код активации", + "email_label": "Адрес электронной почты", + "activation_code_label": "Код активации", + "email_placeholder": "Введите email", + "code_placeholder": "Введите код (XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)", + "activate_button": "Активировать", + "deactivate_button": "Деактивировать", + "activating": "Активация...", + "deactivating": "Деактивация...", + "license_status": "Состояние лицензии", + "license_type": "Тип лицензии", + "expiry_date": "Дата истечения", + "days_remaining": "Осталось дней", + "days_unit": "д", + "permanent": "Бессрочная", + "expiry_warning_title": "Срок лицензии скоро истекает", + "expiry_warning_message": "Ваша лицензия истекает через {0} дней. Пожалуйста, продлите, чтобы избежать прерывания.", + "renew_now": "Продлить сейчас", + "i_understand": "Понятно", + "activation_success": "Лицензия успешно активирована!", + "activation_failed": "Ошибка активации: {0}", + "invalid_code": "Неверный формат кода активации", + "purchase_license": "Купить лицензию", + "renew_license": "Продлить лицензию", + "contact_support": "Поддержка", + "language_switch": "Русский", + "version_label": "Версия", + "license_required": "Для запуска сервера требуется лицензия", + "license_check_failed": "Ошибка проверки лицензии", + + "status_stopped": "Остановлен", + "status_running": "Работает", + "status_error": "Ошибка подключения", + "config_tab": "Быстрая настройка", + "status_label": "Состояние", + "connections_count": "Подключения", + "transport_protocol": "Транспортный протокол", + "transport_type": "Тип транспорта", + "mcp_url": "MCP URL", + "port_fallback_title": "Порт {0} недоступен — выполнен переход на {1}", + "port_fallback_reason": "Порт {0} зарезервирован системой или заблокирован брандмауэром/антивирусом (часто встречается в Windows с Hyper-V, WSL2 или Docker). Сервер нормально работает на новом порту.", + "port_fallback_resynced": "Обновлена конфигурация для: {0}. Перезапустите эти клиенты, чтобы новый порт вступил в силу.", + "port_fallback_no_clients": "Если вы уже настраивали ИИ-клиенты, перенастройте их на новый порт через конфигурацию в один клик.", + "port_fallback_dismiss": "Понятно", + "port_label": "Порт", + "auto_start_label": "Автозапуск", + "debug_log_label": "Журнал отладки", + "max_connections_label": "Макс. подключений", + "server_settings_title": "Настройки сервера", + "server_status_title": "Состояние сервера", + "connection_info_title": "Информация о подключении", + "tools_count": "{0} инструментов", + "tools_stats": "{0} инструментов ({1} включено / {2} отключено)", + "quick_config_title": "Быстрая настройка клиентов MCP", + "quick_config_desc": "Автоматическая настройка MCP сервера в AI-редакторах и CLI-инструментах", + "server_info": "Информация о сервере", + "server_name_label": "Имя сервера", + "server_address": "Адрес сервера", + "timeout_ms": "Тайм-аут (мс)", + "auto_config_title": "Автонастройка (IDE + Codex CLI)", + "add_all": "Добавить все", + "remove_all": "Удалить все", + "auto_config_note": "Нажмите для автоматического изменения файлов конфигурации (Cursor, Windsurf, Trae, Codex CLI)", + "configured": "Настроено", + "not_configured": "Не настроено", + "auto_config_btn": "Автонастройка", + "delete_btn": "Удалить", + "copy_config": "Копировать конфиг", + "open_config": "Открыть конфиг", + "manual_config_title": "Ручная настройка (Claude CLI + Gemini CLI)", + "refresh_commands": "Обновить команды", + "manual_config_note": "Следующие CLI-инструменты требуют ручного выполнения в терминале", + "config_scope": "Область конфигурации:", + "scope_user": "Уровень пользователя", + "scope_project": "Уровень проекта", + "operation_log": "Журнал операций", + "no_logs": "Нет записей", + "copyright_line": "© 2025 LiDaxian - Cocos Creator MCP Server v{0}", + "protocol_line": "На основе протокола Streamable HTTP (MCP 2025-03-26)", + "tools_copyright_line1": "Система инструментов - 13 intent-level инструментов, 150+ операций редактора, диспетчеризация action-кодов", + "config_copyright_line1": "Быстрая настройка - Поддержка Cursor, Windsurf, Trae, Codex CLI, Claude CLI, Gemini CLI", + "email_required": "Пожалуйста, введите email и код активации", + "permanent_label": "Бессрочная", + "logout_button": "Выйти", + "invite_code_label": "Код приглашения", + "copy_button": "Копировать", + "change_code_button": "Сменить код", + "change_code_title": "Смена кода активации", + "change_code_desc": "Введите новый код активации. Привязка текущего устройства будет сначала снята.", + "confirm_change": "Подтвердить", + "cancel": "Отмена", + "code_required": "Пожалуйста, введите код активации", + "expiry_inline_warning": "Скоро истекает!", + "store_purchase_hint": "Если вы приобрели этот плагин в Cocos Store, свяжитесь со мной для получения кода активации.", + "license_expired_title": "Лицензия истекла", + "license_expired_message": "Ваша лицензия истекла, сервер не может быть запущен. Пожалуйста, продлите или повторно активируйте для продолжения.", + "go_to_login": "Перейти к входу", + + "log_load_config_failed": "Не удалось загрузить состояние конфигурации: {0}", + "log_gen_cli_failed": "Не удалось сгенерировать команды CLI: {0}", + "log_add_client_failed": "Не удалось добавить в {0}: {1}", + "log_remove_client_failed": "Не удалось удалить из {0}: {1}", + "quick_config_dialog_title": "Настройка в один клик", + "config_operation_failed": "Операция настройки не удалась", + "config_file_not_found_changed_path": "Ошибка настройки: файл конфигурации не найден. Возможно, {0} установлен в пользовательский путь.", + "log_batch_add_failed": "Пакетное добавление не удалось: {0}", + "log_batch_remove_failed": "Пакетное удаление не удалось: {0}", + "log_copied_config": "Конфигурация {0} скопирована в буфер обмена", + "log_gen_config_failed": "Не удалось сгенерировать конфигурацию", + "log_copy_config_failed": "Не удалось скопировать конфигурацию: {0}", + "log_copied_command": "Команда {0} скопирована в буфер обмена", + "log_copy_command_failed": "Не удалось скопировать команду: {0}", + "log_opened_config_file": "Файл конфигурации открыт: {0}", + "log_open_config_failed": "Не удалось открыть файл конфигурации", + "log_open_config_error": "Не удалось открыть файл конфигурации: {0}", + + "update_available_title": "Доступно обновление", + "update_new_version": "Новая версия v{0} доступна!", + "update_now": "Обновить сейчас", + "update_not_now": "Позже", + "update_skip_version": "Пропустить v{0}", + "check_for_updates": "Проверить обновления", + "update_checking": "Проверка...", + "update_no_update": "У вас последняя версия", + "update_urgency_critical": "Критическое обновление", + "update_urgency_high": "Важное обновление", + "update_current_version": "Текущая версия: v{0}", + "update_changelog": "Список изменений", + "auth_choose_subtitle": "Выберите способ входа, чтобы продолжить", + "update_changelog_empty": "Для этой версии не указаны примечания к выпуску.", + "subscription_login_title": "Аккаунт подписки VberAI", + "subscription_password_label": "Пароль", + "subscription_password_placeholder": "Введите пароль", + "subscription_login_button": "Войти через VberAI", + "subscription_google_button": "Войти через Google", + "subscription_google_pending": "Браузер открыт. Завершите авторизацию Google, затем вернитесь в Cocos Creator.", + "subscription_google_pending_button": "Ожидание авторизации Google...", + "subscription_register_button": "Создать аккаунт VberAI", + "subscription_logging_in": "Вход...", + "subscription_credentials_required": "Введите адрес электронной почты и пароль", + "subscription_login_success": "Вход в аккаунт подписки выполнен", + "subscription_login_failed": "Не удалось войти в аккаунт подписки", + "subscription_not_entitled": "Ваша текущая подписка не включает Cocos 3.x MCP. Оформите повышение, чтобы продолжить.", + "subscription_upgrade_to": "Перейдите на «{0}», чтобы использовать этот продукт", + "subscription_upgrade_action": "Повысить подписку", + "subscription_logged_in": "Вошедший аккаунт", + "subscription_choose_button": "Аккаунт подписки VberAI", + "license_choose_button": "Код активации", + "subscription_method_label": "Подписка", + "back_to_login_choices": "← Выбрать другой способ входа", + "subscription_signed_out_other_device": "Выполнен выход: ваш аккаунт использовался на другом устройстве.", + "subscription_google_cancelled": "Вход через Google отменён.", + "subscription_request_timeout": "Время ожидания запроса истекло. Проверьте сеть и повторите попытку.", + "subscription_network_error": "Сбой сетевого подключения. Проверьте сеть и повторите попытку.", + "subscription_server_error": "Сервер вернул недопустимый ответ. Повторите попытку позже.", + "subscription_google_authorization_failed": "Авторизация Google не завершена. Повторите попытку." +}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/vi.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/vi.js new file mode 100644 index 0000000..d261677 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/vi.js @@ -0,0 +1,244 @@ +"use strict"; + +module.exports = { + "extension_name": "Cocos MCP Server", + "description": "Máy chủ AI MCP cho Cocos Creator 3.8", + "panel_title": "Máy chủ MCP", + "open_panel": "Mở bảng điều khiển MCP", + "start_server": "Khởi động máy chủ", + "stop_server": "Dừng máy chủ", + "server_status": "Trạng thái máy chủ", + "port": "Cổng", + "settings": "Cài đặt", + "connected": "Đã kết nối", + "disconnected": "Đã ngắt kết nối", + "server_running": "Máy chủ đang chạy trên cổng {0}", + "server_stopped": "Máy chủ đã dừng", + "auto_start": "Tự động khởi động", + "debug_log": "Ghi nhật ký gỡ lỗi", + "max_connections": "Số kết nối tối đa", + "connection_info": "Thông tin kết nối", + "http_url": "HTTP URL", + "copy": "Sao chép", + "save_settings": "Lưu cài đặt", + "settings_saved": "Đã lưu cài đặt thành công", + "server_started": "Máy chủ MCP đã khởi động", + "server_stopped_msg": "Máy chủ MCP đã dừng", + "failed_to_start": "Không thể khởi động máy chủ", + "failed_to_stop": "Không thể dừng máy chủ", + "failed_to_save": "Không thể lưu cài đặt", + "url_copied": "Đã sao chép HTTP URL vào bảng nhớ tạm", + "tool_manager": "Trình quản lý công cụ", + "open_tool_manager": "Mở trình quản lý công cụ", + "create_config": "Tạo cấu hình", + "edit_config": "Chỉnh sửa cấu hình", + "delete_config": "Xóa cấu hình", + "import_config": "Nhập cấu hình", + "export_config": "Xuất cấu hình", + "apply_config": "Áp dụng cấu hình", + "select_all": "Chọn tất cả", + "deselect_all": "Bỏ chọn tất cả", + "save_changes": "Lưu thay đổi", + "config_name": "Tên cấu hình", + "config_description": "Mô tả cấu hình", + "current_config": "Cấu hình hiện tại", + "tool_management": "Quản lý công cụ", + "total_tools": "Tổng số công cụ", + "enabled_tools": "Đã bật", + "disabled_tools": "Đã tắt", + "no_config_selected": "Chưa chọn cấu hình", + "select_config_first": "Vui lòng chọn một cấu hình hoặc tạo cấu hình mới trước", + "config_created": "Đã tạo cấu hình thành công", + "config_updated": "Đã cập nhật cấu hình thành công", + "config_deleted": "Đã xóa cấu hình thành công", + "config_applied": "Đã áp dụng cấu hình thành công", + "config_exported": "Đã xuất cấu hình thành công", + "config_imported": "Đã nhập cấu hình thành công", + "confirm_delete": "Xác nhận xóa", + "delete_config_confirm": "Bạn có chắc chắn muốn xóa cấu hình \"{0}\" không? Hành động này không thể hoàn tác.", + "max_config_slots_reached": "Đã đạt số lượng vị trí cấu hình tối đa ({0})", + "invalid_config_format": "Định dạng cấu hình không hợp lệ", + "invalid_json_format": "Định dạng JSON hoặc cấu trúc cấu hình không hợp lệ", + "server_tab": "Máy chủ", + "tools_tab": "Quản lý công cụ", + "available_tools": "Công cụ khả dụng", + "scene_tools": "Công cụ cảnh", + "node_tools": "Công cụ nút", + "component_tools": "Công cụ thành phần", + "prefab_tools": "Công cụ Prefab", + "project_tools": "Công cụ dự án", + "debug_tools": "Công cụ gỡ lỗi", + "preferences_tools": "Tùy chọn", + "server_tools": "Công cụ máy chủ", + "broadcast_tools": "Công cụ quảng bá", + "scene_advanced_tools": "Công cụ cảnh nâng cao", + "scene_view_tools": "Công cụ khung nhìn cảnh", + "reference_image_tools": "Công cụ ảnh tham chiếu", + "asset_advanced_tools": "Công cụ tài nguyên nâng cao", + "validation_tools": "Công cụ kiểm tra", + "brand_name": "LiDaxian MCP Series", + "brand_subtitle": "LiDaxian MCP Series", + "contact_email": "Email", + "contact_wechat": "WeChat", + "contact_github": "GitHub", + "machine_id_label": "Mã máy", + "purchase_renew": "Mua / Gia hạn", + "purchase_url": "https://www.vberai.com/game-engines/cocos", + "auth_title": "Chọn phương thức đăng nhập", + "auth_subtitle": "Nhập email và mã kích hoạt của bạn", + "email_label": "Địa chỉ email", + "activation_code_label": "Mã kích hoạt", + "email_placeholder": "Nhập email", + "code_placeholder": "Nhập mã (XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)", + "activate_button": "Kích hoạt", + "deactivate_button": "Hủy kích hoạt", + "activating": "Đang kích hoạt...", + "deactivating": "Đang hủy kích hoạt...", + "license_status": "Trạng thái giấy phép", + "license_type": "Loại giấy phép", + "expiry_date": "Ngày hết hạn", + "days_remaining": "Số ngày còn lại", + "days_unit": "ngày", + "permanent": "Vĩnh viễn", + "expiry_warning_title": "Giấy phép sắp hết hạn", + "expiry_warning_message": "Giấy phép của bạn sẽ hết hạn sau {0} ngày. Vui lòng gia hạn để tránh gián đoạn dịch vụ.", + "renew_now": "Gia hạn ngay", + "i_understand": "Tôi đã hiểu", + "activation_success": "Đã kích hoạt giấy phép thành công!", + "activation_failed": "Kích hoạt thất bại: {0}", + "invalid_code": "Định dạng mã kích hoạt không hợp lệ", + "purchase_license": "Mua giấy phép", + "renew_license": "Gia hạn giấy phép", + "contact_support": "Hỗ trợ", + "language_switch": "Tiếng Việt", + "version_label": "Phiên bản", + "license_required": "Cần có giấy phép để khởi động máy chủ", + "license_check_failed": "Kiểm tra giấy phép thất bại", + + "status_stopped": "Đã dừng", + "status_running": "Đang chạy", + "status_error": "Kết nối thất bại", + "config_tab": "Cấu hình nhanh", + "status_label": "Trạng thái", + "connections_count": "Số kết nối", + "transport_protocol": "Giao thức truyền tải", + "transport_type": "Loại truyền tải", + "mcp_url": "MCP URL", + "port_fallback_title": "Cổng {0} không khả dụng — đã chuyển sang {1}", + "port_fallback_reason": "Cổng {0} bị hệ thống dành riêng hoặc bị tường lửa/phần mềm diệt virus chặn (thường gặp trên Windows có cài Hyper-V, WSL2 hoặc Docker). Máy chủ đang chạy bình thường trên cổng mới.", + "port_fallback_resynced": "Đã cập nhật cấu hình cho: {0}. Hãy khởi động lại các client này để cổng mới có hiệu lực.", + "port_fallback_no_clients": "Nếu bạn đã cấu hình client AI, hãy cấu hình lại sang cổng mới bằng thiết lập một chạm.", + "port_fallback_dismiss": "Đã hiểu", + "port_label": "Cổng", + "auto_start_label": "Tự động khởi động", + "debug_log_label": "Nhật ký gỡ lỗi", + "max_connections_label": "Số kết nối tối đa", + "server_settings_title": "Cài đặt máy chủ", + "server_status_title": "Trạng thái máy chủ", + "connection_info_title": "Thông tin kết nối", + "tools_count": "{0} công cụ", + "tools_stats": "{0} công cụ ({1} đã bật / {2} đã tắt)", + "quick_config_title": "Cấu hình nhanh máy khách MCP", + "quick_config_desc": "Tự động cấu hình máy chủ MCP hiện tại vào các trình soạn thảo AI phổ biến và công cụ CLI", + "server_info": "Thông tin máy chủ", + "server_name_label": "Tên máy chủ", + "server_address": "Địa chỉ máy chủ", + "timeout_ms": "Thời gian chờ (ms)", + "auto_config_title": "Cấu hình tự động (Trình soạn thảo IDE + Codex CLI)", + "add_all": "Thêm tất cả", + "remove_all": "Xóa tất cả", + "auto_config_note": "Nhấp nút để tự động sửa đổi tệp cấu hình (Cursor, Windsurf, Trae, Codex CLI)", + "configured": "Đã cấu hình", + "not_configured": "Chưa cấu hình", + "auto_config_btn": "Cấu hình tự động", + "delete_btn": "Xóa", + "copy_config": "Sao chép cấu hình", + "open_config": "Mở cấu hình", + "manual_config_title": "Cấu hình thủ công (Claude CLI + Gemini CLI)", + "refresh_commands": "Làm mới lệnh", + "manual_config_note": "Các công cụ CLI sau đây cần thực thi lệnh thủ công trong terminal", + "config_scope": "Phạm vi cấu hình:", + "scope_user": "Cấp người dùng", + "scope_project": "Cấp dự án", + "operation_log": "Nhật ký thao tác", + "no_logs": "Không có bản ghi thao tác", + "copyright_line": "© 2025 LiDaxian - Cocos Creator MCP Server v{0}", + "protocol_line": "Dựa trên giao thức Streamable HTTP (MCP 2025-03-26)", + "tools_copyright_line1": "Hệ thống công cụ - 13 công cụ cấp ý định, 150+ thao tác trình soạn thảo, điều phối mã action", + "config_copyright_line1": "Cấu hình nhanh - Hỗ trợ Cursor, Windsurf, Trae, Codex CLI, Claude CLI, Gemini CLI", + "email_required": "Vui lòng nhập email và mã kích hoạt", + "permanent_label": "Vĩnh viễn", + "logout_button": "Đăng xuất", + "invite_code_label": "Mã mời", + "copy_button": "Sao chép", + "change_code_button": "Thay đổi mã", + "change_code_title": "Thay đổi mã kích hoạt", + "change_code_desc": "Nhập mã kích hoạt mới của bạn. Liên kết thiết bị hiện tại sẽ được giải phóng trước.", + "confirm_change": "Xác nhận", + "cancel": "Hủy", + "code_required": "Vui lòng nhập mã kích hoạt", + "expiry_inline_warning": "Sắp hết hạn!", + "store_purchase_hint": "Nếu bạn đã mua plugin này từ Cocos Store, vui lòng liên hệ với tôi để nhận mã kích hoạt.", + "license_expired_title": "Giấy phép đã hết hạn", + "license_expired_message": "Giấy phép của bạn đã hết hạn và không thể khởi động máy chủ. Vui lòng gia hạn hoặc kích hoạt lại để tiếp tục.", + "go_to_login": "Đi đến đăng nhập", + + "log_load_config_failed": "Không thể tải trạng thái cấu hình: {0}", + "log_gen_cli_failed": "Không thể tạo lệnh CLI: {0}", + "log_add_client_failed": "Không thể thêm vào {0}: {1}", + "log_remove_client_failed": "Không thể xóa khỏi {0}: {1}", + "quick_config_dialog_title": "Cấu hình một chạm", + "config_operation_failed": "Thao tác cấu hình thất bại", + "config_file_not_found_changed_path": "Cấu hình thất bại: không tìm thấy tệp cấu hình. {0} có thể được cài đặt trong một đường dẫn tùy chỉnh.", + "log_batch_add_failed": "Thêm hàng loạt thất bại: {0}", + "log_batch_remove_failed": "Xóa hàng loạt thất bại: {0}", + "log_copied_config": "Đã sao chép cấu hình {0} vào bảng nhớ tạm", + "log_gen_config_failed": "Không thể tạo cấu hình", + "log_copy_config_failed": "Không thể sao chép cấu hình: {0}", + "log_copied_command": "Đã sao chép lệnh {0} vào bảng nhớ tạm", + "log_copy_command_failed": "Không thể sao chép lệnh: {0}", + "log_opened_config_file": "Đã mở tệp cấu hình: {0}", + "log_open_config_failed": "Không thể mở tệp cấu hình", + "log_open_config_error": "Không thể mở tệp cấu hình: {0}", + + "update_available_title": "Có bản cập nhật", + "update_new_version": "Đã có phiên bản mới v{0}!", + "update_now": "Cập nhật ngay", + "update_not_now": "Để sau", + "update_skip_version": "Bỏ qua v{0}", + "check_for_updates": "Kiểm tra cập nhật", + "update_checking": "Đang kiểm tra...", + "update_no_update": "Bạn đang sử dụng phiên bản mới nhất", + "update_urgency_critical": "Cập nhật khẩn cấp", + "update_urgency_high": "Cập nhật quan trọng", + "update_current_version": "Phiên bản hiện tại: v{0}", + "update_changelog": "Nhật ký thay đổi", + "auth_choose_subtitle": "Chọn một phương thức đăng nhập để tiếp tục", + "update_changelog_empty": "Không có ghi chú phát hành cho phiên bản này.", + "subscription_login_title": "Tài khoản đăng ký VberAI", + "subscription_password_label": "Mật khẩu", + "subscription_password_placeholder": "Nhập mật khẩu", + "subscription_login_button": "Đăng nhập bằng VberAI", + "subscription_google_button": "Đăng nhập bằng Google", + "subscription_google_pending": "Đã mở trình duyệt. Hoàn tất ủy quyền Google rồi quay lại Cocos Creator.", + "subscription_google_pending_button": "Đang chờ ủy quyền Google...", + "subscription_register_button": "Tạo tài khoản VberAI", + "subscription_logging_in": "Đang đăng nhập...", + "subscription_credentials_required": "Vui lòng nhập email và mật khẩu", + "subscription_login_success": "Đã đăng nhập tài khoản đăng ký", + "subscription_login_failed": "Đăng nhập đăng ký thất bại", + "subscription_not_entitled": "Gói đăng ký hiện tại của bạn không bao gồm Cocos 3.x MCP. Nâng cấp để tiếp tục.", + "subscription_upgrade_to": "Nâng cấp lên \"{0}\" để sử dụng sản phẩm này", + "subscription_upgrade_action": "Nâng cấp gói đăng ký", + "subscription_logged_in": "Tài khoản đã đăng nhập", + "subscription_choose_button": "Tài khoản đăng ký VberAI", + "license_choose_button": "Mã kích hoạt", + "subscription_method_label": "Đăng ký", + "back_to_login_choices": "← Chọn phương thức đăng nhập khác", + "subscription_signed_out_other_device": "Đã đăng xuất: tài khoản của bạn đã được dùng trên thiết bị khác.", + "subscription_google_cancelled": "Đã hủy đăng nhập Google.", + "subscription_request_timeout": "Yêu cầu đã hết thời gian. Kiểm tra mạng và thử lại.", + "subscription_network_error": "Kết nối mạng thất bại. Kiểm tra mạng và thử lại.", + "subscription_server_error": "Máy chủ trả về phản hồi không hợp lệ. Thử lại sau.", + "subscription_google_authorization_failed": "Ủy quyền Google chưa hoàn tất. Vui lòng thử lại." +}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/zh.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/zh.js new file mode 100644 index 0000000..b852f34 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/i18n/zh.js @@ -0,0 +1,243 @@ +"use strict"; +module.exports = { + "extension_name": "Cocos MCP 服务器", + "description": "适用于 Cocos Creator 3.8 的 AI MCP 服务器", + "panel_title": "MCP 服务器", + "open_panel": "打开 MCP 面板", + "start_server": "启动服务器", + "stop_server": "停止服务器", + "server_status": "服务器状态", + "port": "端口", + "settings": "设置", + "connected": "已连接", + "disconnected": "未连接", + "server_running": "服务器正在端口 {0} 上运行", + "server_stopped": "服务器已停止", + "auto_start": "自动启动", + "debug_log": "调试日志", + "max_connections": "最大连接数", + "connection_info": "连接信息", + "http_url": "HTTP 地址", + "copy": "复制", + "save_settings": "保存设置", + "settings_saved": "设置保存成功", + "server_started": "MCP 服务器已启动", + "server_stopped_msg": "MCP 服务器已停止", + "failed_to_start": "启动服务器失败", + "failed_to_stop": "停止服务器失败", + "failed_to_save": "保存设置失败", + "url_copied": "HTTP 地址已复制到剪贴板", + "tool_manager": "工具管理器", + "open_tool_manager": "打开工具管理器", + "create_config": "新建配置", + "edit_config": "编辑配置", + "delete_config": "删除配置", + "import_config": "导入配置", + "export_config": "导出配置", + "apply_config": "应用配置", + "select_all": "全选", + "deselect_all": "取消全选", + "save_changes": "保存更改", + "config_name": "配置名称", + "config_description": "配置描述", + "current_config": "当前配置", + "tool_management": "工具管理", + "total_tools": "总工具数", + "enabled_tools": "已启用", + "disabled_tools": "已禁用", + "no_config_selected": "没有选择配置", + "select_config_first": "请先选择一个配置或创建新配置", + "config_created": "配置创建成功", + "config_updated": "配置更新成功", + "config_deleted": "配置删除成功", + "config_applied": "配置应用成功", + "config_exported": "配置导出成功", + "config_imported": "配置导入成功", + "confirm_delete": "确认删除", + "delete_config_confirm": "确定要删除配置 \"{0}\" 吗?此操作不可撤销。", + "max_config_slots_reached": "已达到最大配置槽位数量 ({0})", + "invalid_config_format": "无效的配置格式", + "invalid_json_format": "无效的JSON格式或配置结构", + "server_tab": "服务器", + "tools_tab": "工具管理", + "available_tools": "可用工具", + "scene_tools": "场景工具", + "node_tools": "节点工具", + "component_tools": "组件工具", + "prefab_tools": "预制体工具", + "project_tools": "项目工具", + "debug_tools": "调试工具", + "preferences_tools": "偏好设置", + "server_tools": "服务器工具", + "broadcast_tools": "广播工具", + "scene_advanced_tools": "高级场景工具", + "scene_view_tools": "场景视图工具", + "reference_image_tools": "参考图片工具", + "asset_advanced_tools": "高级资源工具", + "validation_tools": "验证工具", + "brand_name": "李大仙 MCP 系列", + "brand_subtitle": "LiDaxian MCP Series", + "contact_email": "邮箱", + "contact_wechat": "微信", + "contact_github": "GitHub", + "machine_id_label": "机器码", + "purchase_renew": "购买/续费", + "purchase_url": "https://www.vberai.com/game-engines/cocos", + "auth_title": "选择登录方式", + "auth_subtitle": "请输入您的邮箱和激活码", + "auth_choose_subtitle": "请选择一种登录方式继续", + "email_label": "邮箱地址", + "activation_code_label": "激活码", + "email_placeholder": "请输入邮箱", + "code_placeholder": "请输入激活码 (XXXXX-XXXXX-XXXXX-XXXXX-XXXXX)", + "activate_button": "激活", + "deactivate_button": "取消激活", + "activating": "激活中...", + "deactivating": "取消激活中...", + "license_status": "授权状态", + "license_type": "授权类型", + "expiry_date": "到期日期", + "days_remaining": "剩余天数", + "days_unit": "天", + "permanent": "永久", + "expiry_warning_title": "授权即将到期", + "expiry_warning_message": "您的授权将在 {0} 天后到期,请及时续费以确保服务不中断。", + "renew_now": "立即续费", + "i_understand": "我知道了", + "activation_success": "授权激活成功!", + "activation_failed": "激活失败:{0}", + "invalid_code": "无效的激活码格式", + "purchase_license": "购买授权", + "renew_license": "续费授权", + "contact_support": "技术支持", + "language_switch": "中文", + "version_label": "版本", + "license_required": "需要授权才能启动服务器", + "license_check_failed": "授权检查失败", + + "status_stopped": "已停止", + "status_running": "运行中", + "status_error": "连接失败", + "config_tab": "一键配置", + "status_label": "状态", + "connections_count": "连接数", + "transport_protocol": "传输协议", + "transport_type": "传输类型", + "mcp_url": "MCP URL", + "port_fallback_title": "端口 {0} 不可用,已自动切换到 {1}", + "port_fallback_reason": "端口 {0} 被系统保留或被防火墙/杀毒软件拦截(安装了 Hyper-V、WSL2 或 Docker 的 Windows 上很常见)。服务已在新端口上正常运行。", + "port_fallback_resynced": "已自动更新这些客户端的配置:{0}。请重启它们,新端口才会生效。", + "port_fallback_no_clients": "如果已配置过 AI 客户端,请在\"一键配置\"里重新配置到新端口。", + "port_fallback_dismiss": "知道了", + "port_label": "端口", + "auto_start_label": "自动启动", + "debug_log_label": "调试日志", + "max_connections_label": "最大连接数", + "server_settings_title": "服务器设置", + "server_status_title": "服务器状态", + "connection_info_title": "连接信息", + "tools_count": "{0} 个工具", + "tools_stats": "{0} 个工具 ({1} 启用 / {2} 禁用)", + "quick_config_title": "一键配置 MCP 客户端", + "quick_config_desc": "自动将当前MCP服务器配置到主流AI编辑器和CLI工具中", + "server_info": "服务器信息", + "server_name_label": "服务器名称", + "server_address": "服务器地址", + "timeout_ms": "超时时间(ms)", + "auto_config_title": "自动配置 (IDE编辑器 + Codex CLI)", + "add_all": "全部添加", + "remove_all": "全部删除", + "auto_config_note": "点击按钮将自动修改配置文件(包括Cursor、Windsurf、Trae、Codex CLI)", + "configured": "已配置", + "not_configured": "未配置", + "auto_config_btn": "自动配置", + "delete_btn": "删除", + "copy_config": "复制配置", + "open_config": "打开配置", + "manual_config_title": "手动配置 (Claude CLI + Gemini CLI)", + "refresh_commands": "刷新命令", + "manual_config_note": "以下CLI工具需要在终端中手动执行命令配置", + "config_scope": "配置范围:", + "scope_user": "用户级别", + "scope_project": "项目级别", + "operation_log": "操作日志", + "no_logs": "暂无操作记录", + "copyright_line": "© 2025 李大仙 (LiDaxian) - Cocos Creator MCP Server v{0}", + "protocol_line": "基于 Streamable HTTP 协议 (MCP 2025-03-26)", + "tools_copyright_line1": "工具系统 - 13个意图级工具,150+编辑器操作,action操作码分发", + "config_copyright_line1": "一键配置 - 支持 Cursor、Windsurf、Trae、Codex CLI、Claude CLI、Gemini CLI", + "email_required": "请输入邮箱和激活码", + "permanent_label": "永久", + "logout_button": "退出登录", + "invite_code_label": "邀请码", + "copy_button": "复制", + "change_code_button": "变更激活码", + "change_code_title": "变更激活码", + "change_code_desc": "输入新的激活码,当前设备的激活占位将被释放后重新激活。", + "confirm_change": "确认变更", + "cancel": "取消", + "code_required": "请输入激活码", + "expiry_inline_warning": "即将到期!", + "store_purchase_hint": "如果您已在 Cocos Store 购买了本插件,请联系我获取激活码。", + "license_expired_title": "授权已过期", + "license_expired_message": "您的授权已过期,无法启动服务。请续费或重新激活以继续使用。", + "go_to_login": "重新登录", + + "log_load_config_failed": "加载配置状态失败: {0}", + "log_gen_cli_failed": "生成CLI命令失败: {0}", + "log_add_client_failed": "添加到{0}失败: {1}", + "log_remove_client_failed": "从{0}删除失败: {1}", + "quick_config_dialog_title": "一键配置", + "config_operation_failed": "配置操作失败", + "config_file_not_found_changed_path": "配置失败:找不到配置文件,{0} 安装时可能更改了路径", + "log_batch_add_failed": "批量添加失败: {0}", + "log_batch_remove_failed": "批量删除失败: {0}", + "log_copied_config": "已复制{0}配置到剪贴板", + "log_gen_config_failed": "生成配置失败", + "log_copy_config_failed": "复制配置失败: {0}", + "log_copied_command": "已复制{0}命令到剪贴板", + "log_copy_command_failed": "复制命令失败: {0}", + "log_opened_config_file": "已打开配置文件: {0}", + "log_open_config_failed": "打开配置文件失败", + "log_open_config_error": "打开配置文件失败: {0}", + + "update_available_title": "发现新版本", + "update_new_version": "新版本 v{0} 已发布!", + "update_now": "立即更新", + "update_not_now": "暂不更新", + "update_skip_version": "跳过 v{0}", + "check_for_updates": "检查更新", + "update_checking": "检查中...", + "update_no_update": "当前已是最新版本", + "update_urgency_critical": "紧急更新", + "update_urgency_high": "重要更新", + "update_current_version": "当前版本: v{0}", + "update_changelog": "更新日志" + ,"update_changelog_empty": "该版本暂未填写更新内容。" + ,"subscription_login_title": "VberAI 官网订阅账号" + ,"subscription_password_label": "密码" + ,"subscription_password_placeholder": "请输入密码" + ,"subscription_login_button": "使用 VberAI 账号登录" + ,"subscription_google_button": "使用 Google 登录" + ,"subscription_google_pending": "已打开浏览器,请完成 Google 授权后返回 Cocos Creator。" + ,"subscription_google_pending_button": "正在等待 Google 授权..." + ,"subscription_register_button": "注册 VberAI 账号" + ,"subscription_logging_in": "登录中..." + ,"subscription_credentials_required": "请输入邮箱和密码" + ,"subscription_login_success": "订阅账号登录成功" + ,"subscription_login_failed": "订阅账号登录失败" + ,"subscription_not_entitled": "当前订阅计划不包含 Cocos 3.x MCP,请升级后使用。" + ,"subscription_upgrade_to": "升级到「{0}」即可使用" + ,"subscription_upgrade_action": "前往升级订阅" + ,"subscription_logged_in": "当前登录账号" + ,"subscription_choose_button": "VberAI 官网订阅账号" + ,"license_choose_button": "激活码授权" + ,"subscription_method_label": "订阅账号" + ,"back_to_login_choices": "← 返回选择登录方式" + ,"subscription_signed_out_other_device": "账号已在另一台设备登录,当前设备已退出。" + ,"subscription_google_cancelled": "已取消 Google 登录" + ,"subscription_request_timeout": "请求超时,请检查网络后重试。" + ,"subscription_network_error": "网络连接失败,请检查网络后重试。" + ,"subscription_server_error": "服务器响应异常,请稍后重试。" + ,"subscription_google_authorization_failed": "Google 授权未完成,请重试。" +}; diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/package.json b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/package.json new file mode 100644 index 0000000..aa64d74 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/package.json @@ -0,0 +1,259 @@ +{ + "$schema": "./@types/schema/package/index.json", + "package_version": 2, + "name": "cocos-mcp-server", + "version": "1.7.9", + "author": "LiDaxian", + "editor": ">=3.7.0", + "description": "i18n:cocos-mcp-server.description", + "main": "./dist/main.js", + "dependencies": { + "@iarna/toml": "^2.2.5", + "fs-extra": "^11.3.0", + "puppeteer-core": "^25.1.0", + "uuid": "^9.0.1", + "vue": "^3.1.4" + }, + "panels": { + "default": { + "title": "i18n:cocos-mcp-server.panel_title", + "type": "dockable", + "main": "dist/panels/default", + "icon": "./static/icon.png", + "size": { + "min-width": 560, + "min-height": 680, + "width": 760, + "height": 820 + } + } + }, + "contributions": { + "menu": [ + { + "path": "i18n:menu.extension/Cocos MCP Server", + "label": "i18n:cocos-mcp-server.open_panel", + "message": "open-panel" + } + ], + "messages": { + "open-panel": { + "methods": [ + "openPanel" + ] + }, + "open-tool-manager": { + "methods": [ + "openToolManager" + ] + }, + "start-server": { + "methods": [ + "startServer" + ] + }, + "stop-server": { + "methods": [ + "stopServer" + ] + }, + "get-server-status": { + "methods": [ + "getServerStatus" + ] + }, + "update-settings": { + "methods": [ + "updateSettings" + ] + }, + "getToolsList": { + "methods": [ + "getToolsList" + ] + }, + "get-server-settings": { + "methods": [ + "getServerSettings" + ] + }, + "getToolManagerState": { + "methods": [ + "getToolManagerState" + ] + }, + "createToolConfiguration": { + "methods": [ + "createToolConfiguration" + ] + }, + "updateToolConfiguration": { + "methods": [ + "updateToolConfiguration" + ] + }, + "deleteToolConfiguration": { + "methods": [ + "deleteToolConfiguration" + ] + }, + "setCurrentToolConfiguration": { + "methods": [ + "setCurrentToolConfiguration" + ] + }, + "updateToolStatus": { + "methods": [ + "updateToolStatus" + ] + }, + "updateToolStatusBatch": { + "methods": [ + "updateToolStatusBatch" + ] + }, + "exportToolConfiguration": { + "methods": [ + "exportToolConfiguration" + ] + }, + "importToolConfiguration": { + "methods": [ + "importToolConfiguration" + ] + }, + "getEnabledTools": { + "methods": [ + "getEnabledTools" + ] + }, + "get-config-status": { + "methods": [ + "getConfigStatus" + ] + }, + "generate-cli-commands": { + "methods": [ + "generateCLICommands" + ] + }, + "generate-client-config": { + "methods": [ + "generateClientConfig" + ] + }, + "add-to-client": { + "methods": [ + "addToClient" + ] + }, + "remove-from-client": { + "methods": [ + "removeFromClient" + ] + }, + "add-to-all-clients": { + "methods": [ + "addToAllClients" + ] + }, + "remove-from-all-clients": { + "methods": [ + "removeFromAllClients" + ] + }, + "open-config-file": { + "methods": [ + "openConfigFile" + ] + }, + "check-license": { + "methods": [ + "checkLicense" + ] + }, + "subscription-status": { + "methods": [ + "subscriptionStatus" + ] + }, + "login-subscription": { + "methods": [ + "loginSubscription" + ] + }, + "login-subscription-google": { + "methods": [ + "loginSubscriptionGoogle" + ] + }, + "logout-subscription": { + "methods": [ + "logoutSubscription" + ] + }, + "activate-license": { + "methods": [ + "activateLicense" + ] + }, + "deactivate-license": { + "methods": [ + "deactivateLicense" + ] + }, + "get-license-info": { + "methods": [ + "getLicenseInfo" + ] + }, + "check-update": { + "methods": [ + "checkUpdate" + ] + }, + "skip-update": { + "methods": [ + "skipUpdate" + ] + }, + "get-machine-id": { + "methods": [ + "getMachineId" + ] + }, + "preview-play": { + "methods": [ + "default.onPreviewPlay" + ] + }, + "preview-stop": { + "methods": [ + "default.onPreviewStop" + ] + }, + "device-removed": { + "methods": [ + "default.device-removed" + ] + } + }, + "scene": { + "script": "./dist/scene.js", + "methods": [ + "createNewScene", + "addComponentToNode", + "removeComponentFromNode", + "getNodeInfo", + "getAllNodes", + "findNodeByName", + "setNodeProperty", + "setComponentProperty", + "getCurrentSceneInfo", + "getSceneHierarchy", + "createPrefabFromNode", + "captureContent" + ] + } + } +} \ No newline at end of file diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/batch-auto-knowledge-mcp.mjs b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/batch-auto-knowledge-mcp.mjs new file mode 100644 index 0000000..90616b7 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/batch-auto-knowledge-mcp.mjs @@ -0,0 +1,94 @@ +#!/usr/bin/env node +/** + * Batch auto_knowledge via MCP HTTP — gets runtime defaults from editor + * Requires: editor running with MCP on port 3000 + */ + +import http from 'http'; + +const MCP_URL = 'http://127.0.0.1:3000/mcp'; +const ALL = [ + 'cc.Label','cc.Sprite','cc.Button','cc.Widget','cc.Layout', + 'cc.UITransform','cc.RichText','cc.ScrollView','cc.Toggle', + 'cc.ToggleContainer','cc.ProgressBar','cc.Slider','cc.PageView', + 'cc.Mask','cc.Graphics','cc.UIOpacity','cc.SafeArea', + 'cc.BlockInputEvents','cc.LabelOutline','cc.LabelShadow', + 'cc.PageViewIndicator','cc.EditBox', + 'cc.AudioSource','cc.VideoPlayer','cc.WebView', + 'cc.ParticleSystem2D', + 'cc.Camera','cc.Canvas','cc.Animation','cc.SkeletalAnimation', + 'cc.MeshRenderer','cc.SkinnedMeshRenderer', + 'cc.DirectionalLight','cc.PointLight','cc.SpotLight','cc.SphereLight', + 'cc.RigidBody2D','cc.BoxCollider2D','cc.CircleCollider2D','cc.PolygonCollider2D', + 'cc.RigidBody','cc.BoxCollider','cc.SphereCollider','cc.CapsuleCollider','cc.MeshCollider', + 'cc.MotionStreak','cc.Billboard','cc.Line', + 'cc.TiledMap','cc.TiledLayer', + 'cc.HingeJoint2D','cc.DistanceJoint2D','cc.SpringJoint2D', + 'cc.FixedJoint2D','cc.WheelJoint2D','cc.SliderJoint2D', + 'cc.RelativeJoint2D','cc.MouseJoint2D', + 'cc.HingeConstraint','cc.FixedConstraint','cc.PointToPointConstraint', + 'cc.Terrain','cc.LODGroup','cc.ReflectionProbe', + 'sp.Skeleton','dragonBones.ArmatureDisplay', +]; + +function callMcp(toolName, args) { + return new Promise((resolve, reject) => { + const body = JSON.stringify({ + jsonrpc: '2.0', id: Date.now(), method: 'tools/call', + params: { name: toolName, arguments: args } + }); + const req = http.request(MCP_URL, { + method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }, + timeout: 30000, + }, (res) => { + const chunks = []; + res.on('data', c => chunks.push(c)); + res.on('end', () => { + try { + const data = JSON.parse(Buffer.concat(chunks).toString()); + const content = data.result?.content || []; + const text = content.find(c => c.type === 'text')?.text; + resolve(text ? JSON.parse(text) : {}); + } catch (e) { reject(e); } + }); + }); + req.on('error', reject); + req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); }); + req.write(body); + req.end(); + }); +} + +// Check connectivity +try { + await callMcp('cocos_editor', { action: 'project_info' }); +} catch { + console.error('❌ Cannot connect to MCP at', MCP_URL); + console.error(' Make sure editor is running and plugin is loaded.'); + process.exit(1); +} + +console.log(`Connected. Running auto_knowledge(save:true) for ${ALL.length} components...\n`); + +let ok = 0, fail = 0, withDefaults = 0; + +for (const comp of ALL) { + try { + const r = await callMcp('cocos_devtools', { action: 'auto_knowledge', componentType: comp, save: true }); + if (r.success) { + ok++; + const hasRuntime = r.data?.hasRuntimeDefaults; + if (hasRuntime) withDefaults++; + const propCount = Object.keys(r.data?.recommendedKnowledge?.properties || {}).length; + console.log(` ✅ ${comp.padEnd(35)} ${propCount} props ${hasRuntime ? '(+defaults)' : '(dts only)'}`); + } else { + fail++; + console.log(` ❌ ${comp}: ${r.error}`); + } + } catch (e) { + fail++; + console.log(` ❌ ${comp}: ${e.message}`); + } +} + +console.log(`\nDone: ${ok} ok, ${fail} fail, ${withDefaults} with runtime defaults`); diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/batch-auto-knowledge.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/batch-auto-knowledge.js new file mode 100644 index 0000000..a2235cf --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/batch-auto-knowledge.js @@ -0,0 +1,83 @@ +/** + * Batch auto_knowledge — generate complete COMPONENT_PROPERTIES from d.ts + * Output: knowledge JSON files + combined TS snippet for knowledge-handler.ts + */ + +const path = require('path'); +const fs = require('fs'); +const mod = require(path.resolve(__dirname, '../dist/tools/cocos/utils/component-knowledge')); + +const ALL_COMPONENTS = [ + 'cc.Label', 'cc.Sprite', 'cc.Button', 'cc.Widget', 'cc.Layout', + 'cc.UITransform', 'cc.RichText', 'cc.ScrollView', 'cc.Toggle', + 'cc.ToggleContainer', 'cc.ProgressBar', 'cc.Slider', 'cc.PageView', + 'cc.Mask', 'cc.Graphics', 'cc.UIOpacity', 'cc.SafeArea', + 'cc.BlockInputEvents', 'cc.LabelOutline', 'cc.LabelShadow', + 'cc.PageViewIndicator', 'cc.EditBox', + 'cc.AudioSource', 'cc.VideoPlayer', 'cc.WebView', + 'cc.ParticleSystem2D', 'cc.ParticleSystem', + 'cc.Camera', 'cc.Canvas', 'cc.Animation', 'cc.SkeletalAnimation', + 'cc.MeshRenderer', 'cc.SkinnedMeshRenderer', + 'cc.DirectionalLight', 'cc.PointLight', 'cc.SpotLight', 'cc.SphereLight', + 'cc.RigidBody2D', 'cc.BoxCollider2D', 'cc.CircleCollider2D', 'cc.PolygonCollider2D', + 'cc.RigidBody', 'cc.BoxCollider', 'cc.SphereCollider', 'cc.CapsuleCollider', 'cc.MeshCollider', + 'cc.MotionStreak', 'cc.Billboard', 'cc.Line', + 'cc.TiledMap', 'cc.TiledLayer', + 'cc.HingeJoint2D', 'cc.DistanceJoint2D', 'cc.SpringJoint2D', + 'cc.FixedJoint2D', 'cc.WheelJoint2D', 'cc.SliderJoint2D', + 'cc.RelativeJoint2D', 'cc.MouseJoint2D', + 'cc.HingeConstraint', 'cc.FixedConstraint', 'cc.PointToPointConstraint', + 'cc.Terrain', 'cc.LODGroup', 'cc.ReflectionProbe', + 'sp.Skeleton', 'dragonBones.ArmatureDisplay', +]; + +const snippets = []; +let saved = 0, failed = 0; + +for (const comp of ALL_COMPONENTS) { + const className = mod.componentTypeToClassName(comp); + const dtsInfo = mod.parseDtsClass(className, { recursive: true }); + + if (!dtsInfo) { + console.log(` ❌ ${comp}: not found in d.ts`); + failed++; + continue; + } + + const classified = mod.classifyProperties(dtsInfo, null); + const knowledge = mod.buildRecommendedKnowledge(comp, dtsInfo, classified); + + // Save knowledge file + mod.saveKnowledgeFile(comp, knowledge); + + // Generate TS snippet + const snippet = mod.generateTsSnippet(comp, knowledge); + snippets.push(snippet); + + const propCount = Object.keys(knowledge.properties).length; + console.log(` ✅ ${comp}: ${propCount} props`); + saved++; +} + +// Write combined snippets to file +const outputPath = path.resolve(__dirname, '../dev-knowledge/COMPONENT_PROPERTIES_GENERATED.ts'); +const outputDir = path.dirname(outputPath); +if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true }); + +const header = `/** + * AUTO-GENERATED by batch-auto-knowledge.js + * Date: ${new Date().toISOString()} + * Components: ${saved} + * + * Paste this into COMPONENT_PROPERTIES in knowledge-handler.ts + */ + +const COMPONENT_PROPERTIES: Record = {\n`; + +const footer = `\n};\n`; + +fs.writeFileSync(outputPath, header + snippets.join('\n') + footer, 'utf-8'); + +console.log(`\n Saved: ${saved} | Failed: ${failed}`); +console.log(` Knowledge files: dev-knowledge/components/`); +console.log(` TS output: ${outputPath}`); diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/preinstall.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/preinstall.js new file mode 100644 index 0000000..392e2f1 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/preinstall.js @@ -0,0 +1,111 @@ +const fs = require("fs"); +const path = require("path"); +const { spawnSync } = require("child_process"); + +const PATH = { + packageJSON: path.join(__dirname, "../package.json") +}; + +function checkCreatorTypesVersion(version) { + try { + // 根据平台选择合适的npm命令 + const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm"; + + // 检查npm命令是否可用 + const npmCheck = spawnSync(npmCmd, ["--version"], { + stdio: 'pipe', + shell: process.platform === "win32" + }); + + if (npmCheck.error || npmCheck.status !== 0) { + console.warn("Warning: npm command not available, skipping version check"); + return true; // 如果npm不可用,跳过检查 + } + + // 获取版本列表 + const result = spawnSync(npmCmd, ["view", "@cocos/creator-types", "versions"], { + stdio: 'pipe', + shell: process.platform === "win32" + }); + + if (result.error || result.status !== 0) { + console.warn("Warning: Failed to fetch @cocos/creator-types versions, skipping check"); + return true; // 如果获取失败,跳过检查 + } + + let output = result.stdout.toString().trim(); + + // 尝试解析JSON + try { + const versions = JSON.parse(output); + if (Array.isArray(versions)) { + return versions.includes(version); + } else if (typeof versions === 'string') { + return versions.includes(version); + } + } catch (parseError) { + // 如果JSON解析失败,尝试作为字符串处理 + return output.includes(version); + } + + return false; + } catch (error) { + console.warn("Warning: Version check failed:", error.message); + return true; // 出错时跳过检查 + } +} + +function getCreatorTypesVersion() { + try { + // 检查package.json文件是否存在 + if (!fs.existsSync(PATH.packageJSON)) { + console.warn("Warning: package.json not found"); + return null; + } + + const packageContent = fs.readFileSync(PATH.packageJSON, "utf8"); + const packageJson = JSON.parse(packageContent); + + // 检查devDependencies是否存在 + if (!packageJson.devDependencies || !packageJson.devDependencies["@cocos/creator-types"]) { + console.warn("Warning: @cocos/creator-types not found in devDependencies"); + return null; + } + + const versionString = packageJson.devDependencies["@cocos/creator-types"]; + return versionString.replace(/^[^\d]+/, ""); + } catch (error) { + console.warn("Warning: Failed to read package.json:", error.message); + return null; + } +} + +function main() { + try { + const creatorTypesVersion = getCreatorTypesVersion(); + + if (!creatorTypesVersion) { + console.log("Skipping @cocos/creator-types version check"); + return; + } + + if (!checkCreatorTypesVersion(creatorTypesVersion)) { + console.log("\x1b[33mWarning:\x1b[0m"); + console.log(" @en"); + console.log(" Version check of @cocos/creator-types failed."); + console.log(` The definition of ${creatorTypesVersion} has not been released yet. Please export the definition to the ./node_modules directory by selecting "Developer -> Export Interface Definition" in the menu of the Creator editor.`); + console.log(" The definition of the corresponding version will be released on npm after the editor is officially released."); + console.log(" @zh"); + console.log(" @cocos/creator-types 版本检查失败。"); + console.log(` ${creatorTypesVersion} 定义还未发布,请先通过 Creator 编辑器菜单 "开发者 -> 导出接口定义",导出定义到 ./node_modules 目录。`); + console.log(" 对应版本的定义会在编辑器正式发布后同步发布到 npm 上。"); + } + } catch (error) { + console.error("Preinstall script error:", error.message); + // 不要抛出错误,让安装继续进行 + process.exit(0); + } +} + +// 执行主函数 +main(); \ No newline at end of file diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/promote-all-knowledge.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/promote-all-knowledge.js new file mode 100644 index 0000000..3f834e7 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/promote-all-knowledge.js @@ -0,0 +1,25 @@ +/** + * Generate COMPONENT_PROPERTIES from saved dev-knowledge JSON files. + * Does NOT re-parse d.ts — uses the saved knowledge (which has runtime defaults). + */ +const path = require('path'); +const fs = require('fs'); +const mod = require(path.resolve(__dirname, '../dist/tools/cocos/utils/component-knowledge')); + +const files = mod.listKnowledgeFiles(); +const snippets = []; + +for (const comp of files) { + const knowledge = mod.loadKnowledgeFile(comp); + if (!knowledge) continue; + snippets.push(mod.generateTsSnippet(comp, knowledge)); + const propCount = Object.keys(knowledge.properties).length; + const defaultCount = Object.values(knowledge.properties).filter(p => p.default !== undefined).length; + console.log(` ${comp.padEnd(35)} ${propCount} props, ${defaultCount} with defaults`); +} + +const outputPath = path.resolve(__dirname, '../dev-knowledge/COMPONENT_PROPERTIES_GENERATED.txt'); +const header = `const COMPONENT_PROPERTIES: Record = {\n`; +const footer = `\n};\n`; +fs.writeFileSync(outputPath, header + snippets.join('\n') + footer, 'utf-8'); +console.log(`\nGenerated ${files.length} components → ${outputPath}`); diff --git a/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/validate-schemas.js b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/validate-schemas.js new file mode 100644 index 0000000..60998d1 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/scripts/validate-schemas.js @@ -0,0 +1,126 @@ +/** + * validate-schemas.js + * 构建后校验所有 MCP 工具的 inputSchema,检测常见 JSON Schema 错误: + * - array 类型缺少 items + * - object 类型缺少 properties + * - enum 为空数组 + * - type 字段缺失 + */ + +const path = require('path'); +const fs = require('fs'); + +// 颜色 +const RED = '\x1b[31m'; +const YELLOW = '\x1b[33m'; +const GREEN = '\x1b[32m'; +const RESET = '\x1b[0m'; + +let errors = 0; +let warnings = 0; + +function validateSchema(schema, context, toolName) { + if (!schema || typeof schema !== 'object') return; + + // 检查 array 缺少 items + if (schema.type === 'array' && !schema.items) { + console.error(`${RED}ERROR${RESET} [${toolName}] ${context}: type "array" missing "items"`); + errors++; + } + + // 检查 items 但不是 array + if (schema.items && schema.type !== 'array') { + console.warn(`${YELLOW}WARN${RESET} [${toolName}] ${context}: has "items" but type is "${schema.type}", not "array"`); + warnings++; + } + + // 检查 enum 为空 + if (schema.enum && Array.isArray(schema.enum) && schema.enum.length === 0) { + console.warn(`${YELLOW}WARN${RESET} [${toolName}] ${context}: empty "enum" array`); + warnings++; + } + + // 递归检查 properties + if (schema.properties && typeof schema.properties === 'object') { + for (const [key, value] of Object.entries(schema.properties)) { + validateSchema(value, `${context}.${key}`, toolName); + } + } + + // 递归检查 items + if (schema.items && typeof schema.items === 'object') { + validateSchema(schema.items, `${context}.items`, toolName); + } +} + +function main() { + const distDir = path.join(__dirname, '..', 'dist'); + if (!fs.existsSync(distDir)) { + console.error(`${RED}ERROR${RESET} dist/ not found. Run "npm run build" first.`); + process.exit(1); + } + + // 收集所有工具定义 + const tools = []; + + // 1. 加载 CocosTools + try { + const { CocosTools } = require(path.join(distDir, 'tools', 'cocos', 'cocos-tools.js')); + const cocosTools = new CocosTools(); + tools.push(...cocosTools.getTools()); + } catch (e) { + console.warn(`${YELLOW}WARN${RESET} Could not load CocosTools: ${e.message}`); + } + + // 2. 加载独立工具集 + const toolFiles = [ + 'tools/scene-tools.js', + 'tools/node-tools.js', + 'tools/component-tools.js', + 'tools/prefab-tools.js', + 'tools/project-tools.js', + 'tools/scene-view-tools.js', + 'tools/reference-image-tools.js', + 'tools/preferences-tools.js', + 'tools/asset-advanced-tools.js', + 'tools/debug-tools.js', + 'tools/server-tools.js', + ]; + + for (const file of toolFiles) { + try { + const fullPath = path.join(distDir, file); + if (!fs.existsSync(fullPath)) continue; + const mod = require(fullPath); + // 找到导出的类并实例化 + for (const [, ExportedClass] of Object.entries(mod)) { + if (typeof ExportedClass === 'function' && ExportedClass.prototype && typeof ExportedClass.prototype.getTools === 'function') { + const instance = new ExportedClass(); + tools.push(...instance.getTools()); + } + } + } catch (e) { + console.warn(`${YELLOW}WARN${RESET} Could not load ${file}: ${e.message}`); + } + } + + console.log(`\nValidating ${tools.length} tool schemas...\n`); + + for (const tool of tools) { + if (tool.inputSchema) { + validateSchema(tool.inputSchema, 'inputSchema', tool.name); + } + } + + console.log(''); + if (errors > 0) { + console.error(`${RED}FAILED${RESET}: ${errors} error(s), ${warnings} warning(s)`); + process.exit(1); + } else if (warnings > 0) { + console.log(`${YELLOW}PASSED${RESET} with ${warnings} warning(s)`); + } else { + console.log(`${GREEN}PASSED${RESET}: All schemas valid`); + } +} + +main(); diff --git a/cocoscreator_projects/YouleNexus/settings/.mcp-version b/cocoscreator_projects/YouleNexus/settings/.mcp-version new file mode 100644 index 0000000..cb1ad9b --- /dev/null +++ b/cocoscreator_projects/YouleNexus/settings/.mcp-version @@ -0,0 +1 @@ +1.7.9 \ No newline at end of file diff --git a/cocoscreator_projects/YouleNexus/settings/mcp-server.json b/cocoscreator_projects/YouleNexus/settings/mcp-server.json new file mode 100644 index 0000000..ac6d885 --- /dev/null +++ b/cocoscreator_projects/YouleNexus/settings/mcp-server.json @@ -0,0 +1,9 @@ +{ + "port": 3000, + "autoStart": false, + "enableDebugLog": false, + "allowedOrigins": [ + "*" + ], + "maxConnections": 10 +} \ No newline at end of file diff --git a/cocoscreator_projects/YouleNexus/settings/tool-manager.json b/cocoscreator_projects/YouleNexus/settings/tool-manager.json new file mode 100644 index 0000000..e4f4c2e --- /dev/null +++ b/cocoscreator_projects/YouleNexus/settings/tool-manager.json @@ -0,0 +1,111 @@ +{ + "configurations": [ + { + "id": "1a189805-4f21-4147-91ab-461fe9ff5f28", + "name": "默认配置", + "description": "自动创建的默认工具配置", + "tools": [ + { + "category": "cocos", + "name": "scene", + "enabled": true, + "description": "Scene management — open/save/create scenes, get hierarchy, undo, detect mode, list_components, validate_scene.\nCommon: hierarchy, get_info, open, save, create, validate_scene, list_components.\nNote: validate_scene checks broken refs IN this tool (NOT cocos_validate). list_components lists all available component types.\nExample: {action:\"hierarchy\"} or {action:\"validate_scene\"}" + }, + { + "category": "cocos", + "name": "node", + "enabled": true, + "description": "⚡ BATCH: When modifying 3+ nodes (position/size/anchor), use \"batch_modify\" instead of calling \"modify\" repeatedly — it's faster and atomic.\nNode CRUD — create/find/modify/delete nodes, list all nodes, set transform, mount/remove scripts, reorder siblings.\nActions: find, info, list, tree, create, delete, modify, move, reorder, duplicate, copy, paste, cut, mount_script, remove_script, reset, detect_type, batch_modify.\nNote: \"list\" returns all nodes flat list, \"tree\" returns subtree of a node. Use \"info\" (NOT get_info), \"reset\" (NOT reset_transform). \"remove_script\" for scripts (NOT component.remove). \"reorder\" to change sibling order within same parent. \"batch_modify\" to modify multiple nodes in one call.\nExample: {action:\"list\"} or {action:\"tree\", node:\"Canvas\"} or {action:\"batch_modify\", nodes:[{node:\"Player\", position:{x:0,y:0,z:0}}]}" + }, + { + "category": "cocos", + "name": "component", + "enabled": true, + "description": "⚡ BATCH: When binding 3+ click events, use \"batch_click_event\" instead of calling \"click_event\" repeatedly.\nComponent management — add/remove/configure components on nodes.\nActions: add, remove, list, info, set_property, available_types, click_event, batch_click_event.\nNote: use \"info\" to view component properties (NOT get_properties). To remove scripts, use cocos_node(action:\"remove_script\"). \"batch_click_event\" binds click events on multiple buttons in one call.\nExample: {action:\"info\", node:\"Canvas/Btn\", componentType:\"cc.Button\"}" + }, + { + "category": "cocos", + "name": "prefab", + "enabled": true, + "description": "Prefab management — list/info/delete/create/instantiate/edit prefabs, apply/revert changes.\nAll prefab operations (including delete) belong here, NOT cocos_asset.\nCommon: instantiate, create, delete, apply, edit_enter, edit_save, edit_exit.\nExample: {action:\"instantiate\", prefabPath:\"db://assets/prefabs/Enemy.prefab\", parent:\"Canvas\"}" + }, + { + "category": "cocos", + "name": "asset", + "enabled": true, + "description": "Asset operations — query/search/create/delete assets, path-UUID conversion.\nCommon: query_uuid, find_by_name, search, create, delete.\nExample: {action:\"query_uuid\", url:\"db://assets/textures/bg.png\"}" + }, + { + "category": "cocos", + "name": "editor", + "enabled": true, + "description": "Editor utilities — project info, console logs, preferences, build, preview, run/stop, reload.\nGroups: project_*, console_*, log_*, pref_*, build*, server_*, run, stop, reload.\n\"run\" launches preview (platform: browser/simulator/editor). \"stop\" stops editor preview. \"reload\" reloads editor window (Developer → 重新加载).\nExample: {action:\"run\", platform:\"editor\"} then {action:\"stop\"}" + }, + { + "category": "cocos", + "name": "view", + "enabled": true, + "description": "Viewport control — gizmo tools, 2D/3D mode, camera focus/align, reference images.\nCommon: gizmo_tool, camera_focus, camera_align_view (sync camera→view), camera_align_node (sync view→node).\nExample: {action:\"camera_focus\", nodes:[\"Player\"]} or {action:\"camera_align_node\", nodes:[\"Camera\"]}" + }, + { + "category": "cocos", + "name": "composite", + "enabled": true, + "description": "One-call UI creation — create complete Button/Label/Image, mount script with bindings.\nActions: create_button, create_label, create_image, mount_and_bind, setup_widget, batch, batch_create_button, batch_create_label, batch_create_image.\nNote: mount_and_bind mounts script AND binds properties in one call (NOT node.mount_script which only mounts). create_button supports optional clickEvent to bind click handler in one call.\nBatch hint: 3+ buttons → batch_create_button, 3+ labels → batch_create_label, 3+ images → batch_create_image.\nExample: {action:\"mount_and_bind\", node:\"Hero\", scriptPath:\"db://assets/scripts/Game.ts\", bindings:{...}}" + }, + { + "category": "cocos", + "name": "knowledge", + "enabled": true, + "description": "Cocos Creator reference — component properties, UI rules, layout patterns, animation recipes, best practices, tool guides.\nUse topic:\"tool_guide\" with query:\"tool.action\" before calling unfamiliar tools.\nExample: {topic:\"component_properties\", query:\"cc.Label\"}, {topic:\"animation_patterns\", query:\"scale_bounce\"}" + }, + { + "category": "cocos", + "name": "validate", + "enabled": true, + "description": "Advanced validation rules — layout overlap/offscreen, reference consistency, hierarchy depth.\nNote: Use this for deep inspection (NOT scene.validate_scene which only checks broken refs).\nActions: layout (overlap/offscreen), references (broken asset refs), hierarchy (depth/naming).\nExample: {action:\"layout\"} or {action:\"references\"}" + }, + { + "category": "cocos", + "name": "template", + "enabled": true, + "description": "UI templates — apply pre-built structures (dialog, scroll_list, nav_bar, settings_page).\nNote: Use this (NOT composite/builder) when user wants a standard UI pattern. Use list to show available templates.\nActions: list, apply.\nExample: {action:\"list\"} or {action:\"apply\", template:\"dialog\", parent:\"Canvas\"}" + }, + { + "category": "cocos", + "name": "capture", + "enabled": true, + "description": "Capture — scene layout JSON + real screenshot.\nActions: scene_snapshot (full scene JSON), node_snapshot (focused subtree JSON), screenshot (PNG).\nscreenshot has TWO modes:\n • mode:\"content\" (RECOMMENDED for pixel diffs) — returns a REAL engine render of ONLY the Canvas content at design resolution with a transparent (or chosen) background: NO editor panels/grid/gizmos/FPS overlay. Runs the built web runtime headlessly internally (one MCP call → content PNG). Requires scenePath. Renders the LAST BUILT (saved) scene; reuses build/web-mobile (auto-builds if missing; rebuild:true forces a fresh build, ~60s). Deterministic. Returns pngBase64.\n • mode:\"window\" (default, legacy) — PNG of the Creator editor window via OS capture; includes panels/grid. Returns contentRect (the scene-viewport box, when detectable) so you can crop.\nUse scene/node_snapshot for position/size/component data; use screenshot for a real visual.\nExamples: {action:\"screenshot\", mode:\"content\", scenePath:\"db://assets/scenes/Main.scene\"} or {action:\"screenshot\", mode:\"content\", scenePath:\"Sprite\", width:1280, height:720, background:{r:0,g:0,b:0,a:0}, rebuild:true} or legacy {action:\"screenshot\"}" + }, + { + "category": "cocos", + "name": "builder", + "enabled": true, + "description": "Build node tree from JSON — create complete hierarchies with components in one call.\nNote: Use this (NOT composite/node) when building complex multi-level UI structures. Query tool_guide for JSON format.\nPrefab shortcut: use \"type\":\"Button\" (or Label, Sprite, ScrollView, etc.) instead of components[] to create complete UI controls from engine prefab templates.\nExample: {action:\"build\", parent:\"Canvas\", tree:{name:\"Panel\", components:[\"cc.Sprite\"], children:[{name:\"MyBtn\", type:\"Button\"}]}}" + }, + { + "category": "cocos", + "name": "animation", + "enabled": true, + "description": "Animation editing — clips, keyframes, tracks, events, presets, batch operations.\nRecommended: batch_file (no edit mode needed), fallback to batch. Use preset for common animations.\nExample: {action:\"preset\", clipUuid:\"...\", preset:\"sprite_fade_in\"} or {action:\"add_event\", clipUuid:\"...\", frame:10, eventData:{func:\"onHit\"}}" + }, + { + "category": "cocos", + "name": "spine", + "enabled": true, + "description": "Spine (sp.Skeleton) — query animations/skins, set animation/skin, change properties, swap skeleton data, manage sockets.\nActions: info, list_animations, list_skins, set_animation, set_skin, set_property, set_data, add_socket, remove_socket.\nNote: \"node\" is the node path or UUID with sp.Skeleton component.\nExample: {action:\"list_animations\", node:\"spinNode\"} or {action:\"set_animation\", node:\"spinNode\", animation:\"idle\"}" + }, + { + "category": "cocos", + "name": "label", + "enabled": true, + "description": "Label & Text — manage cc.Label, cc.RichText, cc.EditBox. Query/set text, font, style, outline, shadow. Batch operations.\nActions: info, list, set_text, set_font, set_style, set_outline, set_shadow, batch_set_font, batch_set_style.\nSupports: cc.Label (full), cc.RichText (text/font/fontSize/color), cc.EditBox (text/placeholder).\nExample: {action:\"set_text\", node:\"Title\", text:\"Hello\"}\n{action:\"set_style\", node:\"Title\", fontSize:32, isBold:true, color:{r:255,g:0,b:0,a:255}}\n{action:\"batch_set_font\", font:\"db://assets/fonts/myfont.ttf\"}" + } + ], + "createdAt": "2026-08-18T23:28:53.590Z", + "updatedAt": "2026-08-18T23:28:53.590Z" + } + ], + "currentConfigId": "1a189805-4f21-4147-91ab-461fe9ff5f28", + "maxConfigSlots": 5 +} \ No newline at end of file