#
tokens: 47466/50000 9/37 files (page 2/5)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 2 of 5. Use http://codebase.md/daxianlee/cocos-mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── @types
│   └── schema
│       └── package
│           ├── base
│           │   └── panels.json
│           ├── contributions
│           │   └── index.json
│           └── index.json
├── base.tsconfig.json
├── dist
│   ├── examples
│   │   └── prefab-instantiation-example.js
│   ├── main.js
│   ├── mcp-server.js
│   ├── panels
│   │   ├── default
│   │   │   └── index.js
│   │   └── tool-manager
│   │       └── index.js
│   ├── scene.js
│   ├── settings.js
│   ├── test
│   │   ├── manual-test.js
│   │   ├── mcp-tool-tester.js
│   │   ├── prefab-tools-test.js
│   │   └── tool-tester.js
│   ├── tools
│   │   ├── asset-advanced-tools.js
│   │   ├── broadcast-tools.js
│   │   ├── component-tools.js
│   │   ├── debug-tools.js
│   │   ├── node-tools.js
│   │   ├── prefab-tools.js
│   │   ├── preferences-tools.js
│   │   ├── project-tools.js
│   │   ├── reference-image-tools.js
│   │   ├── scene-advanced-tools.js
│   │   ├── scene-tools.js
│   │   ├── scene-view-tools.js
│   │   ├── server-tools.js
│   │   ├── tool-manager.js
│   │   └── validation-tools.js
│   └── types
│       └── index.js
├── FEATURE_GUIDE_CN.md
├── FEATURE_GUIDE_EN.md
├── i18n
│   ├── en.js
│   └── zh.js
├── image
│   ├── iamge2.png
│   └── image-20250717174157957.png
├── package-lock.json
├── package.json
├── README.EN.md
├── README.md
├── scripts
│   └── preinstall.js
├── source
│   ├── main.ts
│   ├── mcp-server.ts
│   ├── panels
│   │   ├── default
│   │   │   └── index.ts
│   │   └── tool-manager
│   │       └── index.ts
│   ├── scene.ts
│   ├── settings.ts
│   ├── test
│   │   ├── manual-test.ts
│   │   ├── mcp-tool-tester.ts
│   │   ├── prefab-tools-test.ts
│   │   └── tool-tester.ts
│   ├── tools
│   │   ├── asset-advanced-tools.ts
│   │   ├── broadcast-tools.ts
│   │   ├── component-tools.ts
│   │   ├── debug-tools.ts
│   │   ├── node-tools.ts
│   │   ├── prefab-tools.ts
│   │   ├── preferences-tools.ts
│   │   ├── project-tools.ts
│   │   ├── reference-image-tools.ts
│   │   ├── scene-advanced-tools.ts
│   │   ├── scene-tools.ts
│   │   ├── scene-view-tools.ts
│   │   ├── server-tools.ts
│   │   ├── tool-manager.ts
│   │   └── validation-tools.ts
│   └── types
│       └── index.ts
├── static
│   ├── icon.png
│   ├── style
│   │   └── default
│   │       └── index.css
│   └── template
│       ├── default
│       │   ├── index.html
│       │   └── tool-manager.html
│       └── vue
│           └── mcp-server-app.html
├── TestScript.js
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/source/tools/tool-manager.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { v4 as uuidv4 } from 'uuid';
  2 | import { ToolConfig, ToolConfiguration, ToolManagerSettings, ToolDefinition } from '../types';
  3 | import * as fs from 'fs';
  4 | import * as path from 'path';
  5 | 
  6 | export class ToolManager {
  7 |     private settings: ToolManagerSettings;
  8 |     private availableTools: ToolConfig[] = [];
  9 | 
 10 |     constructor() {
 11 |         this.settings = this.readToolManagerSettings();
 12 |         this.initializeAvailableTools();
 13 |         
 14 |         // 如果没有配置,自动创建一个默认配置
 15 |         if (this.settings.configurations.length === 0) {
 16 |             console.log('[ToolManager] No configurations found, creating default configuration...');
 17 |             this.createConfiguration('默认配置', '自动创建的默认工具配置');
 18 |         }
 19 |     }
 20 | 
 21 |     private getToolManagerSettingsPath(): string {
 22 |         return path.join(Editor.Project.path, 'settings', 'tool-manager.json');
 23 |     }
 24 | 
 25 |     private ensureSettingsDir(): void {
 26 |         const settingsDir = path.dirname(this.getToolManagerSettingsPath());
 27 |         if (!fs.existsSync(settingsDir)) {
 28 |             fs.mkdirSync(settingsDir, { recursive: true });
 29 |         }
 30 |     }
 31 | 
 32 |     private readToolManagerSettings(): ToolManagerSettings {
 33 |         const DEFAULT_TOOL_MANAGER_SETTINGS: ToolManagerSettings = {
 34 |             configurations: [],
 35 |             currentConfigId: '',
 36 |             maxConfigSlots: 5
 37 |         };
 38 | 
 39 |         try {
 40 |             this.ensureSettingsDir();
 41 |             const settingsFile = this.getToolManagerSettingsPath();
 42 |             if (fs.existsSync(settingsFile)) {
 43 |                 const content = fs.readFileSync(settingsFile, 'utf8');
 44 |                 return { ...DEFAULT_TOOL_MANAGER_SETTINGS, ...JSON.parse(content) };
 45 |             }
 46 |         } catch (e) {
 47 |             console.error('Failed to read tool manager settings:', e);
 48 |         }
 49 |         return DEFAULT_TOOL_MANAGER_SETTINGS;
 50 |     }
 51 | 
 52 |     private saveToolManagerSettings(settings: ToolManagerSettings): void {
 53 |         try {
 54 |             this.ensureSettingsDir();
 55 |             const settingsFile = this.getToolManagerSettingsPath();
 56 |             fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2));
 57 |         } catch (e) {
 58 |             console.error('Failed to save tool manager settings:', e);
 59 |             throw e;
 60 |         }
 61 |     }
 62 | 
 63 |     private exportToolConfiguration(config: ToolConfiguration): string {
 64 |         return JSON.stringify(config, null, 2);
 65 |     }
 66 | 
 67 |     private importToolConfiguration(configJson: string): ToolConfiguration {
 68 |         try {
 69 |             const config = JSON.parse(configJson);
 70 |             // 验证配置格式
 71 |             if (!config.id || !config.name || !Array.isArray(config.tools)) {
 72 |                 throw new Error('Invalid configuration format');
 73 |             }
 74 |             return config;
 75 |         } catch (e) {
 76 |             console.error('Failed to parse tool configuration:', e);
 77 |             throw new Error('Invalid JSON format or configuration structure');
 78 |         }
 79 |     }
 80 | 
 81 |     private initializeAvailableTools(): void {
 82 |         // 从MCP服务器获取真实的工具列表
 83 |         try {
 84 |             // 导入所有工具类
 85 |             const { SceneTools } = require('./scene-tools');
 86 |             const { NodeTools } = require('./node-tools');
 87 |             const { ComponentTools } = require('./component-tools');
 88 |             const { PrefabTools } = require('./prefab-tools');
 89 |             const { ProjectTools } = require('./project-tools');
 90 |             const { DebugTools } = require('./debug-tools');
 91 |             const { PreferencesTools } = require('./preferences-tools');
 92 |             const { ServerTools } = require('./server-tools');
 93 |             const { BroadcastTools } = require('./broadcast-tools');
 94 |             const { SceneAdvancedTools } = require('./scene-advanced-tools');
 95 |             const { SceneViewTools } = require('./scene-view-tools');
 96 |             const { ReferenceImageTools } = require('./reference-image-tools');
 97 |             const { AssetAdvancedTools } = require('./asset-advanced-tools');
 98 |             const { ValidationTools } = require('./validation-tools');
 99 | 
100 |             // 初始化工具实例
101 |             const tools = {
102 |                 scene: new SceneTools(),
103 |                 node: new NodeTools(),
104 |                 component: new ComponentTools(),
105 |                 prefab: new PrefabTools(),
106 |                 project: new ProjectTools(),
107 |                 debug: new DebugTools(),
108 |                 preferences: new PreferencesTools(),
109 |                 server: new ServerTools(),
110 |                 broadcast: new BroadcastTools(),
111 |                 sceneAdvanced: new SceneAdvancedTools(),
112 |                 sceneView: new SceneViewTools(),
113 |                 referenceImage: new ReferenceImageTools(),
114 |                 assetAdvanced: new AssetAdvancedTools(),
115 |                 validation: new ValidationTools()
116 |             };
117 | 
118 |             // 从每个工具类获取工具列表
119 |             this.availableTools = [];
120 |             for (const [category, toolSet] of Object.entries(tools)) {
121 |                 const toolDefinitions = toolSet.getTools();
122 |                 toolDefinitions.forEach((tool: any) => {
123 |                     this.availableTools.push({
124 |                         category: category,
125 |                         name: tool.name,
126 |                         enabled: true, // 默认启用
127 |                         description: tool.description
128 |                     });
129 |                 });
130 |             }
131 | 
132 |             console.log(`[ToolManager] Initialized ${this.availableTools.length} tools from MCP server`);
133 |         } catch (error) {
134 |             console.error('[ToolManager] Failed to initialize tools from MCP server:', error);
135 |             // 如果获取失败,使用默认工具列表作为后备
136 |             this.initializeDefaultTools();
137 |         }
138 |     }
139 | 
140 |     private initializeDefaultTools(): void {
141 |         // 默认工具列表作为后备方案
142 |         const toolCategories = [
143 |             { category: 'scene', name: '场景工具', tools: [
144 |                 { name: 'getCurrentSceneInfo', description: '获取当前场景信息' },
145 |                 { name: 'getSceneHierarchy', description: '获取场景层级结构' },
146 |                 { name: 'createNewScene', description: '创建新场景' },
147 |                 { name: 'saveScene', description: '保存场景' },
148 |                 { name: 'loadScene', description: '加载场景' }
149 |             ]},
150 |             { category: 'node', name: '节点工具', tools: [
151 |                 { name: 'getAllNodes', description: '获取所有节点' },
152 |                 { name: 'findNodeByName', description: '根据名称查找节点' },
153 |                 { name: 'createNode', description: '创建节点' },
154 |                 { name: 'deleteNode', description: '删除节点' },
155 |                 { name: 'setNodeProperty', description: '设置节点属性' },
156 |                 { name: 'getNodeInfo', description: '获取节点信息' }
157 |             ]},
158 |             { category: 'component', name: '组件工具', tools: [
159 |                 { name: 'addComponentToNode', description: '添加组件到节点' },
160 |                 { name: 'removeComponentFromNode', description: '从节点移除组件' },
161 |                 { name: 'setComponentProperty', description: '设置组件属性' },
162 |                 { name: 'getComponentInfo', description: '获取组件信息' }
163 |             ]},
164 |             { category: 'prefab', name: '预制体工具', tools: [
165 |                 { name: 'createPrefabFromNode', description: '从节点创建预制体' },
166 |                 { name: 'instantiatePrefab', description: '实例化预制体' },
167 |                 { name: 'getPrefabInfo', description: '获取预制体信息' },
168 |                 { name: 'savePrefab', description: '保存预制体' }
169 |             ]},
170 |             { category: 'project', name: '项目工具', tools: [
171 |                 { name: 'getProjectInfo', description: '获取项目信息' },
172 |                 { name: 'getAssetList', description: '获取资源列表' },
173 |                 { name: 'createAsset', description: '创建资源' },
174 |                 { name: 'deleteAsset', description: '删除资源' }
175 |             ]},
176 |             { category: 'debug', name: '调试工具', tools: [
177 |                 { name: 'getConsoleLogs', description: '获取控制台日志' },
178 |                 { name: 'getPerformanceStats', description: '获取性能统计' },
179 |                 { name: 'validateScene', description: '验证场景' },
180 |                 { name: 'getErrorLogs', description: '获取错误日志' }
181 |             ]},
182 |             { category: 'preferences', name: '偏好设置工具', tools: [
183 |                 { name: 'getPreferences', description: '获取偏好设置' },
184 |                 { name: 'setPreferences', description: '设置偏好设置' },
185 |                 { name: 'resetPreferences', description: '重置偏好设置' }
186 |             ]},
187 |             { category: 'server', name: '服务器工具', tools: [
188 |                 { name: 'getServerStatus', description: '获取服务器状态' },
189 |                 { name: 'getConnectedClients', description: '获取连接的客户端' },
190 |                 { name: 'getServerLogs', description: '获取服务器日志' }
191 |             ]},
192 |             { category: 'broadcast', name: '广播工具', tools: [
193 |                 { name: 'broadcastMessage', description: '广播消息' },
194 |                 { name: 'getBroadcastHistory', description: '获取广播历史' }
195 |             ]},
196 |             { category: 'sceneAdvanced', name: '高级场景工具', tools: [
197 |                 { name: 'optimizeScene', description: '优化场景' },
198 |                 { name: 'analyzeScene', description: '分析场景' },
199 |                 { name: 'batchOperation', description: '批量操作' }
200 |             ]},
201 |             { category: 'sceneView', name: '场景视图工具', tools: [
202 |                 { name: 'getViewportInfo', description: '获取视口信息' },
203 |                 { name: 'setViewportCamera', description: '设置视口相机' },
204 |                 { name: 'focusOnNode', description: '聚焦到节点' }
205 |             ]},
206 |             { category: 'referenceImage', name: '参考图片工具', tools: [
207 |                 { name: 'addReferenceImage', description: '添加参考图片' },
208 |                 { name: 'removeReferenceImage', description: '移除参考图片' },
209 |                 { name: 'getReferenceImages', description: '获取参考图片列表' }
210 |             ]},
211 |             { category: 'assetAdvanced', name: '高级资源工具', tools: [
212 |                 { name: 'importAsset', description: '导入资源' },
213 |                 { name: 'exportAsset', description: '导出资源' },
214 |                 { name: 'processAsset', description: '处理资源' }
215 |             ]},
216 |             { category: 'validation', name: '验证工具', tools: [
217 |                 { name: 'validateProject', description: '验证项目' },
218 |                 { name: 'validateAssets', description: '验证资源' },
219 |                 { name: 'generateReport', description: '生成报告' }
220 |             ]}
221 |         ];
222 | 
223 |         this.availableTools = [];
224 |         toolCategories.forEach(category => {
225 |             category.tools.forEach(tool => {
226 |                 this.availableTools.push({
227 |                     category: category.category,
228 |                     name: tool.name,
229 |                     enabled: true, // 默认启用
230 |                     description: tool.description
231 |                 });
232 |             });
233 |         });
234 | 
235 |         console.log(`[ToolManager] Initialized ${this.availableTools.length} default tools`);
236 |     }
237 | 
238 |     public getAvailableTools(): ToolConfig[] {
239 |         return [...this.availableTools];
240 |     }
241 | 
242 |     public getConfigurations(): ToolConfiguration[] {
243 |         return [...this.settings.configurations];
244 |     }
245 | 
246 |     public getCurrentConfiguration(): ToolConfiguration | null {
247 |         if (!this.settings.currentConfigId) {
248 |             return null;
249 |         }
250 |         return this.settings.configurations.find(config => config.id === this.settings.currentConfigId) || null;
251 |     }
252 | 
253 |     public createConfiguration(name: string, description?: string): ToolConfiguration {
254 |         if (this.settings.configurations.length >= this.settings.maxConfigSlots) {
255 |             throw new Error(`已达到最大配置槽位数量 (${this.settings.maxConfigSlots})`);
256 |         }
257 | 
258 |         const config: ToolConfiguration = {
259 |             id: uuidv4(),
260 |             name,
261 |             description,
262 |             tools: this.availableTools.map(tool => ({ ...tool })),
263 |             createdAt: new Date().toISOString(),
264 |             updatedAt: new Date().toISOString()
265 |         };
266 | 
267 |         this.settings.configurations.push(config);
268 |         this.settings.currentConfigId = config.id;
269 |         this.saveSettings();
270 | 
271 |         return config;
272 |     }
273 | 
274 |     public updateConfiguration(configId: string, updates: Partial<ToolConfiguration>): ToolConfiguration {
275 |         const configIndex = this.settings.configurations.findIndex(config => config.id === configId);
276 |         if (configIndex === -1) {
277 |             throw new Error('配置不存在');
278 |         }
279 | 
280 |         const config = this.settings.configurations[configIndex];
281 |         const updatedConfig: ToolConfiguration = {
282 |             ...config,
283 |             ...updates,
284 |             updatedAt: new Date().toISOString()
285 |         };
286 | 
287 |         this.settings.configurations[configIndex] = updatedConfig;
288 |         this.saveSettings();
289 | 
290 |         return updatedConfig;
291 |     }
292 | 
293 |     public deleteConfiguration(configId: string): void {
294 |         const configIndex = this.settings.configurations.findIndex(config => config.id === configId);
295 |         if (configIndex === -1) {
296 |             throw new Error('配置不存在');
297 |         }
298 | 
299 |         this.settings.configurations.splice(configIndex, 1);
300 |         
301 |         // 如果删除的是当前配置,清空当前配置ID
302 |         if (this.settings.currentConfigId === configId) {
303 |             this.settings.currentConfigId = this.settings.configurations.length > 0 
304 |                 ? this.settings.configurations[0].id 
305 |                 : '';
306 |         }
307 | 
308 |         this.saveSettings();
309 |     }
310 | 
311 |     public setCurrentConfiguration(configId: string): void {
312 |         const config = this.settings.configurations.find(config => config.id === configId);
313 |         if (!config) {
314 |             throw new Error('配置不存在');
315 |         }
316 | 
317 |         this.settings.currentConfigId = configId;
318 |         this.saveSettings();
319 |     }
320 | 
321 |     public updateToolStatus(configId: string, category: string, toolName: string, enabled: boolean): void {
322 |         console.log(`Backend: Updating tool status - configId: ${configId}, category: ${category}, toolName: ${toolName}, enabled: ${enabled}`);
323 |         
324 |         const config = this.settings.configurations.find(config => config.id === configId);
325 |         if (!config) {
326 |             console.error(`Backend: Config not found with ID: ${configId}`);
327 |             throw new Error('配置不存在');
328 |         }
329 | 
330 |         console.log(`Backend: Found config: ${config.name}`);
331 | 
332 |         const tool = config.tools.find(t => t.category === category && t.name === toolName);
333 |         if (!tool) {
334 |             console.error(`Backend: Tool not found - category: ${category}, name: ${toolName}`);
335 |             throw new Error('工具不存在');
336 |         }
337 | 
338 |         console.log(`Backend: Found tool: ${tool.name}, current enabled: ${tool.enabled}, new enabled: ${enabled}`);
339 |         
340 |         tool.enabled = enabled;
341 |         config.updatedAt = new Date().toISOString();
342 |         
343 |         console.log(`Backend: Tool updated, saving settings...`);
344 |         this.saveSettings();
345 |         console.log(`Backend: Settings saved successfully`);
346 |     }
347 | 
348 |     public updateToolStatusBatch(configId: string, updates: { category: string; name: string; enabled: boolean }[]): void {
349 |         console.log(`Backend: updateToolStatusBatch called with configId: ${configId}`);
350 |         console.log(`Backend: Current configurations count: ${this.settings.configurations.length}`);
351 |         console.log(`Backend: Current config IDs:`, this.settings.configurations.map(c => c.id));
352 |         
353 |         const config = this.settings.configurations.find(config => config.id === configId);
354 |         if (!config) {
355 |             console.error(`Backend: Config not found with ID: ${configId}`);
356 |             console.error(`Backend: Available config IDs:`, this.settings.configurations.map(c => c.id));
357 |             throw new Error('配置不存在');
358 |         }
359 | 
360 |         console.log(`Backend: Found config: ${config.name}, updating ${updates.length} tools`);
361 | 
362 |         updates.forEach(update => {
363 |             const tool = config.tools.find(t => t.category === update.category && t.name === update.name);
364 |             if (tool) {
365 |                 tool.enabled = update.enabled;
366 |             }
367 |         });
368 | 
369 |         config.updatedAt = new Date().toISOString();
370 |         this.saveSettings();
371 |         console.log(`Backend: Batch update completed successfully`);
372 |     }
373 | 
374 |     public exportConfiguration(configId: string): string {
375 |         const config = this.settings.configurations.find(config => config.id === configId);
376 |         if (!config) {
377 |             throw new Error('配置不存在');
378 |         }
379 | 
380 |         return this.exportToolConfiguration(config);
381 |     }
382 | 
383 |     public importConfiguration(configJson: string): ToolConfiguration {
384 |         const config = this.importToolConfiguration(configJson);
385 |         
386 |         // 生成新的ID和时间戳
387 |         config.id = uuidv4();
388 |         config.createdAt = new Date().toISOString();
389 |         config.updatedAt = new Date().toISOString();
390 | 
391 |         if (this.settings.configurations.length >= this.settings.maxConfigSlots) {
392 |             throw new Error(`已达到最大配置槽位数量 (${this.settings.maxConfigSlots})`);
393 |         }
394 | 
395 |         this.settings.configurations.push(config);
396 |         this.saveSettings();
397 | 
398 |         return config;
399 |     }
400 | 
401 |     public getEnabledTools(): ToolConfig[] {
402 |         const currentConfig = this.getCurrentConfiguration();
403 |         if (!currentConfig) {
404 |             return this.availableTools.filter(tool => tool.enabled);
405 |         }
406 |         return currentConfig.tools.filter(tool => tool.enabled);
407 |     }
408 | 
409 |     public getToolManagerState() {
410 |         const currentConfig = this.getCurrentConfiguration();
411 |         return {
412 |             success: true,
413 |             availableTools: currentConfig ? currentConfig.tools : this.getAvailableTools(),
414 |             selectedConfigId: this.settings.currentConfigId,
415 |             configurations: this.getConfigurations(),
416 |             maxConfigSlots: this.settings.maxConfigSlots
417 |         };
418 |     }
419 | 
420 |     private saveSettings(): void {
421 |         console.log(`Backend: Saving settings, current configs count: ${this.settings.configurations.length}`);
422 |         this.saveToolManagerSettings(this.settings);
423 |         console.log(`Backend: Settings saved to file`);
424 |     }
425 | } 
```

--------------------------------------------------------------------------------
/source/mcp-server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import * as http from 'http';
  2 | import * as url from 'url';
  3 | import { v4 as uuidv4 } from 'uuid';
  4 | import { MCPServerSettings, ServerStatus, MCPClient, ToolDefinition } from './types';
  5 | import { SceneTools } from './tools/scene-tools';
  6 | import { NodeTools } from './tools/node-tools';
  7 | import { ComponentTools } from './tools/component-tools';
  8 | import { PrefabTools } from './tools/prefab-tools';
  9 | import { ProjectTools } from './tools/project-tools';
 10 | import { DebugTools } from './tools/debug-tools';
 11 | import { PreferencesTools } from './tools/preferences-tools';
 12 | import { ServerTools } from './tools/server-tools';
 13 | import { BroadcastTools } from './tools/broadcast-tools';
 14 | import { SceneAdvancedTools } from './tools/scene-advanced-tools';
 15 | import { SceneViewTools } from './tools/scene-view-tools';
 16 | import { ReferenceImageTools } from './tools/reference-image-tools';
 17 | import { AssetAdvancedTools } from './tools/asset-advanced-tools';
 18 | import { ValidationTools } from './tools/validation-tools';
 19 | 
 20 | export class MCPServer {
 21 |     private settings: MCPServerSettings;
 22 |     private httpServer: http.Server | null = null;
 23 |     private clients: Map<string, MCPClient> = new Map();
 24 |     private tools: Record<string, any> = {};
 25 |     private toolsList: ToolDefinition[] = [];
 26 |     private enabledTools: any[] = []; // 存储启用的工具列表
 27 | 
 28 |     constructor(settings: MCPServerSettings) {
 29 |         this.settings = settings;
 30 |         this.initializeTools();
 31 |     }
 32 | 
 33 |     private initializeTools(): void {
 34 |         try {
 35 |             console.log('[MCPServer] Initializing tools...');
 36 |             this.tools.scene = new SceneTools();
 37 |             this.tools.node = new NodeTools();
 38 |             this.tools.component = new ComponentTools();
 39 |             this.tools.prefab = new PrefabTools();
 40 |             this.tools.project = new ProjectTools();
 41 |             this.tools.debug = new DebugTools();
 42 |             this.tools.preferences = new PreferencesTools();
 43 |             this.tools.server = new ServerTools();
 44 |             this.tools.broadcast = new BroadcastTools();
 45 |             this.tools.sceneAdvanced = new SceneAdvancedTools();
 46 |             this.tools.sceneView = new SceneViewTools();
 47 |             this.tools.referenceImage = new ReferenceImageTools();
 48 |             this.tools.assetAdvanced = new AssetAdvancedTools();
 49 |             this.tools.validation = new ValidationTools();
 50 |             console.log('[MCPServer] Tools initialized successfully');
 51 |         } catch (error) {
 52 |             console.error('[MCPServer] Error initializing tools:', error);
 53 |             throw error;
 54 |         }
 55 |     }
 56 | 
 57 |     public async start(): Promise<void> {
 58 |         if (this.httpServer) {
 59 |             console.log('[MCPServer] Server is already running');
 60 |             return;
 61 |         }
 62 | 
 63 |         try {
 64 |             console.log(`[MCPServer] Starting HTTP server on port ${this.settings.port}...`);
 65 |             this.httpServer = http.createServer(this.handleHttpRequest.bind(this));
 66 | 
 67 |             await new Promise<void>((resolve, reject) => {
 68 |                 this.httpServer!.listen(this.settings.port, '127.0.0.1', () => {
 69 |                     console.log(`[MCPServer] ✅ HTTP server started successfully on http://127.0.0.1:${this.settings.port}`);
 70 |                     console.log(`[MCPServer] Health check: http://127.0.0.1:${this.settings.port}/health`);
 71 |                     console.log(`[MCPServer] MCP endpoint: http://127.0.0.1:${this.settings.port}/mcp`);
 72 |                     resolve();
 73 |                 });
 74 |                 this.httpServer!.on('error', (err: any) => {
 75 |                     console.error('[MCPServer] ❌ Failed to start server:', err);
 76 |                     if (err.code === 'EADDRINUSE') {
 77 |                         console.error(`[MCPServer] Port ${this.settings.port} is already in use. Please change the port in settings.`);
 78 |                     }
 79 |                     reject(err);
 80 |                 });
 81 |             });
 82 | 
 83 |             this.setupTools();
 84 |             console.log('[MCPServer] 🚀 MCP Server is ready for connections');
 85 |         } catch (error) {
 86 |             console.error('[MCPServer] ❌ Failed to start server:', error);
 87 |             throw error;
 88 |         }
 89 |     }
 90 | 
 91 |     private setupTools(): void {
 92 |         this.toolsList = [];
 93 |         
 94 |         // 如果没有启用工具配置,返回所有工具
 95 |         if (!this.enabledTools || this.enabledTools.length === 0) {
 96 |             for (const [category, toolSet] of Object.entries(this.tools)) {
 97 |                 const tools = toolSet.getTools();
 98 |                 for (const tool of tools) {
 99 |                     this.toolsList.push({
100 |                         name: `${category}_${tool.name}`,
101 |                         description: tool.description,
102 |                         inputSchema: tool.inputSchema
103 |                     });
104 |                 }
105 |             }
106 |         } else {
107 |             // 根据启用的工具配置过滤
108 |             const enabledToolNames = new Set(this.enabledTools.map(tool => `${tool.category}_${tool.name}`));
109 |             
110 |             for (const [category, toolSet] of Object.entries(this.tools)) {
111 |                 const tools = toolSet.getTools();
112 |                 for (const tool of tools) {
113 |                     const toolName = `${category}_${tool.name}`;
114 |                     if (enabledToolNames.has(toolName)) {
115 |                         this.toolsList.push({
116 |                             name: toolName,
117 |                             description: tool.description,
118 |                             inputSchema: tool.inputSchema
119 |                         });
120 |                     }
121 |                 }
122 |             }
123 |         }
124 |         
125 |         console.log(`[MCPServer] Setup tools: ${this.toolsList.length} tools available`);
126 |     }
127 | 
128 |     public getFilteredTools(enabledTools: any[]): ToolDefinition[] {
129 |         if (!enabledTools || enabledTools.length === 0) {
130 |             return this.toolsList; // 如果没有过滤配置,返回所有工具
131 |         }
132 | 
133 |         const enabledToolNames = new Set(enabledTools.map(tool => `${tool.category}_${tool.name}`));
134 |         return this.toolsList.filter(tool => enabledToolNames.has(tool.name));
135 |     }
136 | 
137 |     public async executeToolCall(toolName: string, args: any): Promise<any> {
138 |         const parts = toolName.split('_');
139 |         const category = parts[0];
140 |         const toolMethodName = parts.slice(1).join('_');
141 |         
142 |         if (this.tools[category]) {
143 |             return await this.tools[category].execute(toolMethodName, args);
144 |         }
145 |         
146 |         throw new Error(`Tool ${toolName} not found`);
147 |     }
148 | 
149 |     public getClients(): MCPClient[] {
150 |         return Array.from(this.clients.values());
151 |     }
152 |     public getAvailableTools(): ToolDefinition[] {
153 |         return this.toolsList;
154 |     }
155 | 
156 |     public updateEnabledTools(enabledTools: any[]): void {
157 |         console.log(`[MCPServer] Updating enabled tools: ${enabledTools.length} tools`);
158 |         this.enabledTools = enabledTools;
159 |         this.setupTools(); // 重新设置工具列表
160 |     }
161 | 
162 |     public getSettings(): MCPServerSettings {
163 |         return this.settings;
164 |     }
165 | 
166 |     private async handleHttpRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
167 |         const parsedUrl = url.parse(req.url || '', true);
168 |         const pathname = parsedUrl.pathname;
169 |         
170 |         // Set CORS headers
171 |         res.setHeader('Access-Control-Allow-Origin', '*');
172 |         res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
173 |         res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
174 |         res.setHeader('Content-Type', 'application/json');
175 |         
176 |         if (req.method === 'OPTIONS') {
177 |             res.writeHead(200);
178 |             res.end();
179 |             return;
180 |         }
181 |         
182 |         try {
183 |             if (pathname === '/mcp' && req.method === 'POST') {
184 |                 await this.handleMCPRequest(req, res);
185 |             } else if (pathname === '/health' && req.method === 'GET') {
186 |                 res.writeHead(200);
187 |                 res.end(JSON.stringify({ status: 'ok', tools: this.toolsList.length }));
188 |             } else if (pathname?.startsWith('/api/') && req.method === 'POST') {
189 |                 await this.handleSimpleAPIRequest(req, res, pathname);
190 |             } else if (pathname === '/api/tools' && req.method === 'GET') {
191 |                 res.writeHead(200);
192 |                 res.end(JSON.stringify({ tools: this.getSimplifiedToolsList() }));
193 |             } else {
194 |                 res.writeHead(404);
195 |                 res.end(JSON.stringify({ error: 'Not found' }));
196 |             }
197 |         } catch (error) {
198 |             console.error('HTTP request error:', error);
199 |             res.writeHead(500);
200 |             res.end(JSON.stringify({ error: 'Internal server error' }));
201 |         }
202 |     }
203 |     
204 |     private async handleMCPRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
205 |         let body = '';
206 |         
207 |         req.on('data', (chunk) => {
208 |             body += chunk.toString();
209 |         });
210 |         
211 |         req.on('end', async () => {
212 |             try {
213 |                 // Enhanced JSON parsing with better error handling
214 |                 let message;
215 |                 try {
216 |                     message = JSON.parse(body);
217 |                 } catch (parseError: any) {
218 |                     // Try to fix common JSON issues
219 |                     const fixedBody = this.fixCommonJsonIssues(body);
220 |                     try {
221 |                         message = JSON.parse(fixedBody);
222 |                         console.log('[MCPServer] Fixed JSON parsing issue');
223 |                     } catch (secondError) {
224 |                         throw new Error(`JSON parsing failed: ${parseError.message}. Original body: ${body.substring(0, 500)}...`);
225 |                     }
226 |                 }
227 |                 
228 |                 const response = await this.handleMessage(message);
229 |                 res.writeHead(200);
230 |                 res.end(JSON.stringify(response));
231 |             } catch (error: any) {
232 |                 console.error('Error handling MCP request:', error);
233 |                 res.writeHead(400);
234 |                 res.end(JSON.stringify({
235 |                     jsonrpc: '2.0',
236 |                     id: null,
237 |                     error: {
238 |                         code: -32700,
239 |                         message: `Parse error: ${error.message}`
240 |                     }
241 |                 }));
242 |             }
243 |         });
244 |     }
245 | 
246 |     private async handleMessage(message: any): Promise<any> {
247 |         const { id, method, params } = message;
248 | 
249 |         try {
250 |             let result: any;
251 | 
252 |             switch (method) {
253 |                 case 'tools/list':
254 |                     result = { tools: this.getAvailableTools() };
255 |                     break;
256 |                 case 'tools/call':
257 |                     const { name, arguments: args } = params;
258 |                     const toolResult = await this.executeToolCall(name, args);
259 |                     result = { content: [{ type: 'text', text: JSON.stringify(toolResult) }] };
260 |                     break;
261 |                 case 'initialize':
262 |                     // MCP initialization
263 |                     result = {
264 |                         protocolVersion: '2024-11-05',
265 |                         capabilities: {
266 |                             tools: {}
267 |                         },
268 |                         serverInfo: {
269 |                             name: 'cocos-mcp-server',
270 |                             version: '1.0.0'
271 |                         }
272 |                     };
273 |                     break;
274 |                 default:
275 |                     throw new Error(`Unknown method: ${method}`);
276 |             }
277 | 
278 |             return {
279 |                 jsonrpc: '2.0',
280 |                 id,
281 |                 result
282 |             };
283 |         } catch (error: any) {
284 |             return {
285 |                 jsonrpc: '2.0',
286 |                 id,
287 |                 error: {
288 |                     code: -32603,
289 |                     message: error.message
290 |                 }
291 |             };
292 |         }
293 |     }
294 | 
295 |     private fixCommonJsonIssues(jsonStr: string): string {
296 |         let fixed = jsonStr;
297 |         
298 |         // Fix common escape character issues
299 |         fixed = fixed
300 |             // Fix unescaped quotes in strings
301 |             .replace(/([^\\])"([^"]*[^\\])"([^,}\]:])/g, '$1\\"$2\\"$3')
302 |             // Fix unescaped backslashes
303 |             .replace(/([^\\])\\([^"\\\/bfnrt])/g, '$1\\\\$2')
304 |             // Fix trailing commas
305 |             .replace(/,(\s*[}\]])/g, '$1')
306 |             // Fix single quotes (should be double quotes)
307 |             .replace(/'/g, '"')
308 |             // Fix common control characters
309 |             .replace(/\n/g, '\\n')
310 |             .replace(/\r/g, '\\r')
311 |             .replace(/\t/g, '\\t');
312 |         
313 |         return fixed;
314 |     }
315 | 
316 |     public stop(): void {
317 |         if (this.httpServer) {
318 |             this.httpServer.close();
319 |             this.httpServer = null;
320 |             console.log('[MCPServer] HTTP server stopped');
321 |         }
322 | 
323 |         this.clients.clear();
324 |     }
325 | 
326 |     public getStatus(): ServerStatus {
327 |         return {
328 |             running: !!this.httpServer,
329 |             port: this.settings.port,
330 |             clients: 0 // HTTP is stateless, no persistent clients
331 |         };
332 |     }
333 | 
334 |     private async handleSimpleAPIRequest(req: http.IncomingMessage, res: http.ServerResponse, pathname: string): Promise<void> {
335 |         let body = '';
336 |         
337 |         req.on('data', (chunk) => {
338 |             body += chunk.toString();
339 |         });
340 |         
341 |         req.on('end', async () => {
342 |             try {
343 |                 // Extract tool name from path like /api/node/set_position
344 |                 const pathParts = pathname.split('/').filter(p => p);
345 |                 if (pathParts.length < 3) {
346 |                     res.writeHead(400);
347 |                     res.end(JSON.stringify({ error: 'Invalid API path. Use /api/{category}/{tool_name}' }));
348 |                     return;
349 |                 }
350 |                 
351 |                 const category = pathParts[1];
352 |                 const toolName = pathParts[2];
353 |                 const fullToolName = `${category}_${toolName}`;
354 |                 
355 |                 // Parse parameters with enhanced error handling
356 |                 let params;
357 |                 try {
358 |                     params = body ? JSON.parse(body) : {};
359 |                 } catch (parseError: any) {
360 |                     // Try to fix JSON issues
361 |                     const fixedBody = this.fixCommonJsonIssues(body);
362 |                     try {
363 |                         params = JSON.parse(fixedBody);
364 |                         console.log('[MCPServer] Fixed API JSON parsing issue');
365 |                     } catch (secondError: any) {
366 |                         res.writeHead(400);
367 |                         res.end(JSON.stringify({
368 |                             error: 'Invalid JSON in request body',
369 |                             details: parseError.message,
370 |                             receivedBody: body.substring(0, 200)
371 |                         }));
372 |                         return;
373 |                     }
374 |                 }
375 |                 
376 |                 // Execute tool
377 |                 const result = await this.executeToolCall(fullToolName, params);
378 |                 
379 |                 res.writeHead(200);
380 |                 res.end(JSON.stringify({
381 |                     success: true,
382 |                     tool: fullToolName,
383 |                     result: result
384 |                 }));
385 |                 
386 |             } catch (error: any) {
387 |                 console.error('Simple API error:', error);
388 |                 res.writeHead(500);
389 |                 res.end(JSON.stringify({
390 |                     success: false,
391 |                     error: error.message,
392 |                     tool: pathname
393 |                 }));
394 |             }
395 |         });
396 |     }
397 | 
398 |     private getSimplifiedToolsList(): any[] {
399 |         return this.toolsList.map(tool => {
400 |             const parts = tool.name.split('_');
401 |             const category = parts[0];
402 |             const toolName = parts.slice(1).join('_');
403 |             
404 |             return {
405 |                 name: tool.name,
406 |                 category: category,
407 |                 toolName: toolName,
408 |                 description: tool.description,
409 |                 apiPath: `/api/${category}/${toolName}`,
410 |                 curlExample: this.generateCurlExample(category, toolName, tool.inputSchema)
411 |             };
412 |         });
413 |     }
414 | 
415 |     private generateCurlExample(category: string, toolName: string, schema: any): string {
416 |         // Generate sample parameters based on schema
417 |         const sampleParams = this.generateSampleParams(schema);
418 |         const jsonString = JSON.stringify(sampleParams, null, 2);
419 |         
420 |         return `curl -X POST http://127.0.0.1:8585/api/${category}/${toolName} \\
421 |   -H "Content-Type: application/json" \\
422 |   -d '${jsonString}'`;
423 |     }
424 | 
425 |     private generateSampleParams(schema: any): any {
426 |         if (!schema || !schema.properties) return {};
427 |         
428 |         const sample: any = {};
429 |         for (const [key, prop] of Object.entries(schema.properties as any)) {
430 |             const propSchema = prop as any;
431 |             switch (propSchema.type) {
432 |                 case 'string':
433 |                     sample[key] = propSchema.default || 'example_string';
434 |                     break;
435 |                 case 'number':
436 |                     sample[key] = propSchema.default || 42;
437 |                     break;
438 |                 case 'boolean':
439 |                     sample[key] = propSchema.default || true;
440 |                     break;
441 |                 case 'object':
442 |                     sample[key] = propSchema.default || { x: 0, y: 0, z: 0 };
443 |                     break;
444 |                 default:
445 |                     sample[key] = 'example_value';
446 |             }
447 |         }
448 |         return sample;
449 |     }
450 | 
451 |     public updateSettings(settings: MCPServerSettings) {
452 |         this.settings = settings;
453 |         if (this.httpServer) {
454 |             this.stop();
455 |             this.start();
456 |         }
457 |     }
458 | }
459 | 
460 | // HTTP transport doesn't need persistent connections
461 | // MCP over HTTP uses request-response pattern
```

--------------------------------------------------------------------------------
/source/panels/default/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | /* eslint-disable vue/one-component-per-file */
  2 | 
  3 | import { readFileSync } from 'fs-extra';
  4 | import { join } from 'path';
  5 | import { createApp, App, defineComponent, ref, computed, onMounted, watch, nextTick } from 'vue';
  6 | 
  7 | const panelDataMap = new WeakMap<any, App>();
  8 | 
  9 | // 定义工具配置接口
 10 | interface ToolConfig {
 11 |     category: string;
 12 |     name: string;
 13 |     enabled: boolean;
 14 |     description: string;
 15 | }
 16 | 
 17 | // 定义配置接口
 18 | interface Configuration {
 19 |     id: string;
 20 |     name: string;
 21 |     description: string;
 22 |     tools: ToolConfig[];
 23 |     createdAt: string;
 24 |     updatedAt: string;
 25 | }
 26 | 
 27 | // 定义服务器设置接口
 28 | interface ServerSettings {
 29 |     port: number;
 30 |     autoStart: boolean;
 31 |     debugLog: boolean;
 32 |     maxConnections: number;
 33 | }
 34 | 
 35 | module.exports = Editor.Panel.define({
 36 |     listeners: {
 37 |         show() { 
 38 |             console.log('[MCP Panel] Panel shown'); 
 39 |         },
 40 |         hide() { 
 41 |             console.log('[MCP Panel] Panel hidden'); 
 42 |         },
 43 |     },
 44 |     template: readFileSync(join(__dirname, '../../../static/template/default/index.html'), 'utf-8'),
 45 |     style: readFileSync(join(__dirname, '../../../static/style/default/index.css'), 'utf-8'),
 46 |     $: {
 47 |         app: '#app',
 48 |         panelTitle: '#panelTitle',
 49 |     },
 50 |     ready() {
 51 |         if (this.$.app) {
 52 |             const app = createApp({});
 53 |             app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('ui-');
 54 |             
 55 |             // 创建主应用组件
 56 |             app.component('McpServerApp', defineComponent({
 57 |                 setup() {
 58 |                     // 响应式数据
 59 |                     const activeTab = ref('server');
 60 |                     const serverRunning = ref(false);
 61 |                     const serverStatus = ref('已停止');
 62 |                     const connectedClients = ref(0);
 63 |                     const httpUrl = ref('');
 64 |                     const isProcessing = ref(false);
 65 |                     
 66 |                     const settings = ref<ServerSettings>({
 67 |                         port: 3000,
 68 |                         autoStart: false,
 69 |                         debugLog: false,
 70 |                         maxConnections: 10
 71 |                     });
 72 |                     
 73 |                     const availableTools = ref<ToolConfig[]>([]);
 74 |                     const toolCategories = ref<string[]>([]);
 75 |                     
 76 | 
 77 |                     
 78 |                     // 计算属性
 79 |                     const statusClass = computed(() => ({
 80 |                         'status-running': serverRunning.value,
 81 |                         'status-stopped': !serverRunning.value
 82 |                     }));
 83 |                     
 84 |                     const totalTools = computed(() => availableTools.value.length);
 85 |                     const enabledTools = computed(() => availableTools.value.filter(t => t.enabled).length);
 86 |                     const disabledTools = computed(() => totalTools.value - enabledTools.value);
 87 |                     
 88 | 
 89 |                     
 90 |                     const settingsChanged = ref(false);
 91 |                     
 92 |                     // 方法
 93 |                     const switchTab = (tabName: string) => {
 94 |                         activeTab.value = tabName;
 95 |                         if (tabName === 'tools') {
 96 |                             loadToolManagerState();
 97 |                         }
 98 |                     };
 99 |                     
100 |                     const toggleServer = async () => {
101 |                         try {
102 |                             if (serverRunning.value) {
103 |                                 await Editor.Message.request('cocos-mcp-server', 'stop-server');
104 |                             } else {
105 |                                 // 启动服务器时使用当前面板设置
106 |                                 const currentSettings = {
107 |                                     port: settings.value.port,
108 |                                     autoStart: settings.value.autoStart,
109 |                                     enableDebugLog: settings.value.debugLog,
110 |                                     maxConnections: settings.value.maxConnections
111 |                                 };
112 |                                 await Editor.Message.request('cocos-mcp-server', 'update-settings', currentSettings);
113 |                                 await Editor.Message.request('cocos-mcp-server', 'start-server');
114 |                             }
115 |                             console.log('[Vue App] Server toggled');
116 |                         } catch (error) {
117 |                             console.error('[Vue App] Failed to toggle server:', error);
118 |                         }
119 |                     };
120 |                     
121 |                     const saveSettings = async () => {
122 |                         try {
123 |                             // 创建一个简单的对象,避免克隆错误
124 |                             const settingsData = {
125 |                                 port: settings.value.port,
126 |                                 autoStart: settings.value.autoStart,
127 |                                 debugLog: settings.value.debugLog,
128 |                                 maxConnections: settings.value.maxConnections
129 |                             };
130 |                             
131 |                             const result = await Editor.Message.request('cocos-mcp-server', 'update-settings', settingsData);
132 |                             console.log('[Vue App] Save settings result:', result);
133 |                             settingsChanged.value = false;
134 |                         } catch (error) {
135 |                             console.error('[Vue App] Failed to save settings:', error);
136 |                         }
137 |                     };
138 |                     
139 |                     const copyUrl = async () => {
140 |                         try {
141 |                             await navigator.clipboard.writeText(httpUrl.value);
142 |                             console.log('[Vue App] URL copied to clipboard');
143 |                         } catch (error) {
144 |                             console.error('[Vue App] Failed to copy URL:', error);
145 |                         }
146 |                     };
147 |                     
148 |                     const loadToolManagerState = async () => {
149 |                         try {
150 |                             const result = await Editor.Message.request('cocos-mcp-server', 'getToolManagerState');
151 |                             if (result && result.success) {
152 |                                 // 总是加载后端状态,确保数据是最新的
153 |                                 availableTools.value = result.availableTools || [];
154 |                                 console.log('[Vue App] Loaded tools:', availableTools.value.length);
155 |                                 
156 |                                 // 更新工具分类
157 |                                 const categories = new Set(availableTools.value.map(tool => tool.category));
158 |                                 toolCategories.value = Array.from(categories);
159 |                             }
160 |                         } catch (error) {
161 |                             console.error('[Vue App] Failed to load tool manager state:', error);
162 |                         }
163 |                     };
164 |                     
165 |                     const updateToolStatus = async (category: string, name: string, enabled: boolean) => {
166 |                         try {
167 |                             console.log('[Vue App] updateToolStatus called:', category, name, enabled);
168 |                             
169 |                             // 先更新本地状态
170 |                             const toolIndex = availableTools.value.findIndex(t => t.category === category && t.name === name);
171 |                             if (toolIndex !== -1) {
172 |                                 availableTools.value[toolIndex].enabled = enabled;
173 |                                 // 强制触发响应式更新
174 |                                 availableTools.value = [...availableTools.value];
175 |                                 console.log('[Vue App] Local state updated, tool enabled:', availableTools.value[toolIndex].enabled);
176 |                             }
177 |                             
178 |                             // 调用后端更新
179 |                             const result = await Editor.Message.request('cocos-mcp-server', 'updateToolStatus', category, name, enabled);
180 |                             if (!result || !result.success) {
181 |                                 // 如果后端更新失败,回滚本地状态
182 |                                 if (toolIndex !== -1) {
183 |                                     availableTools.value[toolIndex].enabled = !enabled;
184 |                                     availableTools.value = [...availableTools.value];
185 |                                 }
186 |                                 console.error('[Vue App] Backend update failed, rolled back local state');
187 |                             } else {
188 |                                 console.log('[Vue App] Backend update successful');
189 |                             }
190 |                         } catch (error) {
191 |                             // 如果发生错误,回滚本地状态
192 |                             const toolIndex = availableTools.value.findIndex(t => t.category === category && t.name === name);
193 |                             if (toolIndex !== -1) {
194 |                                 availableTools.value[toolIndex].enabled = !enabled;
195 |                                 availableTools.value = [...availableTools.value];
196 |                             }
197 |                             console.error('[Vue App] Failed to update tool status:', error);
198 |                         }
199 |                     };
200 |                     
201 |                     const selectAllTools = async () => {
202 |                         try {
203 |                             // 直接更新本地状态,然后保存
204 |                             availableTools.value.forEach(tool => tool.enabled = true);
205 |                             await saveChanges();
206 |                         } catch (error) {
207 |                             console.error('[Vue App] Failed to select all tools:', error);
208 |                         }
209 |                     };
210 |                     
211 |                     const deselectAllTools = async () => {
212 |                         try {
213 |                             // 直接更新本地状态,然后保存
214 |                             availableTools.value.forEach(tool => tool.enabled = false);
215 |                             await saveChanges();
216 |                         } catch (error) {
217 |                             console.error('[Vue App] Failed to deselect all tools:', error);
218 |                         }
219 |                     };
220 |                     
221 |                                         const saveChanges = async () => {
222 |                         try {
223 |                             // 创建普通对象,避免Vue3响应式对象克隆错误
224 |                             const updates = availableTools.value.map(tool => ({
225 |                                 category: String(tool.category),
226 |                                 name: String(tool.name),
227 |                                 enabled: Boolean(tool.enabled)
228 |                             }));
229 |                             
230 |                             console.log('[Vue App] Sending updates:', updates.length, 'tools');
231 |                             
232 |                             const result = await Editor.Message.request('cocos-mcp-server', 'updateToolStatusBatch', updates);
233 |                             
234 |                             if (result && result.success) {
235 |                                 console.log('[Vue App] Tool changes saved successfully');
236 |                             }
237 |                         } catch (error) {
238 |                             console.error('[Vue App] Failed to save tool changes:', error);
239 |                         }
240 |                     };
241 |                     
242 | 
243 |                     
244 |                     const toggleCategoryTools = async (category: string, enabled: boolean) => {
245 |                         try {
246 |                             // 直接更新本地状态,然后保存
247 |                             availableTools.value.forEach(tool => {
248 |                                 if (tool.category === category) {
249 |                                     tool.enabled = enabled;
250 |                                 }
251 |                             });
252 |                             await saveChanges();
253 |                         } catch (error) {
254 |                             console.error('[Vue App] Failed to toggle category tools:', error);
255 |                         }
256 |                     };
257 |                     
258 |                     const getToolsByCategory = (category: string) => {
259 |                         return availableTools.value.filter(tool => tool.category === category);
260 |                     };
261 |                     
262 |                     const getCategoryDisplayName = (category: string): string => {
263 |                         const categoryNames: { [key: string]: string } = {
264 |                             'scene': '场景工具',
265 |                             'node': '节点工具',
266 |                             'component': '组件工具',
267 |                             'prefab': '预制体工具',
268 |                             'project': '项目工具',
269 |                             'debug': '调试工具',
270 |                             'preferences': '偏好设置工具',
271 |                             'server': '服务器工具',
272 |                             'broadcast': '广播工具',
273 |                             'sceneAdvanced': '高级场景工具',
274 |                             'sceneView': '场景视图工具',
275 |                             'referenceImage': '参考图片工具',
276 |                             'assetAdvanced': '高级资源工具',
277 |                             'validation': '验证工具'
278 |                         };
279 |                         return categoryNames[category] || category;
280 |                     };
281 |                     
282 | 
283 |                     
284 | 
285 |                     
286 |                     // 监听设置变化
287 |                     watch(settings, () => {
288 |                         settingsChanged.value = true;
289 |                     }, { deep: true });
290 |                     
291 | 
292 |                     
293 |                     // 组件挂载时加载数据
294 |                     onMounted(async () => {
295 |                         // 加载工具管理器状态
296 |                         await loadToolManagerState();
297 |                         
298 |                         // 从服务器状态获取设置信息
299 |                         try {
300 |                             const serverStatus = await Editor.Message.request('cocos-mcp-server', 'get-server-status');
301 |                             if (serverStatus && serverStatus.settings) {
302 |                                 settings.value = {
303 |                                     port: serverStatus.settings.port || 3000,
304 |                                     autoStart: serverStatus.settings.autoStart || false,
305 |                                     debugLog: serverStatus.settings.enableDebugLog || false,
306 |                                     maxConnections: serverStatus.settings.maxConnections || 10
307 |                                 };
308 |                                 console.log('[Vue App] Server settings loaded from status:', serverStatus.settings);
309 |                             } else if (serverStatus && serverStatus.port) {
310 |                                 // 兼容旧版本,只获取端口信息
311 |                                 settings.value.port = serverStatus.port;
312 |                                 console.log('[Vue App] Port loaded from server status:', serverStatus.port);
313 |                             }
314 |                         } catch (error) {
315 |                             console.error('[Vue App] Failed to get server status:', error);
316 |                             console.log('[Vue App] Using default server settings');
317 |                         }
318 |                         
319 |                         // 定期更新服务器状态
320 |                         setInterval(async () => {
321 |                             try {
322 |                                 const result = await Editor.Message.request('cocos-mcp-server', 'get-server-status');
323 |                                 if (result) {
324 |                                     serverRunning.value = result.running;
325 |                                     serverStatus.value = result.running ? '运行中' : '已停止';
326 |                                     connectedClients.value = result.clients || 0;
327 |                                     httpUrl.value = result.running ? `http://localhost:${result.port}` : '';
328 |                                     isProcessing.value = false;
329 |                                 }
330 |                             } catch (error) {
331 |                                 console.error('[Vue App] Failed to get server status:', error);
332 |                             }
333 |                         }, 2000);
334 |                     });
335 |                     
336 |                     return {
337 |                         // 数据
338 |                         activeTab,
339 |                         serverRunning,
340 |                         serverStatus,
341 |                         connectedClients,
342 |                         httpUrl,
343 |                         isProcessing,
344 |                         settings,
345 |                         availableTools,
346 |                         toolCategories,
347 |                         settingsChanged,
348 |                         
349 |                         // 计算属性
350 |                         statusClass,
351 |                         totalTools,
352 |                         enabledTools,
353 |                         disabledTools,
354 |                         
355 |                         // 方法
356 |                         switchTab,
357 |                         toggleServer,
358 |                         saveSettings,
359 |                         copyUrl,
360 |                         loadToolManagerState,
361 |                         updateToolStatus,
362 |                         selectAllTools,
363 |                         deselectAllTools,
364 |                         saveChanges,
365 |                         toggleCategoryTools,
366 |                         getToolsByCategory,
367 |                         getCategoryDisplayName
368 |                     };
369 |                 },
370 |                 template: readFileSync(join(__dirname, '../../../static/template/vue/mcp-server-app.html'), 'utf-8'),
371 |             }));
372 |             
373 |             app.mount(this.$.app);
374 |             panelDataMap.set(this, app);
375 |             
376 |             console.log('[MCP Panel] Vue3 app mounted successfully');
377 |         }
378 |     },
379 |     beforeClose() { },
380 |     close() {
381 |         const app = panelDataMap.get(this);
382 |         if (app) {
383 |             app.unmount();
384 |         }
385 |     },
386 | });
```

--------------------------------------------------------------------------------
/source/tools/scene-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ToolDefinition, ToolResponse, ToolExecutor, SceneInfo } from '../types';
  2 | 
  3 | export class SceneTools implements ToolExecutor {
  4 |     getTools(): ToolDefinition[] {
  5 |         return [
  6 |             {
  7 |                 name: 'get_current_scene',
  8 |                 description: 'Get current scene information',
  9 |                 inputSchema: {
 10 |                     type: 'object',
 11 |                     properties: {}
 12 |                 }
 13 |             },
 14 |             {
 15 |                 name: 'get_scene_list',
 16 |                 description: 'Get all scenes in the project',
 17 |                 inputSchema: {
 18 |                     type: 'object',
 19 |                     properties: {}
 20 |                 }
 21 |             },
 22 |             {
 23 |                 name: 'open_scene',
 24 |                 description: 'Open a scene by path',
 25 |                 inputSchema: {
 26 |                     type: 'object',
 27 |                     properties: {
 28 |                         scenePath: {
 29 |                             type: 'string',
 30 |                             description: 'The scene file path'
 31 |                         }
 32 |                     },
 33 |                     required: ['scenePath']
 34 |                 }
 35 |             },
 36 |             {
 37 |                 name: 'save_scene',
 38 |                 description: 'Save current scene',
 39 |                 inputSchema: {
 40 |                     type: 'object',
 41 |                     properties: {}
 42 |                 }
 43 |             },
 44 |             {
 45 |                 name: 'create_scene',
 46 |                 description: 'Create a new scene asset',
 47 |                 inputSchema: {
 48 |                     type: 'object',
 49 |                     properties: {
 50 |                         sceneName: {
 51 |                             type: 'string',
 52 |                             description: 'Name of the new scene'
 53 |                         },
 54 |                         savePath: {
 55 |                             type: 'string',
 56 |                             description: 'Path to save the scene (e.g., db://assets/scenes/NewScene.scene)'
 57 |                         }
 58 |                     },
 59 |                     required: ['sceneName', 'savePath']
 60 |                 }
 61 |             },
 62 |             {
 63 |                 name: 'save_scene_as',
 64 |                 description: 'Save scene as new file',
 65 |                 inputSchema: {
 66 |                     type: 'object',
 67 |                     properties: {
 68 |                         path: {
 69 |                             type: 'string',
 70 |                             description: 'Path to save the scene'
 71 |                         }
 72 |                     },
 73 |                     required: ['path']
 74 |                 }
 75 |             },
 76 |             {
 77 |                 name: 'close_scene',
 78 |                 description: 'Close current scene',
 79 |                 inputSchema: {
 80 |                     type: 'object',
 81 |                     properties: {}
 82 |                 }
 83 |             },
 84 |             {
 85 |                 name: 'get_scene_hierarchy',
 86 |                 description: 'Get the complete hierarchy of current scene',
 87 |                 inputSchema: {
 88 |                     type: 'object',
 89 |                     properties: {
 90 |                         includeComponents: {
 91 |                             type: 'boolean',
 92 |                             description: 'Include component information',
 93 |                             default: false
 94 |                         }
 95 |                     }
 96 |                 }
 97 |             }
 98 |         ];
 99 |     }
100 | 
101 |     async execute(toolName: string, args: any): Promise<ToolResponse> {
102 |         switch (toolName) {
103 |             case 'get_current_scene':
104 |                 return await this.getCurrentScene();
105 |             case 'get_scene_list':
106 |                 return await this.getSceneList();
107 |             case 'open_scene':
108 |                 return await this.openScene(args.scenePath);
109 |             case 'save_scene':
110 |                 return await this.saveScene();
111 |             case 'create_scene':
112 |                 return await this.createScene(args.sceneName, args.savePath);
113 |             case 'save_scene_as':
114 |                 return await this.saveSceneAs(args.path);
115 |             case 'close_scene':
116 |                 return await this.closeScene();
117 |             case 'get_scene_hierarchy':
118 |                 return await this.getSceneHierarchy(args.includeComponents);
119 |             default:
120 |                 throw new Error(`Unknown tool: ${toolName}`);
121 |         }
122 |     }
123 | 
124 |     private async getCurrentScene(): Promise<ToolResponse> {
125 |         return new Promise((resolve) => {
126 |             // 直接使用 query-node-tree 来获取场景信息(这个方法已经验证可用)
127 |             Editor.Message.request('scene', 'query-node-tree').then((tree: any) => {
128 |                 if (tree && tree.uuid) {
129 |                     resolve({
130 |                         success: true,
131 |                         data: {
132 |                             name: tree.name || 'Current Scene',
133 |                             uuid: tree.uuid,
134 |                             type: tree.type || 'cc.Scene',
135 |                             active: tree.active !== undefined ? tree.active : true,
136 |                             nodeCount: tree.children ? tree.children.length : 0
137 |                         }
138 |                     });
139 |                 } else {
140 |                     resolve({ success: false, error: 'No scene data available' });
141 |                 }
142 |             }).catch((err: Error) => {
143 |                 // 备用方案:使用场景脚本
144 |                 const options = {
145 |                     name: 'cocos-mcp-server',
146 |                     method: 'getCurrentSceneInfo',
147 |                     args: []
148 |                 };
149 |                 
150 |                 Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
151 |                     resolve(result);
152 |                 }).catch((err2: Error) => {
153 |                     resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
154 |                 });
155 |             });
156 |         });
157 |     }
158 | 
159 |     private async getSceneList(): Promise<ToolResponse> {
160 |         return new Promise((resolve) => {
161 |             // Note: query-assets API corrected with proper parameters
162 |             Editor.Message.request('asset-db', 'query-assets', {
163 |                 pattern: 'db://assets/**/*.scene'
164 |             }).then((results: any[]) => {
165 |                 const scenes: SceneInfo[] = results.map(asset => ({
166 |                     name: asset.name,
167 |                     path: asset.url,
168 |                     uuid: asset.uuid
169 |                 }));
170 |                 resolve({ success: true, data: scenes });
171 |             }).catch((err: Error) => {
172 |                 resolve({ success: false, error: err.message });
173 |             });
174 |         });
175 |     }
176 | 
177 |     private async openScene(scenePath: string): Promise<ToolResponse> {
178 |         return new Promise((resolve) => {
179 |             // 首先获取场景的UUID
180 |             Editor.Message.request('asset-db', 'query-uuid', scenePath).then((uuid: string | null) => {
181 |                 if (!uuid) {
182 |                     throw new Error('Scene not found');
183 |                 }
184 |                 
185 |                 // 使用正确的 scene API 打开场景 (需要UUID)
186 |                 return Editor.Message.request('scene', 'open-scene', uuid);
187 |             }).then(() => {
188 |                 resolve({ success: true, message: `Scene opened: ${scenePath}` });
189 |             }).catch((err: Error) => {
190 |                 resolve({ success: false, error: err.message });
191 |             });
192 |         });
193 |     }
194 | 
195 |     private async saveScene(): Promise<ToolResponse> {
196 |         return new Promise((resolve) => {
197 |             Editor.Message.request('scene', 'save-scene').then(() => {
198 |                 resolve({ success: true, message: 'Scene saved successfully' });
199 |             }).catch((err: Error) => {
200 |                 resolve({ success: false, error: err.message });
201 |             });
202 |         });
203 |     }
204 | 
205 |     private async createScene(sceneName: string, savePath: string): Promise<ToolResponse> {
206 |         return new Promise((resolve) => {
207 |             // 确保路径以.scene结尾
208 |             const fullPath = savePath.endsWith('.scene') ? savePath : `${savePath}/${sceneName}.scene`;
209 |             
210 |             // 使用正确的Cocos Creator 3.8场景格式
211 |             const sceneContent = JSON.stringify([
212 |                 {
213 |                     "__type__": "cc.SceneAsset",
214 |                     "_name": sceneName,
215 |                     "_objFlags": 0,
216 |                     "__editorExtras__": {},
217 |                     "_native": "",
218 |                     "scene": {
219 |                         "__id__": 1
220 |                     }
221 |                 },
222 |                 {
223 |                     "__type__": "cc.Scene",
224 |                     "_name": sceneName,
225 |                     "_objFlags": 0,
226 |                     "__editorExtras__": {},
227 |                     "_parent": null,
228 |                     "_children": [],
229 |                     "_active": true,
230 |                     "_components": [],
231 |                     "_prefab": null,
232 |                     "_lpos": {
233 |                         "__type__": "cc.Vec3",
234 |                         "x": 0,
235 |                         "y": 0,
236 |                         "z": 0
237 |                     },
238 |                     "_lrot": {
239 |                         "__type__": "cc.Quat",
240 |                         "x": 0,
241 |                         "y": 0,
242 |                         "z": 0,
243 |                         "w": 1
244 |                     },
245 |                     "_lscale": {
246 |                         "__type__": "cc.Vec3",
247 |                         "x": 1,
248 |                         "y": 1,
249 |                         "z": 1
250 |                     },
251 |                     "_mobility": 0,
252 |                     "_layer": 1073741824,
253 |                     "_euler": {
254 |                         "__type__": "cc.Vec3",
255 |                         "x": 0,
256 |                         "y": 0,
257 |                         "z": 0
258 |                     },
259 |                     "autoReleaseAssets": false,
260 |                     "_globals": {
261 |                         "__id__": 2
262 |                     },
263 |                     "_id": "scene"
264 |                 },
265 |                 {
266 |                     "__type__": "cc.SceneGlobals",
267 |                     "ambient": {
268 |                         "__id__": 3
269 |                     },
270 |                     "skybox": {
271 |                         "__id__": 4
272 |                     },
273 |                     "fog": {
274 |                         "__id__": 5
275 |                     },
276 |                     "octree": {
277 |                         "__id__": 6
278 |                     }
279 |                 },
280 |                 {
281 |                     "__type__": "cc.AmbientInfo",
282 |                     "_skyColorHDR": {
283 |                         "__type__": "cc.Vec4",
284 |                         "x": 0.2,
285 |                         "y": 0.5,
286 |                         "z": 0.8,
287 |                         "w": 0.520833
288 |                     },
289 |                     "_skyColor": {
290 |                         "__type__": "cc.Vec4",
291 |                         "x": 0.2,
292 |                         "y": 0.5,
293 |                         "z": 0.8,
294 |                         "w": 0.520833
295 |                     },
296 |                     "_skyIllumHDR": 20000,
297 |                     "_skyIllum": 20000,
298 |                     "_groundAlbedoHDR": {
299 |                         "__type__": "cc.Vec4",
300 |                         "x": 0.2,
301 |                         "y": 0.2,
302 |                         "z": 0.2,
303 |                         "w": 1
304 |                     },
305 |                     "_groundAlbedo": {
306 |                         "__type__": "cc.Vec4",
307 |                         "x": 0.2,
308 |                         "y": 0.2,
309 |                         "z": 0.2,
310 |                         "w": 1
311 |                     }
312 |                 },
313 |                 {
314 |                     "__type__": "cc.SkyboxInfo",
315 |                     "_envLightingType": 0,
316 |                     "_envmapHDR": null,
317 |                     "_envmap": null,
318 |                     "_envmapLodCount": 0,
319 |                     "_diffuseMapHDR": null,
320 |                     "_diffuseMap": null,
321 |                     "_enabled": false,
322 |                     "_useHDR": true,
323 |                     "_editableMaterial": null,
324 |                     "_reflectionHDR": null,
325 |                     "_reflectionMap": null,
326 |                     "_rotationAngle": 0
327 |                 },
328 |                 {
329 |                     "__type__": "cc.FogInfo",
330 |                     "_type": 0,
331 |                     "_fogColor": {
332 |                         "__type__": "cc.Color",
333 |                         "r": 200,
334 |                         "g": 200,
335 |                         "b": 200,
336 |                         "a": 255
337 |                     },
338 |                     "_enabled": false,
339 |                     "_fogDensity": 0.3,
340 |                     "_fogStart": 0.5,
341 |                     "_fogEnd": 300,
342 |                     "_fogAtten": 5,
343 |                     "_fogTop": 1.5,
344 |                     "_fogRange": 1.2,
345 |                     "_accurate": false
346 |                 },
347 |                 {
348 |                     "__type__": "cc.OctreeInfo",
349 |                     "_enabled": false,
350 |                     "_minPos": {
351 |                         "__type__": "cc.Vec3",
352 |                         "x": -1024,
353 |                         "y": -1024,
354 |                         "z": -1024
355 |                     },
356 |                     "_maxPos": {
357 |                         "__type__": "cc.Vec3",
358 |                         "x": 1024,
359 |                         "y": 1024,
360 |                         "z": 1024
361 |                     },
362 |                     "_depth": 8
363 |                 }
364 |             ], null, 2);
365 |             
366 |             Editor.Message.request('asset-db', 'create-asset', fullPath, sceneContent).then((result: any) => {
367 |                 // Verify scene creation by checking if it exists
368 |                 this.getSceneList().then((sceneList) => {
369 |                     const createdScene = sceneList.data?.find((scene: any) => scene.uuid === result.uuid);
370 |                     resolve({
371 |                         success: true,
372 |                         data: {
373 |                             uuid: result.uuid,
374 |                             url: result.url,
375 |                             name: sceneName,
376 |                             message: `Scene '${sceneName}' created successfully`,
377 |                             sceneVerified: !!createdScene
378 |                         },
379 |                         verificationData: createdScene
380 |                     });
381 |                 }).catch(() => {
382 |                     resolve({
383 |                         success: true,
384 |                         data: {
385 |                             uuid: result.uuid,
386 |                             url: result.url,
387 |                             name: sceneName,
388 |                             message: `Scene '${sceneName}' created successfully (verification failed)`
389 |                         }
390 |                     });
391 |                 });
392 |             }).catch((err: Error) => {
393 |                 resolve({ success: false, error: err.message });
394 |             });
395 |         });
396 |     }
397 | 
398 |     private async getSceneHierarchy(includeComponents: boolean = false): Promise<ToolResponse> {
399 |         return new Promise((resolve) => {
400 |             // 优先尝试使用 Editor API 查询场景节点树
401 |             Editor.Message.request('scene', 'query-node-tree').then((tree: any) => {
402 |                 if (tree) {
403 |                     const hierarchy = this.buildHierarchy(tree, includeComponents);
404 |                     resolve({
405 |                         success: true,
406 |                         data: hierarchy
407 |                     });
408 |                 } else {
409 |                     resolve({ success: false, error: 'No scene hierarchy available' });
410 |                 }
411 |             }).catch((err: Error) => {
412 |                 // 备用方案:使用场景脚本
413 |                 const options = {
414 |                     name: 'cocos-mcp-server',
415 |                     method: 'getSceneHierarchy',
416 |                     args: [includeComponents]
417 |                 };
418 |                 
419 |                 Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
420 |                     resolve(result);
421 |                 }).catch((err2: Error) => {
422 |                     resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
423 |                 });
424 |             });
425 |         });
426 |     }
427 | 
428 |     private buildHierarchy(node: any, includeComponents: boolean): any {
429 |         const nodeInfo: any = {
430 |             uuid: node.uuid,
431 |             name: node.name,
432 |             type: node.type,
433 |             active: node.active,
434 |             children: []
435 |         };
436 | 
437 |         if (includeComponents && node.__comps__) {
438 |             nodeInfo.components = node.__comps__.map((comp: any) => ({
439 |                 type: comp.__type__ || 'Unknown',
440 |                 enabled: comp.enabled !== undefined ? comp.enabled : true
441 |             }));
442 |         }
443 | 
444 |         if (node.children) {
445 |             nodeInfo.children = node.children.map((child: any) => 
446 |                 this.buildHierarchy(child, includeComponents)
447 |             );
448 |         }
449 | 
450 |         return nodeInfo;
451 |     }
452 | 
453 |     private async saveSceneAs(path: string): Promise<ToolResponse> {
454 |         return new Promise((resolve) => {
455 |             // save-as-scene API 不接受路径参数,会弹出对话框让用户选择
456 |             (Editor.Message.request as any)('scene', 'save-as-scene').then(() => {
457 |                 resolve({
458 |                     success: true,
459 |                     data: {
460 |                         path: path,
461 |                         message: `Scene save-as dialog opened`
462 |                     }
463 |                 });
464 |             }).catch((err: Error) => {
465 |                 resolve({ success: false, error: err.message });
466 |             });
467 |         });
468 |     }
469 | 
470 |     private async closeScene(): Promise<ToolResponse> {
471 |         return new Promise((resolve) => {
472 |             Editor.Message.request('scene', 'close-scene').then(() => {
473 |                 resolve({
474 |                     success: true,
475 |                     message: 'Scene closed successfully'
476 |                 });
477 |             }).catch((err: Error) => {
478 |                 resolve({ success: false, error: err.message });
479 |             });
480 |         });
481 |     }
482 | }
```

--------------------------------------------------------------------------------
/source/tools/scene-view-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ToolDefinition, ToolResponse, ToolExecutor } from '../types';
  2 | 
  3 | export class SceneViewTools implements ToolExecutor {
  4 |     getTools(): ToolDefinition[] {
  5 |         return [
  6 |             {
  7 |                 name: 'change_gizmo_tool',
  8 |                 description: 'Change Gizmo tool',
  9 |                 inputSchema: {
 10 |                     type: 'object',
 11 |                     properties: {
 12 |                         name: {
 13 |                             type: 'string',
 14 |                             description: 'Tool name',
 15 |                             enum: ['position', 'rotation', 'scale', 'rect']
 16 |                         }
 17 |                     },
 18 |                     required: ['name']
 19 |                 }
 20 |             },
 21 |             {
 22 |                 name: 'query_gizmo_tool_name',
 23 |                 description: 'Get current Gizmo tool name',
 24 |                 inputSchema: {
 25 |                     type: 'object',
 26 |                     properties: {}
 27 |                 }
 28 |             },
 29 |             {
 30 |                 name: 'change_gizmo_pivot',
 31 |                 description: 'Change transform pivot point',
 32 |                 inputSchema: {
 33 |                     type: 'object',
 34 |                     properties: {
 35 |                         name: {
 36 |                             type: 'string',
 37 |                             description: 'Pivot point',
 38 |                             enum: ['pivot', 'center']
 39 |                         }
 40 |                     },
 41 |                     required: ['name']
 42 |                 }
 43 |             },
 44 |             {
 45 |                 name: 'query_gizmo_pivot',
 46 |                 description: 'Get current Gizmo pivot point',
 47 |                 inputSchema: {
 48 |                     type: 'object',
 49 |                     properties: {}
 50 |                 }
 51 |             },
 52 |             {
 53 |                 name: 'query_gizmo_view_mode',
 54 |                 description: 'Query view mode (view/select)',
 55 |                 inputSchema: {
 56 |                     type: 'object',
 57 |                     properties: {}
 58 |                 }
 59 |             },
 60 |             {
 61 |                 name: 'change_gizmo_coordinate',
 62 |                 description: 'Change coordinate system',
 63 |                 inputSchema: {
 64 |                     type: 'object',
 65 |                     properties: {
 66 |                         type: {
 67 |                             type: 'string',
 68 |                             description: 'Coordinate system',
 69 |                             enum: ['local', 'global']
 70 |                         }
 71 |                     },
 72 |                     required: ['type']
 73 |                 }
 74 |             },
 75 |             {
 76 |                 name: 'query_gizmo_coordinate',
 77 |                 description: 'Get current coordinate system',
 78 |                 inputSchema: {
 79 |                     type: 'object',
 80 |                     properties: {}
 81 |                 }
 82 |             },
 83 |             {
 84 |                 name: 'change_view_mode_2d_3d',
 85 |                 description: 'Change 2D/3D view mode',
 86 |                 inputSchema: {
 87 |                     type: 'object',
 88 |                     properties: {
 89 |                         is2D: {
 90 |                             type: 'boolean',
 91 |                             description: '2D/3D view mode (true for 2D, false for 3D)'
 92 |                         }
 93 |                     },
 94 |                     required: ['is2D']
 95 |                 }
 96 |             },
 97 |             {
 98 |                 name: 'query_view_mode_2d_3d',
 99 |                 description: 'Get current view mode',
100 |                 inputSchema: {
101 |                     type: 'object',
102 |                     properties: {}
103 |                 }
104 |             },
105 |             {
106 |                 name: 'set_grid_visible',
107 |                 description: 'Show/hide grid',
108 |                 inputSchema: {
109 |                     type: 'object',
110 |                     properties: {
111 |                         visible: {
112 |                             type: 'boolean',
113 |                             description: 'Grid visibility'
114 |                         }
115 |                     },
116 |                     required: ['visible']
117 |                 }
118 |             },
119 |             {
120 |                 name: 'query_grid_visible',
121 |                 description: 'Query grid visibility status',
122 |                 inputSchema: {
123 |                     type: 'object',
124 |                     properties: {}
125 |                 }
126 |             },
127 |             {
128 |                 name: 'set_icon_gizmo_3d',
129 |                 description: 'Set IconGizmo to 3D or 2D mode',
130 |                 inputSchema: {
131 |                     type: 'object',
132 |                     properties: {
133 |                         is3D: {
134 |                             type: 'boolean',
135 |                             description: '3D/2D IconGizmo (true for 3D, false for 2D)'
136 |                         }
137 |                     },
138 |                     required: ['is3D']
139 |                 }
140 |             },
141 |             {
142 |                 name: 'query_icon_gizmo_3d',
143 |                 description: 'Query IconGizmo mode',
144 |                 inputSchema: {
145 |                     type: 'object',
146 |                     properties: {}
147 |                 }
148 |             },
149 |             {
150 |                 name: 'set_icon_gizmo_size',
151 |                 description: 'Set IconGizmo size',
152 |                 inputSchema: {
153 |                     type: 'object',
154 |                     properties: {
155 |                         size: {
156 |                             type: 'number',
157 |                             description: 'IconGizmo size',
158 |                             minimum: 10,
159 |                             maximum: 100
160 |                         }
161 |                     },
162 |                     required: ['size']
163 |                 }
164 |             },
165 |             {
166 |                 name: 'query_icon_gizmo_size',
167 |                 description: 'Query IconGizmo size',
168 |                 inputSchema: {
169 |                     type: 'object',
170 |                     properties: {}
171 |                 }
172 |             },
173 |             {
174 |                 name: 'focus_camera_on_nodes',
175 |                 description: 'Focus scene camera on nodes',
176 |                 inputSchema: {
177 |                     type: 'object',
178 |                     properties: {
179 |                         uuids: {
180 |                             oneOf: [
181 |                                 { type: 'array', items: { type: 'string' } },
182 |                                 { type: 'null' }
183 |                             ],
184 |                             description: 'Node UUIDs to focus on (null for all)'
185 |                         }
186 |                     },
187 |                     required: ['uuids']
188 |                 }
189 |             },
190 |             {
191 |                 name: 'align_camera_with_view',
192 |                 description: 'Apply scene camera position and angle to selected node',
193 |                 inputSchema: {
194 |                     type: 'object',
195 |                     properties: {}
196 |                 }
197 |             },
198 |             {
199 |                 name: 'align_view_with_node',
200 |                 description: 'Apply selected node position and angle to current view',
201 |                 inputSchema: {
202 |                     type: 'object',
203 |                     properties: {}
204 |                 }
205 |             },
206 |             {
207 |                 name: 'get_scene_view_status',
208 |                 description: 'Get comprehensive scene view status',
209 |                 inputSchema: {
210 |                     type: 'object',
211 |                     properties: {}
212 |                 }
213 |             },
214 |             {
215 |                 name: 'reset_scene_view',
216 |                 description: 'Reset scene view to default settings',
217 |                 inputSchema: {
218 |                     type: 'object',
219 |                     properties: {}
220 |                 }
221 |             }
222 |         ];
223 |     }
224 | 
225 |     async execute(toolName: string, args: any): Promise<ToolResponse> {
226 |         switch (toolName) {
227 |             case 'change_gizmo_tool':
228 |                 return await this.changeGizmoTool(args.name);
229 |             case 'query_gizmo_tool_name':
230 |                 return await this.queryGizmoToolName();
231 |             case 'change_gizmo_pivot':
232 |                 return await this.changeGizmoPivot(args.name);
233 |             case 'query_gizmo_pivot':
234 |                 return await this.queryGizmoPivot();
235 |             case 'query_gizmo_view_mode':
236 |                 return await this.queryGizmoViewMode();
237 |             case 'change_gizmo_coordinate':
238 |                 return await this.changeGizmoCoordinate(args.type);
239 |             case 'query_gizmo_coordinate':
240 |                 return await this.queryGizmoCoordinate();
241 |             case 'change_view_mode_2d_3d':
242 |                 return await this.changeViewMode2D3D(args.is2D);
243 |             case 'query_view_mode_2d_3d':
244 |                 return await this.queryViewMode2D3D();
245 |             case 'set_grid_visible':
246 |                 return await this.setGridVisible(args.visible);
247 |             case 'query_grid_visible':
248 |                 return await this.queryGridVisible();
249 |             case 'set_icon_gizmo_3d':
250 |                 return await this.setIconGizmo3D(args.is3D);
251 |             case 'query_icon_gizmo_3d':
252 |                 return await this.queryIconGizmo3D();
253 |             case 'set_icon_gizmo_size':
254 |                 return await this.setIconGizmoSize(args.size);
255 |             case 'query_icon_gizmo_size':
256 |                 return await this.queryIconGizmoSize();
257 |             case 'focus_camera_on_nodes':
258 |                 return await this.focusCameraOnNodes(args.uuids);
259 |             case 'align_camera_with_view':
260 |                 return await this.alignCameraWithView();
261 |             case 'align_view_with_node':
262 |                 return await this.alignViewWithNode();
263 |             case 'get_scene_view_status':
264 |                 return await this.getSceneViewStatus();
265 |             case 'reset_scene_view':
266 |                 return await this.resetSceneView();
267 |             default:
268 |                 throw new Error(`Unknown tool: ${toolName}`);
269 |         }
270 |     }
271 | 
272 |     private async changeGizmoTool(name: string): Promise<ToolResponse> {
273 |         return new Promise((resolve) => {
274 |             Editor.Message.request('scene', 'change-gizmo-tool', name).then(() => {
275 |                 resolve({
276 |                     success: true,
277 |                     message: `Gizmo tool changed to '${name}'`
278 |                 });
279 |             }).catch((err: Error) => {
280 |                 resolve({ success: false, error: err.message });
281 |             });
282 |         });
283 |     }
284 | 
285 |     private async queryGizmoToolName(): Promise<ToolResponse> {
286 |         return new Promise((resolve) => {
287 |             Editor.Message.request('scene', 'query-gizmo-tool-name').then((toolName: string) => {
288 |                 resolve({
289 |                     success: true,
290 |                     data: {
291 |                         currentTool: toolName,
292 |                         message: `Current Gizmo tool: ${toolName}`
293 |                     }
294 |                 });
295 |             }).catch((err: Error) => {
296 |                 resolve({ success: false, error: err.message });
297 |             });
298 |         });
299 |     }
300 | 
301 |     private async changeGizmoPivot(name: string): Promise<ToolResponse> {
302 |         return new Promise((resolve) => {
303 |             Editor.Message.request('scene', 'change-gizmo-pivot', name).then(() => {
304 |                 resolve({
305 |                     success: true,
306 |                     message: `Gizmo pivot changed to '${name}'`
307 |                 });
308 |             }).catch((err: Error) => {
309 |                 resolve({ success: false, error: err.message });
310 |             });
311 |         });
312 |     }
313 | 
314 |     private async queryGizmoPivot(): Promise<ToolResponse> {
315 |         return new Promise((resolve) => {
316 |             Editor.Message.request('scene', 'query-gizmo-pivot').then((pivotName: string) => {
317 |                 resolve({
318 |                     success: true,
319 |                     data: {
320 |                         currentPivot: pivotName,
321 |                         message: `Current Gizmo pivot: ${pivotName}`
322 |                     }
323 |                 });
324 |             }).catch((err: Error) => {
325 |                 resolve({ success: false, error: err.message });
326 |             });
327 |         });
328 |     }
329 | 
330 |     private async queryGizmoViewMode(): Promise<ToolResponse> {
331 |         return new Promise((resolve) => {
332 |             Editor.Message.request('scene', 'query-gizmo-view-mode').then((viewMode: string) => {
333 |                 resolve({
334 |                     success: true,
335 |                     data: {
336 |                         viewMode: viewMode,
337 |                         message: `Current view mode: ${viewMode}`
338 |                     }
339 |                 });
340 |             }).catch((err: Error) => {
341 |                 resolve({ success: false, error: err.message });
342 |             });
343 |         });
344 |     }
345 | 
346 |     private async changeGizmoCoordinate(type: string): Promise<ToolResponse> {
347 |         return new Promise((resolve) => {
348 |             Editor.Message.request('scene', 'change-gizmo-coordinate', type).then(() => {
349 |                 resolve({
350 |                     success: true,
351 |                     message: `Coordinate system changed to '${type}'`
352 |                 });
353 |             }).catch((err: Error) => {
354 |                 resolve({ success: false, error: err.message });
355 |             });
356 |         });
357 |     }
358 | 
359 |     private async queryGizmoCoordinate(): Promise<ToolResponse> {
360 |         return new Promise((resolve) => {
361 |             Editor.Message.request('scene', 'query-gizmo-coordinate').then((coordinate: string) => {
362 |                 resolve({
363 |                     success: true,
364 |                     data: {
365 |                         coordinate: coordinate,
366 |                         message: `Current coordinate system: ${coordinate}`
367 |                     }
368 |                 });
369 |             }).catch((err: Error) => {
370 |                 resolve({ success: false, error: err.message });
371 |             });
372 |         });
373 |     }
374 | 
375 |     private async changeViewMode2D3D(is2D: boolean): Promise<ToolResponse> {
376 |         return new Promise((resolve) => {
377 |             Editor.Message.request('scene', 'change-is2D', is2D).then(() => {
378 |                 resolve({
379 |                     success: true,
380 |                     message: `View mode changed to ${is2D ? '2D' : '3D'}`
381 |                 });
382 |             }).catch((err: Error) => {
383 |                 resolve({ success: false, error: err.message });
384 |             });
385 |         });
386 |     }
387 | 
388 |     private async queryViewMode2D3D(): Promise<ToolResponse> {
389 |         return new Promise((resolve) => {
390 |             Editor.Message.request('scene', 'query-is2D').then((is2D: boolean) => {
391 |                 resolve({
392 |                     success: true,
393 |                     data: {
394 |                         is2D: is2D,
395 |                         viewMode: is2D ? '2D' : '3D',
396 |                         message: `Current view mode: ${is2D ? '2D' : '3D'}`
397 |                     }
398 |                 });
399 |             }).catch((err: Error) => {
400 |                 resolve({ success: false, error: err.message });
401 |             });
402 |         });
403 |     }
404 | 
405 |     private async setGridVisible(visible: boolean): Promise<ToolResponse> {
406 |         return new Promise((resolve) => {
407 |             Editor.Message.request('scene', 'set-grid-visible', visible).then(() => {
408 |                 resolve({
409 |                     success: true,
410 |                     message: `Grid ${visible ? 'shown' : 'hidden'}`
411 |                 });
412 |             }).catch((err: Error) => {
413 |                 resolve({ success: false, error: err.message });
414 |             });
415 |         });
416 |     }
417 | 
418 |     private async queryGridVisible(): Promise<ToolResponse> {
419 |         return new Promise((resolve) => {
420 |             Editor.Message.request('scene', 'query-is-grid-visible').then((visible: boolean) => {
421 |                 resolve({
422 |                     success: true,
423 |                     data: {
424 |                         visible: visible,
425 |                         message: `Grid is ${visible ? 'visible' : 'hidden'}`
426 |                     }
427 |                 });
428 |             }).catch((err: Error) => {
429 |                 resolve({ success: false, error: err.message });
430 |             });
431 |         });
432 |     }
433 | 
434 |     private async setIconGizmo3D(is3D: boolean): Promise<ToolResponse> {
435 |         return new Promise((resolve) => {
436 |             Editor.Message.request('scene', 'set-icon-gizmo-3d', is3D).then(() => {
437 |                 resolve({
438 |                     success: true,
439 |                     message: `IconGizmo set to ${is3D ? '3D' : '2D'} mode`
440 |                 });
441 |             }).catch((err: Error) => {
442 |                 resolve({ success: false, error: err.message });
443 |             });
444 |         });
445 |     }
446 | 
447 |     private async queryIconGizmo3D(): Promise<ToolResponse> {
448 |         return new Promise((resolve) => {
449 |             Editor.Message.request('scene', 'query-is-icon-gizmo-3d').then((is3D: boolean) => {
450 |                 resolve({
451 |                     success: true,
452 |                     data: {
453 |                         is3D: is3D,
454 |                         mode: is3D ? '3D' : '2D',
455 |                         message: `IconGizmo is in ${is3D ? '3D' : '2D'} mode`
456 |                     }
457 |                 });
458 |             }).catch((err: Error) => {
459 |                 resolve({ success: false, error: err.message });
460 |             });
461 |         });
462 |     }
463 | 
464 |     private async setIconGizmoSize(size: number): Promise<ToolResponse> {
465 |         return new Promise((resolve) => {
466 |             Editor.Message.request('scene', 'set-icon-gizmo-size', size).then(() => {
467 |                 resolve({
468 |                     success: true,
469 |                     message: `IconGizmo size set to ${size}`
470 |                 });
471 |             }).catch((err: Error) => {
472 |                 resolve({ success: false, error: err.message });
473 |             });
474 |         });
475 |     }
476 | 
477 |     private async queryIconGizmoSize(): Promise<ToolResponse> {
478 |         return new Promise((resolve) => {
479 |             Editor.Message.request('scene', 'query-icon-gizmo-size').then((size: number) => {
480 |                 resolve({
481 |                     success: true,
482 |                     data: {
483 |                         size: size,
484 |                         message: `IconGizmo size: ${size}`
485 |                     }
486 |                 });
487 |             }).catch((err: Error) => {
488 |                 resolve({ success: false, error: err.message });
489 |             });
490 |         });
491 |     }
492 | 
493 |     private async focusCameraOnNodes(uuids: string[] | null): Promise<ToolResponse> {
494 |         return new Promise((resolve) => {
495 |             Editor.Message.request('scene', 'focus-camera', uuids || []).then(() => {
496 |                 const message = uuids === null ? 
497 |                     'Camera focused on all nodes' : 
498 |                     `Camera focused on ${uuids.length} node(s)`;
499 |                 resolve({
500 |                     success: true,
501 |                     message: message
502 |                 });
503 |             }).catch((err: Error) => {
504 |                 resolve({ success: false, error: err.message });
505 |             });
506 |         });
507 |     }
508 | 
509 |     private async alignCameraWithView(): Promise<ToolResponse> {
510 |         return new Promise((resolve) => {
511 |             Editor.Message.request('scene', 'align-with-view').then(() => {
512 |                 resolve({
513 |                     success: true,
514 |                     message: 'Scene camera aligned with current view'
515 |                 });
516 |             }).catch((err: Error) => {
517 |                 resolve({ success: false, error: err.message });
518 |             });
519 |         });
520 |     }
521 | 
522 |     private async alignViewWithNode(): Promise<ToolResponse> {
523 |         return new Promise((resolve) => {
524 |             Editor.Message.request('scene', 'align-with-view-node').then(() => {
525 |                 resolve({
526 |                     success: true,
527 |                     message: 'View aligned with selected node'
528 |                 });
529 |             }).catch((err: Error) => {
530 |                 resolve({ success: false, error: err.message });
531 |             });
532 |         });
533 |     }
534 | 
535 |     private async getSceneViewStatus(): Promise<ToolResponse> {
536 |         return new Promise(async (resolve) => {
537 |             try {
538 |                 // Gather all view status information
539 |                 const [
540 |                     gizmoTool,
541 |                     gizmoPivot,
542 |                     gizmoCoordinate,
543 |                     viewMode2D3D,
544 |                     gridVisible,
545 |                     iconGizmo3D,
546 |                     iconGizmoSize
547 |                 ] = await Promise.allSettled([
548 |                     this.queryGizmoToolName(),
549 |                     this.queryGizmoPivot(),
550 |                     this.queryGizmoCoordinate(),
551 |                     this.queryViewMode2D3D(),
552 |                     this.queryGridVisible(),
553 |                     this.queryIconGizmo3D(),
554 |                     this.queryIconGizmoSize()
555 |                 ]);
556 | 
557 |                 const status: any = {
558 |                     timestamp: new Date().toISOString()
559 |                 };
560 | 
561 |                 // Extract data from fulfilled promises
562 |                 if (gizmoTool.status === 'fulfilled' && gizmoTool.value.success) {
563 |                     status.gizmoTool = gizmoTool.value.data.currentTool;
564 |                 }
565 |                 if (gizmoPivot.status === 'fulfilled' && gizmoPivot.value.success) {
566 |                     status.gizmoPivot = gizmoPivot.value.data.currentPivot;
567 |                 }
568 |                 if (gizmoCoordinate.status === 'fulfilled' && gizmoCoordinate.value.success) {
569 |                     status.coordinate = gizmoCoordinate.value.data.coordinate;
570 |                 }
571 |                 if (viewMode2D3D.status === 'fulfilled' && viewMode2D3D.value.success) {
572 |                     status.is2D = viewMode2D3D.value.data.is2D;
573 |                     status.viewMode = viewMode2D3D.value.data.viewMode;
574 |                 }
575 |                 if (gridVisible.status === 'fulfilled' && gridVisible.value.success) {
576 |                     status.gridVisible = gridVisible.value.data.visible;
577 |                 }
578 |                 if (iconGizmo3D.status === 'fulfilled' && iconGizmo3D.value.success) {
579 |                     status.iconGizmo3D = iconGizmo3D.value.data.is3D;
580 |                 }
581 |                 if (iconGizmoSize.status === 'fulfilled' && iconGizmoSize.value.success) {
582 |                     status.iconGizmoSize = iconGizmoSize.value.data.size;
583 |                 }
584 | 
585 |                 resolve({
586 |                     success: true,
587 |                     data: status
588 |                 });
589 | 
590 |             } catch (err: any) {
591 |                 resolve({
592 |                     success: false,
593 |                     error: `Failed to get scene view status: ${err.message}`
594 |                 });
595 |             }
596 |         });
597 |     }
598 | 
599 |     private async resetSceneView(): Promise<ToolResponse> {
600 |         return new Promise(async (resolve) => {
601 |             try {
602 |                 // Reset scene view to default settings
603 |                 const resetActions = [
604 |                     this.changeGizmoTool('position'),
605 |                     this.changeGizmoPivot('pivot'),
606 |                     this.changeGizmoCoordinate('local'),
607 |                     this.changeViewMode2D3D(false), // 3D mode
608 |                     this.setGridVisible(true),
609 |                     this.setIconGizmo3D(true),
610 |                     this.setIconGizmoSize(60)
611 |                 ];
612 | 
613 |                 await Promise.all(resetActions);
614 | 
615 |                 resolve({
616 |                     success: true,
617 |                     message: 'Scene view reset to default settings'
618 |                 });
619 | 
620 |             } catch (err: any) {
621 |                 resolve({
622 |                     success: false,
623 |                     error: `Failed to reset scene view: ${err.message}`
624 |                 });
625 |             }
626 |         });
627 |     }
628 | }
```

--------------------------------------------------------------------------------
/source/panels/tool-manager/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { readFileSync } from 'fs-extra';
  2 | import { join } from 'path';
  3 | 
  4 | module.exports = Editor.Panel.define({
  5 |     listeners: {
  6 |         show() { console.log('Tool Manager panel shown'); },
  7 |         hide() { console.log('Tool Manager panel hidden'); }
  8 |     },
  9 |     template: readFileSync(join(__dirname, '../../../static/template/default/tool-manager.html'), 'utf-8'),
 10 |     style: readFileSync(join(__dirname, '../../../static/style/default/index.css'), 'utf-8'),
 11 |     $: {
 12 |         panelTitle: '#panelTitle',
 13 |         createConfigBtn: '#createConfigBtn',
 14 |         importConfigBtn: '#importConfigBtn',
 15 |         exportConfigBtn: '#exportConfigBtn',
 16 |         configSelector: '#configSelector',
 17 |         applyConfigBtn: '#applyConfigBtn',
 18 |         editConfigBtn: '#editConfigBtn',
 19 |         deleteConfigBtn: '#deleteConfigBtn',
 20 |         toolsContainer: '#toolsContainer',
 21 |         selectAllBtn: '#selectAllBtn',
 22 |         deselectAllBtn: '#deselectAllBtn',
 23 |         saveChangesBtn: '#saveChangesBtn',
 24 |         totalToolsCount: '#totalToolsCount',
 25 |         enabledToolsCount: '#enabledToolsCount',
 26 |         disabledToolsCount: '#disabledToolsCount',
 27 |         configModal: '#configModal',
 28 |         modalTitle: '#modalTitle',
 29 |         configForm: '#configForm',
 30 |         configName: '#configName',
 31 |         configDescription: '#configDescription',
 32 |         closeModal: '#closeModal',
 33 |         cancelConfigBtn: '#cancelConfigBtn',
 34 |         saveConfigBtn: '#saveConfigBtn',
 35 |         importModal: '#importModal',
 36 |         importConfigJson: '#importConfigJson',
 37 |         closeImportModal: '#closeImportModal',
 38 |         cancelImportBtn: '#cancelImportBtn',
 39 |         confirmImportBtn: '#confirmImportBtn'
 40 |     },
 41 |     methods: {
 42 |         async loadToolManagerState(this: any) {
 43 |             try {
 44 |                 this.toolManagerState = await Editor.Message.request('cocos-mcp-server', 'getToolManagerState');
 45 |                 this.currentConfiguration = this.toolManagerState.currentConfiguration;
 46 |                 this.configurations = this.toolManagerState.configurations;
 47 |                 this.availableTools = this.toolManagerState.availableTools;
 48 |                 this.updateUI();
 49 |             } catch (error) {
 50 |                 console.error('Failed to load tool manager state:', error);
 51 |                 this.showError('加载工具管理器状态失败');
 52 |             }
 53 |         },
 54 | 
 55 |         updateUI(this: any) {
 56 |             this.updateConfigSelector();
 57 |             this.updateToolsDisplay();
 58 |             this.updateStatusBar();
 59 |             this.updateButtons();
 60 |         },
 61 | 
 62 |         updateConfigSelector(this: any) {
 63 |             const selector = this.$.configSelector;
 64 |             selector.innerHTML = '<option value="">选择配置...</option>';
 65 |             
 66 |             this.configurations.forEach((config: any) => {
 67 |                 const option = document.createElement('option');
 68 |                 option.value = config.id;
 69 |                 option.textContent = config.name;
 70 |                 if (this.currentConfiguration && config.id === this.currentConfiguration.id) {
 71 |                     option.selected = true;
 72 |                 }
 73 |                 selector.appendChild(option);
 74 |             });
 75 |         },
 76 | 
 77 |         updateToolsDisplay(this: any) {
 78 |             const container = this.$.toolsContainer;
 79 |             
 80 |             if (!this.currentConfiguration) {
 81 |                 container.innerHTML = `
 82 |                     <div class="empty-state">
 83 |                         <h3>没有选择配置</h3>
 84 |                         <p>请先选择一个配置或创建新配置</p>
 85 |                     </div>
 86 |                 `;
 87 |                 return;
 88 |             }
 89 | 
 90 |             const toolsByCategory: any = {};
 91 |             this.currentConfiguration.tools.forEach((tool: any) => {
 92 |                 if (!toolsByCategory[tool.category]) {
 93 |                     toolsByCategory[tool.category] = [];
 94 |                 }
 95 |                 toolsByCategory[tool.category].push(tool);
 96 |             });
 97 | 
 98 |             container.innerHTML = '';
 99 |             
100 |             Object.entries(toolsByCategory).forEach(([category, tools]: [string, any]) => {
101 |                 const categoryDiv = document.createElement('div');
102 |                 categoryDiv.className = 'tool-category';
103 |                 
104 |                 const enabledCount = tools.filter((t: any) => t.enabled).length;
105 |                 const totalCount = tools.length;
106 |                 
107 |                 categoryDiv.innerHTML = `
108 |                     <div class="category-header">
109 |                         <div class="category-name">${this.getCategoryDisplayName(category)}</div>
110 |                         <div class="category-toggle">
111 |                             <span>${enabledCount}/${totalCount}</span>
112 |                             <input type="checkbox" class="checkbox category-checkbox" 
113 |                                    data-category="${category}" 
114 |                                    ${enabledCount === totalCount ? 'checked' : ''}>
115 |                         </div>
116 |                     </div>
117 |                     <div class="tool-list">
118 |                         ${tools.map((tool: any) => `
119 |                             <div class="tool-item">
120 |                                 <div class="tool-info">
121 |                                     <div class="tool-name">${tool.name}</div>
122 |                                     <div class="tool-description">${tool.description}</div>
123 |                                 </div>
124 |                                 <div class="tool-toggle">
125 |                                     <input type="checkbox" class="checkbox tool-checkbox" 
126 |                                            data-category="${tool.category}" 
127 |                                            data-name="${tool.name}" 
128 |                                            ${tool.enabled ? 'checked' : ''}>
129 |                                 </div>
130 |                             </div>
131 |                         `).join('')}
132 |                     </div>
133 |                 `;
134 |                 
135 |                 container.appendChild(categoryDiv);
136 |             });
137 | 
138 |             this.bindToolEvents();
139 |         },
140 | 
141 |         bindToolEvents(this: any) {
142 |             document.querySelectorAll('.category-checkbox').forEach((checkbox: any) => {
143 |                 checkbox.addEventListener('change', (e: any) => {
144 |                     const category = e.target.dataset.category;
145 |                     const checked = e.target.checked;
146 |                     this.toggleCategoryTools(category, checked);
147 |                 });
148 |             });
149 | 
150 |             document.querySelectorAll('.tool-checkbox').forEach((checkbox: any) => {
151 |                 checkbox.addEventListener('change', (e: any) => {
152 |                     const category = e.target.dataset.category;
153 |                     const name = e.target.dataset.name;
154 |                     const enabled = e.target.checked;
155 |                     this.updateToolStatus(category, name, enabled);
156 |                 });
157 |             });
158 |         },
159 | 
160 |         async toggleCategoryTools(this: any, category: string, enabled: boolean) {
161 |             if (!this.currentConfiguration) return;
162 | 
163 |             console.log(`Toggling category tools: ${category} = ${enabled}`);
164 | 
165 |             const categoryTools = this.currentConfiguration.tools.filter((tool: any) => tool.category === category);
166 |             if (categoryTools.length === 0) return;
167 | 
168 |             const updates = categoryTools.map((tool: any) => ({
169 |                 category: tool.category,
170 |                 name: tool.name,
171 |                 enabled: enabled
172 |             }));
173 | 
174 |             try {
175 |                 // 先更新本地状态
176 |                 categoryTools.forEach((tool: any) => {
177 |                     tool.enabled = enabled;
178 |                 });
179 |                 console.log(`Updated local category state: ${category} = ${enabled}`);
180 |                 
181 |                 // 立即更新UI
182 |                 this.updateStatusBar();
183 |                 this.updateCategoryCounts();
184 |                 this.updateToolCheckboxes(category, enabled);
185 | 
186 |                 // 然后发送到后端
187 |                 await Editor.Message.request('cocos-mcp-server', 'updateToolStatusBatch', 
188 |                     this.currentConfiguration.id, updates);
189 |                 
190 |             } catch (error) {
191 |                 console.error('Failed to toggle category tools:', error);
192 |                 this.showError('切换类别工具失败');
193 |                 
194 |                 // 如果后端更新失败,回滚本地状态
195 |                 categoryTools.forEach((tool: any) => {
196 |                     tool.enabled = !enabled;
197 |                 });
198 |                 this.updateStatusBar();
199 |                 this.updateCategoryCounts();
200 |                 this.updateToolCheckboxes(category, !enabled);
201 |             }
202 |         },
203 | 
204 |         async updateToolStatus(this: any, category: string, name: string, enabled: boolean) {
205 |             if (!this.currentConfiguration) return;
206 | 
207 |             console.log(`Updating tool status: ${category}.${name} = ${enabled}`);
208 |             console.log(`Current config ID: ${this.currentConfiguration.id}`);
209 | 
210 |             // 先更新本地状态
211 |             const tool = this.currentConfiguration.tools.find((t: any) => 
212 |                 t.category === category && t.name === name);
213 |             if (!tool) {
214 |                 console.error(`Tool not found: ${category}.${name}`);
215 |                 return;
216 |             }
217 | 
218 |             try {
219 |                 tool.enabled = enabled;
220 |                 console.log(`Updated local tool state: ${tool.name} = ${tool.enabled}`);
221 |                 
222 |                 // 立即更新UI(只更新统计信息,不重新渲染工具列表)
223 |                 this.updateStatusBar();
224 |                 this.updateCategoryCounts();
225 | 
226 |                 // 然后发送到后端
227 |                 console.log(`Sending to backend: configId=${this.currentConfiguration.id}, category=${category}, name=${name}, enabled=${enabled}`);
228 |                 const result = await Editor.Message.request('cocos-mcp-server', 'updateToolStatus', 
229 |                     this.currentConfiguration.id, category, name, enabled);
230 |                 console.log('Backend response:', result);
231 |                 
232 |             } catch (error) {
233 |                 console.error('Failed to update tool status:', error);
234 |                 this.showError('更新工具状态失败');
235 |                 
236 |                 // 如果后端更新失败,回滚本地状态
237 |                 tool.enabled = !enabled;
238 |                 this.updateStatusBar();
239 |                 this.updateCategoryCounts();
240 |             }
241 |         },
242 | 
243 |         updateStatusBar(this: any) {
244 |             if (!this.currentConfiguration) {
245 |                 this.$.totalToolsCount.textContent = '0';
246 |                 this.$.enabledToolsCount.textContent = '0';
247 |                 this.$.disabledToolsCount.textContent = '0';
248 |                 return;
249 |             }
250 | 
251 |             const total = this.currentConfiguration.tools.length;
252 |             const enabled = this.currentConfiguration.tools.filter((t: any) => t.enabled).length;
253 |             const disabled = total - enabled;
254 | 
255 |             console.log(`Status bar update: total=${total}, enabled=${enabled}, disabled=${disabled}`);
256 | 
257 |             this.$.totalToolsCount.textContent = total.toString();
258 |             this.$.enabledToolsCount.textContent = enabled.toString();
259 |             this.$.disabledToolsCount.textContent = disabled.toString();
260 |         },
261 | 
262 |         updateCategoryCounts(this: any) {
263 |             if (!this.currentConfiguration) return;
264 | 
265 |             // 更新每个类别的计数显示
266 |             document.querySelectorAll('.category-checkbox').forEach((checkbox: any) => {
267 |                 const category = checkbox.dataset.category;
268 |                 const categoryTools = this.currentConfiguration.tools.filter((t: any) => t.category === category);
269 |                 const enabledCount = categoryTools.filter((t: any) => t.enabled).length;
270 |                 const totalCount = categoryTools.length;
271 |                 
272 |                 // 更新计数显示
273 |                 const countSpan = checkbox.parentElement.querySelector('span');
274 |                 if (countSpan) {
275 |                     countSpan.textContent = `${enabledCount}/${totalCount}`;
276 |                 }
277 |                 
278 |                 // 更新类别复选框状态
279 |                 checkbox.checked = enabledCount === totalCount;
280 |             });
281 |         },
282 | 
283 |         updateToolCheckboxes(this: any, category: string, enabled: boolean) {
284 |             // 更新特定类别的所有工具复选框
285 |             document.querySelectorAll(`.tool-checkbox[data-category="${category}"]`).forEach((checkbox: any) => {
286 |                 checkbox.checked = enabled;
287 |             });
288 |         },
289 | 
290 |         updateButtons(this: any) {
291 |             const hasCurrentConfig = !!this.currentConfiguration;
292 |             this.$.editConfigBtn.disabled = !hasCurrentConfig;
293 |             this.$.deleteConfigBtn.disabled = !hasCurrentConfig;
294 |             this.$.exportConfigBtn.disabled = !hasCurrentConfig;
295 |             this.$.applyConfigBtn.disabled = !hasCurrentConfig;
296 |         },
297 | 
298 |         async createConfiguration(this: any) {
299 |             this.editingConfig = null;
300 |             this.$.modalTitle.textContent = '新建配置';
301 |             this.$.configName.value = '';
302 |             this.$.configDescription.value = '';
303 |             this.showModal('configModal');
304 |         },
305 | 
306 |         async editConfiguration(this: any) {
307 |             if (!this.currentConfiguration) return;
308 | 
309 |             this.editingConfig = this.currentConfiguration;
310 |             this.$.modalTitle.textContent = '编辑配置';
311 |             this.$.configName.value = this.currentConfiguration.name;
312 |             this.$.configDescription.value = this.currentConfiguration.description || '';
313 |             this.showModal('configModal');
314 |         },
315 | 
316 |         async saveConfiguration(this: any) {
317 |             const name = this.$.configName.value.trim();
318 |             const description = this.$.configDescription.value.trim();
319 | 
320 |             if (!name) {
321 |                 this.showError('配置名称不能为空');
322 |                 return;
323 |             }
324 | 
325 |             try {
326 |                 if (this.editingConfig) {
327 |                     await Editor.Message.request('cocos-mcp-server', 'updateToolConfiguration', 
328 |                         this.editingConfig.id, { name, description });
329 |                 } else {
330 |                     await Editor.Message.request('cocos-mcp-server', 'createToolConfiguration', name, description);
331 |                 }
332 |                 
333 |                 this.hideModal('configModal');
334 |                 await this.loadToolManagerState();
335 |             } catch (error) {
336 |                 console.error('Failed to save configuration:', error);
337 |                 this.showError('保存配置失败');
338 |             }
339 |         },
340 | 
341 |         async deleteConfiguration(this: any) {
342 |             if (!this.currentConfiguration) return;
343 | 
344 |             const confirmed = await Editor.Dialog.warn('确认删除', {
345 |                 detail: `确定要删除配置 "${this.currentConfiguration.name}" 吗?此操作不可撤销。`
346 |             });
347 |             
348 |             if (confirmed) {
349 |                 try {
350 |                     await Editor.Message.request('cocos-mcp-server', 'deleteToolConfiguration', 
351 |                         this.currentConfiguration.id);
352 |                     await this.loadToolManagerState();
353 |                 } catch (error) {
354 |                     console.error('Failed to delete configuration:', error);
355 |                     this.showError('删除配置失败');
356 |                 }
357 |             }
358 |         },
359 | 
360 |         async applyConfiguration(this: any) {
361 |             const configId = this.$.configSelector.value;
362 |             if (!configId) return;
363 | 
364 |             try {
365 |                 await Editor.Message.request('cocos-mcp-server', 'setCurrentToolConfiguration', configId);
366 |                 await this.loadToolManagerState();
367 |             } catch (error) {
368 |                 console.error('Failed to apply configuration:', error);
369 |                 this.showError('应用配置失败');
370 |             }
371 |         },
372 | 
373 |         async exportConfiguration(this: any) {
374 |             if (!this.currentConfiguration) return;
375 | 
376 |             try {
377 |                 const result = await Editor.Message.request('cocos-mcp-server', 'exportToolConfiguration', 
378 |                     this.currentConfiguration.id);
379 |                 
380 |                 Editor.Clipboard.write('text', result.configJson);
381 |                 Editor.Dialog.info('导出成功', { detail: '配置已复制到剪贴板' });
382 |             } catch (error) {
383 |                 console.error('Failed to export configuration:', error);
384 |                 this.showError('导出配置失败');
385 |             }
386 |         },
387 | 
388 |         async importConfiguration(this: any) {
389 |             this.$.importConfigJson.value = '';
390 |             this.showModal('importModal');
391 |         },
392 | 
393 |         async confirmImport(this: any) {
394 |             const configJson = this.$.importConfigJson.value.trim();
395 |             if (!configJson) {
396 |                 this.showError('请输入配置JSON');
397 |                 return;
398 |             }
399 | 
400 |             try {
401 |                 await Editor.Message.request('cocos-mcp-server', 'importToolConfiguration', configJson);
402 |                 this.hideModal('importModal');
403 |                 await this.loadToolManagerState();
404 |                 Editor.Dialog.info('导入成功', { detail: '配置已成功导入' });
405 |             } catch (error) {
406 |                 console.error('Failed to import configuration:', error);
407 |                 this.showError('导入配置失败');
408 |             }
409 |         },
410 | 
411 |         async selectAllTools(this: any) {
412 |             if (!this.currentConfiguration) return;
413 | 
414 |             console.log('Selecting all tools');
415 | 
416 |             const updates = this.currentConfiguration.tools.map((tool: any) => ({
417 |                 category: tool.category,
418 |                 name: tool.name,
419 |                 enabled: true
420 |             }));
421 | 
422 |             try {
423 |                 // 先更新本地状态
424 |                 this.currentConfiguration.tools.forEach((tool: any) => {
425 |                     tool.enabled = true;
426 |                 });
427 |                 console.log('Updated local state: all tools enabled');
428 |                 
429 |                 // 立即更新UI
430 |                 this.updateStatusBar();
431 |                 this.updateToolsDisplay();
432 | 
433 |                 // 然后发送到后端
434 |                 await Editor.Message.request('cocos-mcp-server', 'updateToolStatusBatch', 
435 |                     this.currentConfiguration.id, updates);
436 |                 
437 |             } catch (error) {
438 |                 console.error('Failed to select all tools:', error);
439 |                 this.showError('全选工具失败');
440 |                 
441 |                 // 如果后端更新失败,回滚本地状态
442 |                 this.currentConfiguration.tools.forEach((tool: any) => {
443 |                     tool.enabled = false;
444 |                 });
445 |                 this.updateStatusBar();
446 |                 this.updateToolsDisplay();
447 |             }
448 |         },
449 | 
450 |         async deselectAllTools(this: any) {
451 |             if (!this.currentConfiguration) return;
452 | 
453 |             console.log('Deselecting all tools');
454 | 
455 |             const updates = this.currentConfiguration.tools.map((tool: any) => ({
456 |                 category: tool.category,
457 |                 name: tool.name,
458 |                 enabled: false
459 |             }));
460 | 
461 |             try {
462 |                 // 先更新本地状态
463 |                 this.currentConfiguration.tools.forEach((tool: any) => {
464 |                     tool.enabled = false;
465 |                 });
466 |                 console.log('Updated local state: all tools disabled');
467 |                 
468 |                 // 立即更新UI
469 |                 this.updateStatusBar();
470 |                 this.updateToolsDisplay();
471 | 
472 |                 // 然后发送到后端
473 |                 await Editor.Message.request('cocos-mcp-server', 'updateToolStatusBatch', 
474 |                     this.currentConfiguration.id, updates);
475 |                 
476 |             } catch (error) {
477 |                 console.error('Failed to deselect all tools:', error);
478 |                 this.showError('取消全选工具失败');
479 |                 
480 |                 // 如果后端更新失败,回滚本地状态
481 |                 this.currentConfiguration.tools.forEach((tool: any) => {
482 |                     tool.enabled = true;
483 |                 });
484 |                 this.updateStatusBar();
485 |                 this.updateToolsDisplay();
486 |             }
487 |         },
488 | 
489 |         getCategoryDisplayName(this: any, category: string): string {
490 |             const categoryNames: any = {
491 |                 'scene': '场景工具',
492 |                 'node': '节点工具',
493 |                 'component': '组件工具',
494 |                 'prefab': '预制体工具',
495 |                 'project': '项目工具',
496 |                 'debug': '调试工具',
497 |                 'preferences': '偏好设置工具',
498 |                 'server': '服务器工具',
499 |                 'broadcast': '广播工具',
500 |                 'sceneAdvanced': '高级场景工具',
501 |                 'sceneView': '场景视图工具',
502 |                 'referenceImage': '参考图片工具',
503 |                 'assetAdvanced': '高级资源工具',
504 |                 'validation': '验证工具'
505 |             };
506 |             return categoryNames[category] || category;
507 |         },
508 | 
509 |         showModal(this: any, modalId: string) {
510 |             this.$[modalId].style.display = 'block';
511 |         },
512 | 
513 |         hideModal(this: any, modalId: string) {
514 |             this.$[modalId].style.display = 'none';
515 |         },
516 | 
517 |         showError(this: any, message: string) {
518 |             Editor.Dialog.error('错误', { detail: message });
519 |         },
520 | 
521 |         async saveChanges(this: any) {
522 |             if (!this.currentConfiguration) {
523 |                 this.showError('没有选择配置');
524 |                 return;
525 |             }
526 | 
527 |             try {
528 |                 // 确保当前配置已保存到后端
529 |                 await Editor.Message.request('cocos-mcp-server', 'updateToolConfiguration', 
530 |                     this.currentConfiguration.id, {
531 |                         name: this.currentConfiguration.name,
532 |                         description: this.currentConfiguration.description,
533 |                         tools: this.currentConfiguration.tools
534 |                     });
535 |                 
536 |                 Editor.Dialog.info('保存成功', { detail: '配置更改已保存' });
537 |             } catch (error) {
538 |                 console.error('Failed to save changes:', error);
539 |                 this.showError('保存更改失败');
540 |             }
541 |         },
542 | 
543 |         bindEvents(this: any) {
544 |             this.$.createConfigBtn.addEventListener('click', this.createConfiguration.bind(this));
545 |             this.$.editConfigBtn.addEventListener('click', this.editConfiguration.bind(this));
546 |             this.$.deleteConfigBtn.addEventListener('click', this.deleteConfiguration.bind(this));
547 |             this.$.applyConfigBtn.addEventListener('click', this.applyConfiguration.bind(this));
548 |             this.$.exportConfigBtn.addEventListener('click', this.exportConfiguration.bind(this));
549 |             this.$.importConfigBtn.addEventListener('click', this.importConfiguration.bind(this));
550 | 
551 |             this.$.selectAllBtn.addEventListener('click', this.selectAllTools.bind(this));
552 |             this.$.deselectAllBtn.addEventListener('click', this.deselectAllTools.bind(this));
553 |             this.$.saveChangesBtn.addEventListener('click', this.saveChanges.bind(this));
554 | 
555 |             this.$.closeModal.addEventListener('click', () => this.hideModal('configModal'));
556 |             this.$.cancelConfigBtn.addEventListener('click', () => this.hideModal('configModal'));
557 |             this.$.configForm.addEventListener('submit', (e: any) => {
558 |                 e.preventDefault();
559 |                 this.saveConfiguration();
560 |             });
561 | 
562 |             this.$.closeImportModal.addEventListener('click', () => this.hideModal('importModal'));
563 |             this.$.cancelImportBtn.addEventListener('click', () => this.hideModal('importModal'));
564 |             this.$.confirmImportBtn.addEventListener('click', this.confirmImport.bind(this));
565 | 
566 |             this.$.configSelector.addEventListener('change', this.applyConfiguration.bind(this));
567 |         }
568 |     },
569 |     ready() {
570 |         (this as any).toolManagerState = null;
571 |         (this as any).currentConfiguration = null;
572 |         (this as any).configurations = [];
573 |         (this as any).availableTools = [];
574 |         (this as any).editingConfig = null;
575 | 
576 |         (this as any).bindEvents();
577 |         (this as any).loadToolManagerState();
578 |     },
579 |     beforeClose() {
580 |         // 清理工作
581 |     },
582 |     close() {
583 |         // 面板关闭清理
584 |     }
585 | } as any); 
```

--------------------------------------------------------------------------------
/source/tools/debug-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ToolDefinition, ToolResponse, ToolExecutor, ConsoleMessage, PerformanceStats, ValidationResult, ValidationIssue } from '../types';
  2 | import * as fs from 'fs';
  3 | import * as path from 'path';
  4 | 
  5 | export class DebugTools implements ToolExecutor {
  6 |     private consoleMessages: ConsoleMessage[] = [];
  7 |     private readonly maxMessages = 1000;
  8 | 
  9 |     constructor() {
 10 |         this.setupConsoleCapture();
 11 |     }
 12 | 
 13 |     private setupConsoleCapture(): void {
 14 |         // Intercept Editor console messages
 15 |         // Note: Editor.Message.addBroadcastListener may not be available in all versions
 16 |         // This is a placeholder for console capture implementation
 17 |         console.log('Console capture setup - implementation depends on Editor API availability');
 18 |     }
 19 | 
 20 |     private addConsoleMessage(message: any): void {
 21 |         this.consoleMessages.push({
 22 |             timestamp: new Date().toISOString(),
 23 |             ...message
 24 |         });
 25 | 
 26 |         // Keep only latest messages
 27 |         if (this.consoleMessages.length > this.maxMessages) {
 28 |             this.consoleMessages.shift();
 29 |         }
 30 |     }
 31 | 
 32 |     getTools(): ToolDefinition[] {
 33 |         return [
 34 |             {
 35 |                 name: 'get_console_logs',
 36 |                 description: 'Get editor console logs',
 37 |                 inputSchema: {
 38 |                     type: 'object',
 39 |                     properties: {
 40 |                         limit: {
 41 |                             type: 'number',
 42 |                             description: 'Number of recent logs to retrieve',
 43 |                             default: 100
 44 |                         },
 45 |                         filter: {
 46 |                             type: 'string',
 47 |                             description: 'Filter logs by type',
 48 |                             enum: ['all', 'log', 'warn', 'error', 'info'],
 49 |                             default: 'all'
 50 |                         }
 51 |                     }
 52 |                 }
 53 |             },
 54 |             {
 55 |                 name: 'clear_console',
 56 |                 description: 'Clear editor console',
 57 |                 inputSchema: {
 58 |                     type: 'object',
 59 |                     properties: {}
 60 |                 }
 61 |             },
 62 |             {
 63 |                 name: 'execute_script',
 64 |                 description: 'Execute JavaScript in scene context',
 65 |                 inputSchema: {
 66 |                     type: 'object',
 67 |                     properties: {
 68 |                         script: {
 69 |                             type: 'string',
 70 |                             description: 'JavaScript code to execute'
 71 |                         }
 72 |                     },
 73 |                     required: ['script']
 74 |                 }
 75 |             },
 76 |             {
 77 |                 name: 'get_node_tree',
 78 |                 description: 'Get detailed node tree for debugging',
 79 |                 inputSchema: {
 80 |                     type: 'object',
 81 |                     properties: {
 82 |                         rootUuid: {
 83 |                             type: 'string',
 84 |                             description: 'Root node UUID (optional, uses scene root if not provided)'
 85 |                         },
 86 |                         maxDepth: {
 87 |                             type: 'number',
 88 |                             description: 'Maximum tree depth',
 89 |                             default: 10
 90 |                         }
 91 |                     }
 92 |                 }
 93 |             },
 94 |             {
 95 |                 name: 'get_performance_stats',
 96 |                 description: 'Get performance statistics',
 97 |                 inputSchema: {
 98 |                     type: 'object',
 99 |                     properties: {}
100 |                 }
101 |             },
102 |             {
103 |                 name: 'validate_scene',
104 |                 description: 'Validate current scene for issues',
105 |                 inputSchema: {
106 |                     type: 'object',
107 |                     properties: {
108 |                         checkMissingAssets: {
109 |                             type: 'boolean',
110 |                             description: 'Check for missing asset references',
111 |                             default: true
112 |                         },
113 |                         checkPerformance: {
114 |                             type: 'boolean',
115 |                             description: 'Check for performance issues',
116 |                             default: true
117 |                         }
118 |                     }
119 |                 }
120 |             },
121 |             {
122 |                 name: 'get_editor_info',
123 |                 description: 'Get editor and environment information',
124 |                 inputSchema: {
125 |                     type: 'object',
126 |                     properties: {}
127 |                 }
128 |             },
129 |             {
130 |                 name: 'get_project_logs',
131 |                 description: 'Get project logs from temp/logs/project.log file',
132 |                 inputSchema: {
133 |                     type: 'object',
134 |                     properties: {
135 |                         lines: {
136 |                             type: 'number',
137 |                             description: 'Number of lines to read from the end of the log file (default: 100)',
138 |                             default: 100,
139 |                             minimum: 1,
140 |                             maximum: 10000
141 |                         },
142 |                         filterKeyword: {
143 |                             type: 'string',
144 |                             description: 'Filter logs containing specific keyword (optional)'
145 |                         },
146 |                         logLevel: {
147 |                             type: 'string',
148 |                             description: 'Filter by log level',
149 |                             enum: ['ERROR', 'WARN', 'INFO', 'DEBUG', 'TRACE', 'ALL'],
150 |                             default: 'ALL'
151 |                         }
152 |                     }
153 |                 }
154 |             },
155 |             {
156 |                 name: 'get_log_file_info',
157 |                 description: 'Get information about the project log file',
158 |                 inputSchema: {
159 |                     type: 'object',
160 |                     properties: {}
161 |                 }
162 |             },
163 |             {
164 |                 name: 'search_project_logs',
165 |                 description: 'Search for specific patterns or errors in project logs',
166 |                 inputSchema: {
167 |                     type: 'object',
168 |                     properties: {
169 |                         pattern: {
170 |                             type: 'string',
171 |                             description: 'Search pattern (supports regex)'
172 |                         },
173 |                         maxResults: {
174 |                             type: 'number',
175 |                             description: 'Maximum number of matching results',
176 |                             default: 20,
177 |                             minimum: 1,
178 |                             maximum: 100
179 |                         },
180 |                         contextLines: {
181 |                             type: 'number',
182 |                             description: 'Number of context lines to show around each match',
183 |                             default: 2,
184 |                             minimum: 0,
185 |                             maximum: 10
186 |                         }
187 |                     },
188 |                     required: ['pattern']
189 |                 }
190 |             }
191 |         ];
192 |     }
193 | 
194 |     async execute(toolName: string, args: any): Promise<ToolResponse> {
195 |         switch (toolName) {
196 |             case 'get_console_logs':
197 |                 return await this.getConsoleLogs(args.limit, args.filter);
198 |             case 'clear_console':
199 |                 return await this.clearConsole();
200 |             case 'execute_script':
201 |                 return await this.executeScript(args.script);
202 |             case 'get_node_tree':
203 |                 return await this.getNodeTree(args.rootUuid, args.maxDepth);
204 |             case 'get_performance_stats':
205 |                 return await this.getPerformanceStats();
206 |             case 'validate_scene':
207 |                 return await this.validateScene(args);
208 |             case 'get_editor_info':
209 |                 return await this.getEditorInfo();
210 |             case 'get_project_logs':
211 |                 return await this.getProjectLogs(args.lines, args.filterKeyword, args.logLevel);
212 |             case 'get_log_file_info':
213 |                 return await this.getLogFileInfo();
214 |             case 'search_project_logs':
215 |                 return await this.searchProjectLogs(args.pattern, args.maxResults, args.contextLines);
216 |             default:
217 |                 throw new Error(`Unknown tool: ${toolName}`);
218 |         }
219 |     }
220 | 
221 |     private async getConsoleLogs(limit: number = 100, filter: string = 'all'): Promise<ToolResponse> {
222 |         let logs = this.consoleMessages;
223 |         
224 |         if (filter !== 'all') {
225 |             logs = logs.filter(log => log.type === filter);
226 |         }
227 | 
228 |         const recentLogs = logs.slice(-limit);
229 |         
230 |         return {
231 |             success: true,
232 |             data: {
233 |                 total: logs.length,
234 |                 returned: recentLogs.length,
235 |                 logs: recentLogs
236 |             }
237 |         };
238 |     }
239 | 
240 |     private async clearConsole(): Promise<ToolResponse> {
241 |         this.consoleMessages = [];
242 |         
243 |         try {
244 |             // Note: Editor.Message.send may not return a promise in all versions
245 |             Editor.Message.send('console', 'clear');
246 |             return {
247 |                 success: true,
248 |                 message: 'Console cleared successfully'
249 |             };
250 |         } catch (err: any) {
251 |             return { success: false, error: err.message };
252 |         }
253 |     }
254 | 
255 |     private async executeScript(script: string): Promise<ToolResponse> {
256 |         return new Promise((resolve) => {
257 |             Editor.Message.request('scene', 'execute-scene-script', {
258 |                 name: 'console',
259 |                 method: 'eval',
260 |                 args: [script]
261 |             }).then((result: any) => {
262 |                 resolve({
263 |                     success: true,
264 |                     data: {
265 |                         result: result,
266 |                         message: 'Script executed successfully'
267 |                     }
268 |                 });
269 |             }).catch((err: Error) => {
270 |                 resolve({ success: false, error: err.message });
271 |             });
272 |         });
273 |     }
274 | 
275 |     private async getNodeTree(rootUuid?: string, maxDepth: number = 10): Promise<ToolResponse> {
276 |         return new Promise((resolve) => {
277 |             const buildTree = async (nodeUuid: string, depth: number = 0): Promise<any> => {
278 |                 if (depth >= maxDepth) {
279 |                     return { truncated: true };
280 |                 }
281 | 
282 |                 try {
283 |                     const nodeData = await Editor.Message.request('scene', 'query-node', nodeUuid);
284 |                     
285 |                     const tree = {
286 |                         uuid: nodeData.uuid,
287 |                         name: nodeData.name,
288 |                         active: nodeData.active,
289 |                         components: (nodeData as any).components ? (nodeData as any).components.map((c: any) => c.__type__) : [],
290 |                         childCount: nodeData.children ? nodeData.children.length : 0,
291 |                         children: [] as any[]
292 |                     };
293 | 
294 |                     if (nodeData.children && nodeData.children.length > 0) {
295 |                         for (const childId of nodeData.children) {
296 |                             const childTree = await buildTree(childId, depth + 1);
297 |                             tree.children.push(childTree);
298 |                         }
299 |                     }
300 | 
301 |                     return tree;
302 |                 } catch (err: any) {
303 |                     return { error: err.message };
304 |                 }
305 |             };
306 | 
307 |             if (rootUuid) {
308 |                 buildTree(rootUuid).then(tree => {
309 |                     resolve({ success: true, data: tree });
310 |                 });
311 |             } else {
312 |                 Editor.Message.request('scene', 'query-hierarchy').then(async (hierarchy: any) => {
313 |                     const trees = [];
314 |                     for (const rootNode of hierarchy.children) {
315 |                         const tree = await buildTree(rootNode.uuid);
316 |                         trees.push(tree);
317 |                     }
318 |                     resolve({ success: true, data: trees });
319 |                 }).catch((err: Error) => {
320 |                     resolve({ success: false, error: err.message });
321 |                 });
322 |             }
323 |         });
324 |     }
325 | 
326 |     private async getPerformanceStats(): Promise<ToolResponse> {
327 |         return new Promise((resolve) => {
328 |             Editor.Message.request('scene', 'query-performance').then((stats: any) => {
329 |                 const perfStats: PerformanceStats = {
330 |                     nodeCount: stats.nodeCount || 0,
331 |                     componentCount: stats.componentCount || 0,
332 |                     drawCalls: stats.drawCalls || 0,
333 |                     triangles: stats.triangles || 0,
334 |                     memory: stats.memory || {}
335 |                 };
336 |                 resolve({ success: true, data: perfStats });
337 |             }).catch(() => {
338 |                 // Fallback to basic stats
339 |                 resolve({
340 |                     success: true,
341 |                     data: {
342 |                         message: 'Performance stats not available in edit mode'
343 |                     }
344 |                 });
345 |             });
346 |         });
347 |     }
348 | 
349 |     private async validateScene(options: any): Promise<ToolResponse> {
350 |         const issues: ValidationIssue[] = [];
351 | 
352 |         try {
353 |             // Check for missing assets
354 |             if (options.checkMissingAssets) {
355 |                 const assetCheck = await Editor.Message.request('scene', 'check-missing-assets');
356 |                 if (assetCheck && assetCheck.missing) {
357 |                     issues.push({
358 |                         type: 'error',
359 |                         category: 'assets',
360 |                         message: `Found ${assetCheck.missing.length} missing asset references`,
361 |                         details: assetCheck.missing
362 |                     });
363 |                 }
364 |             }
365 | 
366 |             // Check for performance issues
367 |             if (options.checkPerformance) {
368 |                 const hierarchy = await Editor.Message.request('scene', 'query-hierarchy');
369 |                 const nodeCount = this.countNodes(hierarchy.children);
370 |                 
371 |                 if (nodeCount > 1000) {
372 |                     issues.push({
373 |                         type: 'warning',
374 |                         category: 'performance',
375 |                         message: `High node count: ${nodeCount} nodes (recommended < 1000)`,
376 |                         suggestion: 'Consider using object pooling or scene optimization'
377 |                     });
378 |                 }
379 |             }
380 | 
381 |             const result: ValidationResult = {
382 |                 valid: issues.length === 0,
383 |                 issueCount: issues.length,
384 |                 issues: issues
385 |             };
386 | 
387 |             return { success: true, data: result };
388 |         } catch (err: any) {
389 |             return { success: false, error: err.message };
390 |         }
391 |     }
392 | 
393 |     private countNodes(nodes: any[]): number {
394 |         let count = nodes.length;
395 |         for (const node of nodes) {
396 |             if (node.children) {
397 |                 count += this.countNodes(node.children);
398 |             }
399 |         }
400 |         return count;
401 |     }
402 | 
403 |     private async getEditorInfo(): Promise<ToolResponse> {
404 |         const info = {
405 |             editor: {
406 |                 version: (Editor as any).versions?.editor || 'Unknown',
407 |                 cocosVersion: (Editor as any).versions?.cocos || 'Unknown',
408 |                 platform: process.platform,
409 |                 arch: process.arch,
410 |                 nodeVersion: process.version
411 |             },
412 |             project: {
413 |                 name: Editor.Project.name,
414 |                 path: Editor.Project.path,
415 |                 uuid: Editor.Project.uuid
416 |             },
417 |             memory: process.memoryUsage(),
418 |             uptime: process.uptime()
419 |         };
420 | 
421 |         return { success: true, data: info };
422 |     }
423 | 
424 |     private async getProjectLogs(lines: number = 100, filterKeyword?: string, logLevel: string = 'ALL'): Promise<ToolResponse> {
425 |         try {
426 |             // Try multiple possible project paths
427 |             let logFilePath = '';
428 |             const possiblePaths = [
429 |                 Editor.Project ? Editor.Project.path : null,
430 |                 '/Users/lizhiyong/NewProject_3',
431 |                 process.cwd(),
432 |             ].filter(p => p !== null);
433 |             
434 |             for (const basePath of possiblePaths) {
435 |                 const testPath = path.join(basePath, 'temp/logs/project.log');
436 |                 if (fs.existsSync(testPath)) {
437 |                     logFilePath = testPath;
438 |                     break;
439 |                 }
440 |             }
441 |             
442 |             if (!logFilePath) {
443 |                 return {
444 |                     success: false,
445 |                     error: `Project log file not found. Tried paths: ${possiblePaths.map(p => path.join(p, 'temp/logs/project.log')).join(', ')}`
446 |                 };
447 |             }
448 | 
449 |             // Read the file content
450 |             const logContent = fs.readFileSync(logFilePath, 'utf8');
451 |             const logLines = logContent.split('\n').filter(line => line.trim() !== '');
452 |             
453 |             // Get the last N lines
454 |             const recentLines = logLines.slice(-lines);
455 |             
456 |             // Apply filters
457 |             let filteredLines = recentLines;
458 |             
459 |             // Filter by log level if not 'ALL'
460 |             if (logLevel !== 'ALL') {
461 |                 filteredLines = filteredLines.filter(line => 
462 |                     line.includes(`[${logLevel}]`) || line.includes(logLevel.toLowerCase())
463 |                 );
464 |             }
465 |             
466 |             // Filter by keyword if provided
467 |             if (filterKeyword) {
468 |                 filteredLines = filteredLines.filter(line => 
469 |                     line.toLowerCase().includes(filterKeyword.toLowerCase())
470 |                 );
471 |             }
472 |             
473 |             return {
474 |                 success: true,
475 |                 data: {
476 |                     totalLines: logLines.length,
477 |                     requestedLines: lines,
478 |                     filteredLines: filteredLines.length,
479 |                     logLevel: logLevel,
480 |                     filterKeyword: filterKeyword || null,
481 |                     logs: filteredLines,
482 |                     logFilePath: logFilePath
483 |                 }
484 |             };
485 |         } catch (error: any) {
486 |             return {
487 |                 success: false,
488 |                 error: `Failed to read project logs: ${error.message}`
489 |             };
490 |         }
491 |     }
492 | 
493 |     private async getLogFileInfo(): Promise<ToolResponse> {
494 |         try {
495 |             // Try multiple possible project paths
496 |             let logFilePath = '';
497 |             const possiblePaths = [
498 |                 Editor.Project ? Editor.Project.path : null,
499 |                 '/Users/lizhiyong/NewProject_3',
500 |                 process.cwd(),
501 |             ].filter(p => p !== null);
502 |             
503 |             for (const basePath of possiblePaths) {
504 |                 const testPath = path.join(basePath, 'temp/logs/project.log');
505 |                 if (fs.existsSync(testPath)) {
506 |                     logFilePath = testPath;
507 |                     break;
508 |                 }
509 |             }
510 |             
511 |             if (!logFilePath) {
512 |                 return {
513 |                     success: false,
514 |                     error: `Project log file not found. Tried paths: ${possiblePaths.map(p => path.join(p, 'temp/logs/project.log')).join(', ')}`
515 |                 };
516 |             }
517 | 
518 |             const stats = fs.statSync(logFilePath);
519 |             const logContent = fs.readFileSync(logFilePath, 'utf8');
520 |             const lineCount = logContent.split('\n').filter(line => line.trim() !== '').length;
521 |             
522 |             return {
523 |                 success: true,
524 |                 data: {
525 |                     filePath: logFilePath,
526 |                     fileSize: stats.size,
527 |                     fileSizeFormatted: this.formatFileSize(stats.size),
528 |                     lastModified: stats.mtime.toISOString(),
529 |                     lineCount: lineCount,
530 |                     created: stats.birthtime.toISOString(),
531 |                     accessible: fs.constants.R_OK
532 |                 }
533 |             };
534 |         } catch (error: any) {
535 |             return {
536 |                 success: false,
537 |                 error: `Failed to get log file info: ${error.message}`
538 |             };
539 |         }
540 |     }
541 | 
542 |     private async searchProjectLogs(pattern: string, maxResults: number = 20, contextLines: number = 2): Promise<ToolResponse> {
543 |         try {
544 |             // Try multiple possible project paths
545 |             let logFilePath = '';
546 |             const possiblePaths = [
547 |                 Editor.Project ? Editor.Project.path : null,
548 |                 '/Users/lizhiyong/NewProject_3',
549 |                 process.cwd(),
550 |             ].filter(p => p !== null);
551 |             
552 |             for (const basePath of possiblePaths) {
553 |                 const testPath = path.join(basePath, 'temp/logs/project.log');
554 |                 if (fs.existsSync(testPath)) {
555 |                     logFilePath = testPath;
556 |                     break;
557 |                 }
558 |             }
559 |             
560 |             if (!logFilePath) {
561 |                 return {
562 |                     success: false,
563 |                     error: `Project log file not found. Tried paths: ${possiblePaths.map(p => path.join(p, 'temp/logs/project.log')).join(', ')}`
564 |                 };
565 |             }
566 | 
567 |             const logContent = fs.readFileSync(logFilePath, 'utf8');
568 |             const logLines = logContent.split('\n');
569 |             
570 |             // Create regex pattern (support both string and regex patterns)
571 |             let regex: RegExp;
572 |             try {
573 |                 regex = new RegExp(pattern, 'gi');
574 |             } catch {
575 |                 // If pattern is not valid regex, treat as literal string
576 |                 regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
577 |             }
578 |             
579 |             const matches: any[] = [];
580 |             let resultCount = 0;
581 |             
582 |             for (let i = 0; i < logLines.length && resultCount < maxResults; i++) {
583 |                 const line = logLines[i];
584 |                 if (regex.test(line)) {
585 |                     // Get context lines
586 |                     const contextStart = Math.max(0, i - contextLines);
587 |                     const contextEnd = Math.min(logLines.length - 1, i + contextLines);
588 |                     
589 |                     const contextLinesArray = [];
590 |                     for (let j = contextStart; j <= contextEnd; j++) {
591 |                         contextLinesArray.push({
592 |                             lineNumber: j + 1,
593 |                             content: logLines[j],
594 |                             isMatch: j === i
595 |                         });
596 |                     }
597 |                     
598 |                     matches.push({
599 |                         lineNumber: i + 1,
600 |                         matchedLine: line,
601 |                         context: contextLinesArray
602 |                     });
603 |                     
604 |                     resultCount++;
605 |                     
606 |                     // Reset regex lastIndex for global search
607 |                     regex.lastIndex = 0;
608 |                 }
609 |             }
610 |             
611 |             return {
612 |                 success: true,
613 |                 data: {
614 |                     pattern: pattern,
615 |                     totalMatches: matches.length,
616 |                     maxResults: maxResults,
617 |                     contextLines: contextLines,
618 |                     logFilePath: logFilePath,
619 |                     matches: matches
620 |                 }
621 |             };
622 |         } catch (error: any) {
623 |             return {
624 |                 success: false,
625 |                 error: `Failed to search project logs: ${error.message}`
626 |             };
627 |         }
628 |     }
629 | 
630 |     private formatFileSize(bytes: number): string {
631 |         const units = ['B', 'KB', 'MB', 'GB'];
632 |         let size = bytes;
633 |         let unitIndex = 0;
634 |         
635 |         while (size >= 1024 && unitIndex < units.length - 1) {
636 |             size /= 1024;
637 |             unitIndex++;
638 |         }
639 |         
640 |         return `${size.toFixed(2)} ${units[unitIndex]}`;
641 |     }
642 | }
```

--------------------------------------------------------------------------------
/source/tools/asset-advanced-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ToolDefinition, ToolResponse, ToolExecutor } from '../types';
  2 | 
  3 | export class AssetAdvancedTools implements ToolExecutor {
  4 |     getTools(): ToolDefinition[] {
  5 |         return [
  6 |             {
  7 |                 name: 'save_asset_meta',
  8 |                 description: 'Save asset meta information',
  9 |                 inputSchema: {
 10 |                     type: 'object',
 11 |                     properties: {
 12 |                         urlOrUUID: {
 13 |                             type: 'string',
 14 |                             description: 'Asset URL or UUID'
 15 |                         },
 16 |                         content: {
 17 |                             type: 'string',
 18 |                             description: 'Asset meta serialized content string'
 19 |                         }
 20 |                     },
 21 |                     required: ['urlOrUUID', 'content']
 22 |                 }
 23 |             },
 24 |             {
 25 |                 name: 'generate_available_url',
 26 |                 description: 'Generate an available URL based on input URL',
 27 |                 inputSchema: {
 28 |                     type: 'object',
 29 |                     properties: {
 30 |                         url: {
 31 |                             type: 'string',
 32 |                             description: 'Asset URL to generate available URL for'
 33 |                         }
 34 |                     },
 35 |                     required: ['url']
 36 |                 }
 37 |             },
 38 |             {
 39 |                 name: 'query_asset_db_ready',
 40 |                 description: 'Check if asset database is ready',
 41 |                 inputSchema: {
 42 |                     type: 'object',
 43 |                     properties: {}
 44 |                 }
 45 |             },
 46 |             {
 47 |                 name: 'open_asset_external',
 48 |                 description: 'Open asset with external program',
 49 |                 inputSchema: {
 50 |                     type: 'object',
 51 |                     properties: {
 52 |                         urlOrUUID: {
 53 |                             type: 'string',
 54 |                             description: 'Asset URL or UUID to open'
 55 |                         }
 56 |                     },
 57 |                     required: ['urlOrUUID']
 58 |                 }
 59 |             },
 60 |             {
 61 |                 name: 'batch_import_assets',
 62 |                 description: 'Import multiple assets in batch',
 63 |                 inputSchema: {
 64 |                     type: 'object',
 65 |                     properties: {
 66 |                         sourceDirectory: {
 67 |                             type: 'string',
 68 |                             description: 'Source directory path'
 69 |                         },
 70 |                         targetDirectory: {
 71 |                             type: 'string',
 72 |                             description: 'Target directory URL'
 73 |                         },
 74 |                         fileFilter: {
 75 |                             type: 'array',
 76 |                             items: { type: 'string' },
 77 |                             description: 'File extensions to include (e.g., [".png", ".jpg"])',
 78 |                             default: []
 79 |                         },
 80 |                         recursive: {
 81 |                             type: 'boolean',
 82 |                             description: 'Include subdirectories',
 83 |                             default: false
 84 |                         },
 85 |                         overwrite: {
 86 |                             type: 'boolean',
 87 |                             description: 'Overwrite existing files',
 88 |                             default: false
 89 |                         }
 90 |                     },
 91 |                     required: ['sourceDirectory', 'targetDirectory']
 92 |                 }
 93 |             },
 94 |             {
 95 |                 name: 'batch_delete_assets',
 96 |                 description: 'Delete multiple assets in batch',
 97 |                 inputSchema: {
 98 |                     type: 'object',
 99 |                     properties: {
100 |                         urls: {
101 |                             type: 'array',
102 |                             items: { type: 'string' },
103 |                             description: 'Array of asset URLs to delete'
104 |                         }
105 |                     },
106 |                     required: ['urls']
107 |                 }
108 |             },
109 |             {
110 |                 name: 'validate_asset_references',
111 |                 description: 'Validate asset references and find broken links',
112 |                 inputSchema: {
113 |                     type: 'object',
114 |                     properties: {
115 |                         directory: {
116 |                             type: 'string',
117 |                             description: 'Directory to validate (default: entire project)',
118 |                             default: 'db://assets'
119 |                         }
120 |                     }
121 |                 }
122 |             },
123 |             {
124 |                 name: 'get_asset_dependencies',
125 |                 description: 'Get asset dependency tree',
126 |                 inputSchema: {
127 |                     type: 'object',
128 |                     properties: {
129 |                         urlOrUUID: {
130 |                             type: 'string',
131 |                             description: 'Asset URL or UUID'
132 |                         },
133 |                         direction: {
134 |                             type: 'string',
135 |                             description: 'Dependency direction',
136 |                             enum: ['dependents', 'dependencies', 'both'],
137 |                             default: 'dependencies'
138 |                         }
139 |                     },
140 |                     required: ['urlOrUUID']
141 |                 }
142 |             },
143 |             {
144 |                 name: 'get_unused_assets',
145 |                 description: 'Find unused assets in project',
146 |                 inputSchema: {
147 |                     type: 'object',
148 |                     properties: {
149 |                         directory: {
150 |                             type: 'string',
151 |                             description: 'Directory to scan (default: entire project)',
152 |                             default: 'db://assets'
153 |                         },
154 |                         excludeDirectories: {
155 |                             type: 'array',
156 |                             items: { type: 'string' },
157 |                             description: 'Directories to exclude from scan',
158 |                             default: []
159 |                         }
160 |                     }
161 |                 }
162 |             },
163 |             {
164 |                 name: 'compress_textures',
165 |                 description: 'Batch compress texture assets',
166 |                 inputSchema: {
167 |                     type: 'object',
168 |                     properties: {
169 |                         directory: {
170 |                             type: 'string',
171 |                             description: 'Directory containing textures',
172 |                             default: 'db://assets'
173 |                         },
174 |                         format: {
175 |                             type: 'string',
176 |                             description: 'Compression format',
177 |                             enum: ['auto', 'jpg', 'png', 'webp'],
178 |                             default: 'auto'
179 |                         },
180 |                         quality: {
181 |                             type: 'number',
182 |                             description: 'Compression quality (0.1-1.0)',
183 |                             minimum: 0.1,
184 |                             maximum: 1.0,
185 |                             default: 0.8
186 |                         }
187 |                     }
188 |                 }
189 |             },
190 |             {
191 |                 name: 'export_asset_manifest',
192 |                 description: 'Export asset manifest/inventory',
193 |                 inputSchema: {
194 |                     type: 'object',
195 |                     properties: {
196 |                         directory: {
197 |                             type: 'string',
198 |                             description: 'Directory to export manifest for',
199 |                             default: 'db://assets'
200 |                         },
201 |                         format: {
202 |                             type: 'string',
203 |                             description: 'Export format',
204 |                             enum: ['json', 'csv', 'xml'],
205 |                             default: 'json'
206 |                         },
207 |                         includeMetadata: {
208 |                             type: 'boolean',
209 |                             description: 'Include asset metadata',
210 |                             default: true
211 |                         }
212 |                     }
213 |                 }
214 |             }
215 |         ];
216 |     }
217 | 
218 |     async execute(toolName: string, args: any): Promise<ToolResponse> {
219 |         switch (toolName) {
220 |             case 'save_asset_meta':
221 |                 return await this.saveAssetMeta(args.urlOrUUID, args.content);
222 |             case 'generate_available_url':
223 |                 return await this.generateAvailableUrl(args.url);
224 |             case 'query_asset_db_ready':
225 |                 return await this.queryAssetDbReady();
226 |             case 'open_asset_external':
227 |                 return await this.openAssetExternal(args.urlOrUUID);
228 |             case 'batch_import_assets':
229 |                 return await this.batchImportAssets(args);
230 |             case 'batch_delete_assets':
231 |                 return await this.batchDeleteAssets(args.urls);
232 |             case 'validate_asset_references':
233 |                 return await this.validateAssetReferences(args.directory);
234 |             case 'get_asset_dependencies':
235 |                 return await this.getAssetDependencies(args.urlOrUUID, args.direction);
236 |             case 'get_unused_assets':
237 |                 return await this.getUnusedAssets(args.directory, args.excludeDirectories);
238 |             case 'compress_textures':
239 |                 return await this.compressTextures(args.directory, args.format, args.quality);
240 |             case 'export_asset_manifest':
241 |                 return await this.exportAssetManifest(args.directory, args.format, args.includeMetadata);
242 |             default:
243 |                 throw new Error(`Unknown tool: ${toolName}`);
244 |         }
245 |     }
246 | 
247 |     private async saveAssetMeta(urlOrUUID: string, content: string): Promise<ToolResponse> {
248 |         return new Promise((resolve) => {
249 |             Editor.Message.request('asset-db', 'save-asset-meta', urlOrUUID, content).then((result: any) => {
250 |                 resolve({
251 |                     success: true,
252 |                     data: {
253 |                         uuid: result?.uuid,
254 |                         url: result?.url,
255 |                         message: 'Asset meta saved successfully'
256 |                     }
257 |                 });
258 |             }).catch((err: Error) => {
259 |                 resolve({ success: false, error: err.message });
260 |             });
261 |         });
262 |     }
263 | 
264 |     private async generateAvailableUrl(url: string): Promise<ToolResponse> {
265 |         return new Promise((resolve) => {
266 |             Editor.Message.request('asset-db', 'generate-available-url', url).then((availableUrl: string) => {
267 |                 resolve({
268 |                     success: true,
269 |                     data: {
270 |                         originalUrl: url,
271 |                         availableUrl: availableUrl,
272 |                         message: availableUrl === url ? 
273 |                             'URL is available' : 
274 |                             'Generated new available URL'
275 |                     }
276 |                 });
277 |             }).catch((err: Error) => {
278 |                 resolve({ success: false, error: err.message });
279 |             });
280 |         });
281 |     }
282 | 
283 |     private async queryAssetDbReady(): Promise<ToolResponse> {
284 |         return new Promise((resolve) => {
285 |             Editor.Message.request('asset-db', 'query-ready').then((ready: boolean) => {
286 |                 resolve({
287 |                     success: true,
288 |                     data: {
289 |                         ready: ready,
290 |                         message: ready ? 'Asset database is ready' : 'Asset database is not ready'
291 |                     }
292 |                 });
293 |             }).catch((err: Error) => {
294 |                 resolve({ success: false, error: err.message });
295 |             });
296 |         });
297 |     }
298 | 
299 |     private async openAssetExternal(urlOrUUID: string): Promise<ToolResponse> {
300 |         return new Promise((resolve) => {
301 |             Editor.Message.request('asset-db', 'open-asset', urlOrUUID).then(() => {
302 |                 resolve({
303 |                     success: true,
304 |                     message: 'Asset opened with external program'
305 |                 });
306 |             }).catch((err: Error) => {
307 |                 resolve({ success: false, error: err.message });
308 |             });
309 |         });
310 |     }
311 | 
312 |     private async batchImportAssets(args: any): Promise<ToolResponse> {
313 |         return new Promise(async (resolve) => {
314 |             try {
315 |                 const fs = require('fs');
316 |                 const path = require('path');
317 |                 
318 |                 if (!fs.existsSync(args.sourceDirectory)) {
319 |                     resolve({ success: false, error: 'Source directory does not exist' });
320 |                     return;
321 |                 }
322 | 
323 |                 const files = this.getFilesFromDirectory(
324 |                     args.sourceDirectory, 
325 |                     args.fileFilter || [], 
326 |                     args.recursive || false
327 |                 );
328 | 
329 |                 const importResults: any[] = [];
330 |                 let successCount = 0;
331 |                 let errorCount = 0;
332 | 
333 |                 for (const filePath of files) {
334 |                     try {
335 |                         const fileName = path.basename(filePath);
336 |                         const targetPath = `${args.targetDirectory}/${fileName}`;
337 |                         
338 |                         const result = await Editor.Message.request('asset-db', 'import-asset', 
339 |                             filePath, targetPath, { 
340 |                                 overwrite: args.overwrite || false,
341 |                                 rename: !(args.overwrite || false)
342 |                             });
343 |                         
344 |                         importResults.push({
345 |                             source: filePath,
346 |                             target: targetPath,
347 |                             success: true,
348 |                             uuid: result?.uuid
349 |                         });
350 |                         successCount++;
351 |                     } catch (err: any) {
352 |                         importResults.push({
353 |                             source: filePath,
354 |                             success: false,
355 |                             error: err.message
356 |                         });
357 |                         errorCount++;
358 |                     }
359 |                 }
360 | 
361 |                 resolve({
362 |                     success: true,
363 |                     data: {
364 |                         totalFiles: files.length,
365 |                         successCount: successCount,
366 |                         errorCount: errorCount,
367 |                         results: importResults,
368 |                         message: `Batch import completed: ${successCount} success, ${errorCount} errors`
369 |                     }
370 |                 });
371 |             } catch (err: any) {
372 |                 resolve({ success: false, error: err.message });
373 |             }
374 |         });
375 |     }
376 | 
377 |     private getFilesFromDirectory(dirPath: string, fileFilter: string[], recursive: boolean): string[] {
378 |         const fs = require('fs');
379 |         const path = require('path');
380 |         const files: string[] = [];
381 | 
382 |         const items = fs.readdirSync(dirPath);
383 |         
384 |         for (const item of items) {
385 |             const fullPath = path.join(dirPath, item);
386 |             const stat = fs.statSync(fullPath);
387 |             
388 |             if (stat.isFile()) {
389 |                 if (fileFilter.length === 0 || fileFilter.some(ext => item.toLowerCase().endsWith(ext.toLowerCase()))) {
390 |                     files.push(fullPath);
391 |                 }
392 |             } else if (stat.isDirectory() && recursive) {
393 |                 files.push(...this.getFilesFromDirectory(fullPath, fileFilter, recursive));
394 |             }
395 |         }
396 |         
397 |         return files;
398 |     }
399 | 
400 |     private async batchDeleteAssets(urls: string[]): Promise<ToolResponse> {
401 |         return new Promise(async (resolve) => {
402 |             try {
403 |                 const deleteResults: any[] = [];
404 |                 let successCount = 0;
405 |                 let errorCount = 0;
406 | 
407 |                 for (const url of urls) {
408 |                     try {
409 |                         await Editor.Message.request('asset-db', 'delete-asset', url);
410 |                         deleteResults.push({
411 |                             url: url,
412 |                             success: true
413 |                         });
414 |                         successCount++;
415 |                     } catch (err: any) {
416 |                         deleteResults.push({
417 |                             url: url,
418 |                             success: false,
419 |                             error: err.message
420 |                         });
421 |                         errorCount++;
422 |                     }
423 |                 }
424 | 
425 |                 resolve({
426 |                     success: true,
427 |                     data: {
428 |                         totalAssets: urls.length,
429 |                         successCount: successCount,
430 |                         errorCount: errorCount,
431 |                         results: deleteResults,
432 |                         message: `Batch delete completed: ${successCount} success, ${errorCount} errors`
433 |                     }
434 |                 });
435 |             } catch (err: any) {
436 |                 resolve({ success: false, error: err.message });
437 |             }
438 |         });
439 |     }
440 | 
441 |     private async validateAssetReferences(directory: string = 'db://assets'): Promise<ToolResponse> {
442 |         return new Promise(async (resolve) => {
443 |             try {
444 |                 // Get all assets in directory
445 |                 const assets = await Editor.Message.request('asset-db', 'query-assets', { pattern: `${directory}/**/*` });
446 |                 
447 |                 const brokenReferences: any[] = [];
448 |                 const validReferences: any[] = [];
449 | 
450 |                 for (const asset of assets) {
451 |                     try {
452 |                         const assetInfo = await Editor.Message.request('asset-db', 'query-asset-info', asset.url);
453 |                         if (assetInfo) {
454 |                             validReferences.push({
455 |                                 url: asset.url,
456 |                                 uuid: asset.uuid,
457 |                                 name: asset.name
458 |                             });
459 |                         }
460 |                     } catch (err) {
461 |                         brokenReferences.push({
462 |                             url: asset.url,
463 |                             uuid: asset.uuid,
464 |                             name: asset.name,
465 |                             error: (err as Error).message
466 |                         });
467 |                     }
468 |                 }
469 | 
470 |                 resolve({
471 |                     success: true,
472 |                     data: {
473 |                         directory: directory,
474 |                         totalAssets: assets.length,
475 |                         validReferences: validReferences.length,
476 |                         brokenReferences: brokenReferences.length,
477 |                         brokenAssets: brokenReferences,
478 |                         message: `Validation completed: ${brokenReferences.length} broken references found`
479 |                     }
480 |                 });
481 |             } catch (err: any) {
482 |                 resolve({ success: false, error: err.message });
483 |             }
484 |         });
485 |     }
486 | 
487 |     private async getAssetDependencies(urlOrUUID: string, direction: string = 'dependencies'): Promise<ToolResponse> {
488 |         return new Promise((resolve) => {
489 |             // Note: This would require scene analysis or additional APIs not available in current documentation
490 |             resolve({
491 |                 success: false,
492 |                 error: 'Asset dependency analysis requires additional APIs not available in current Cocos Creator MCP implementation. Consider using the Editor UI for dependency analysis.'
493 |             });
494 |         });
495 |     }
496 | 
497 |     private async getUnusedAssets(directory: string = 'db://assets', excludeDirectories: string[] = []): Promise<ToolResponse> {
498 |         return new Promise((resolve) => {
499 |             // Note: This would require comprehensive project analysis
500 |             resolve({
501 |                 success: false,
502 |                 error: 'Unused asset detection requires comprehensive project analysis not available in current Cocos Creator MCP implementation. Consider using the Editor UI or third-party tools for unused asset detection.'
503 |             });
504 |         });
505 |     }
506 | 
507 |     private async compressTextures(directory: string = 'db://assets', format: string = 'auto', quality: number = 0.8): Promise<ToolResponse> {
508 |         return new Promise((resolve) => {
509 |             // Note: Texture compression would require image processing APIs
510 |             resolve({
511 |                 success: false,
512 |                 error: 'Texture compression requires image processing capabilities not available in current Cocos Creator MCP implementation. Use the Editor\'s built-in texture compression settings or external tools.'
513 |             });
514 |         });
515 |     }
516 | 
517 |     private async exportAssetManifest(directory: string = 'db://assets', format: string = 'json', includeMetadata: boolean = true): Promise<ToolResponse> {
518 |         return new Promise(async (resolve) => {
519 |             try {
520 |                 const assets = await Editor.Message.request('asset-db', 'query-assets', { pattern: `${directory}/**/*` });
521 |                 
522 |                 const manifest: any[] = [];
523 | 
524 |                 for (const asset of assets) {
525 |                     const manifestEntry: any = {
526 |                         name: asset.name,
527 |                         url: asset.url,
528 |                         uuid: asset.uuid,
529 |                         type: asset.type,
530 |                         size: (asset as any).size || 0,
531 |                         isDirectory: asset.isDirectory || false
532 |                     };
533 | 
534 |                     if (includeMetadata) {
535 |                         try {
536 |                             const assetInfo = await Editor.Message.request('asset-db', 'query-asset-info', asset.url);
537 |                             if (assetInfo && assetInfo.meta) {
538 |                                 manifestEntry.meta = assetInfo.meta;
539 |                             }
540 |                         } catch (err) {
541 |                             // Skip metadata if not available
542 |                         }
543 |                     }
544 | 
545 |                     manifest.push(manifestEntry);
546 |                 }
547 | 
548 |                 let exportData: string;
549 |                 switch (format) {
550 |                     case 'json':
551 |                         exportData = JSON.stringify(manifest, null, 2);
552 |                         break;
553 |                     case 'csv':
554 |                         exportData = this.convertToCSV(manifest);
555 |                         break;
556 |                     case 'xml':
557 |                         exportData = this.convertToXML(manifest);
558 |                         break;
559 |                     default:
560 |                         exportData = JSON.stringify(manifest, null, 2);
561 |                 }
562 | 
563 |                 resolve({
564 |                     success: true,
565 |                     data: {
566 |                         directory: directory,
567 |                         format: format,
568 |                         assetCount: manifest.length,
569 |                         includeMetadata: includeMetadata,
570 |                         manifest: exportData,
571 |                         message: `Asset manifest exported with ${manifest.length} assets`
572 |                     }
573 |                 });
574 |             } catch (err: any) {
575 |                 resolve({ success: false, error: err.message });
576 |             }
577 |         });
578 |     }
579 | 
580 |     private convertToCSV(data: any[]): string {
581 |         if (data.length === 0) return '';
582 |         
583 |         const headers = Object.keys(data[0]);
584 |         const csvRows = [headers.join(',')];
585 |         
586 |         for (const row of data) {
587 |             const values = headers.map(header => {
588 |                 const value = row[header];
589 |                 return typeof value === 'object' ? JSON.stringify(value) : String(value);
590 |             });
591 |             csvRows.push(values.join(','));
592 |         }
593 |         
594 |         return csvRows.join('\n');
595 |     }
596 | 
597 |     private convertToXML(data: any[]): string {
598 |         let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<assets>\n';
599 |         
600 |         for (const item of data) {
601 |             xml += '  <asset>\n';
602 |             for (const [key, value] of Object.entries(item)) {
603 |                 const xmlValue = typeof value === 'object' ? 
604 |                     JSON.stringify(value) : 
605 |                     String(value).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
606 |                 xml += `    <${key}>${xmlValue}</${key}>\n`;
607 |             }
608 |             xml += '  </asset>\n';
609 |         }
610 |         
611 |         xml += '</assets>';
612 |         return xml;
613 |     }
614 | }
```

--------------------------------------------------------------------------------
/source/tools/scene-advanced-tools.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { ToolDefinition, ToolResponse, ToolExecutor } from '../types';
  2 | 
  3 | export class SceneAdvancedTools implements ToolExecutor {
  4 |     getTools(): ToolDefinition[] {
  5 |         return [
  6 |             {
  7 |                 name: 'reset_node_property',
  8 |                 description: 'Reset node property to default value',
  9 |                 inputSchema: {
 10 |                     type: 'object',
 11 |                     properties: {
 12 |                         uuid: {
 13 |                             type: 'string',
 14 |                             description: 'Node UUID'
 15 |                         },
 16 |                         path: {
 17 |                             type: 'string',
 18 |                             description: 'Property path (e.g., position, rotation, scale)'
 19 |                         }
 20 |                     },
 21 |                     required: ['uuid', 'path']
 22 |                 }
 23 |             },
 24 |             {
 25 |                 name: 'move_array_element',
 26 |                 description: 'Move array element position',
 27 |                 inputSchema: {
 28 |                     type: 'object',
 29 |                     properties: {
 30 |                         uuid: {
 31 |                             type: 'string',
 32 |                             description: 'Node UUID'
 33 |                         },
 34 |                         path: {
 35 |                             type: 'string',
 36 |                             description: 'Array property path (e.g., __comps__)'
 37 |                         },
 38 |                         target: {
 39 |                             type: 'number',
 40 |                             description: 'Target item original index'
 41 |                         },
 42 |                         offset: {
 43 |                             type: 'number',
 44 |                             description: 'Offset amount (positive or negative)'
 45 |                         }
 46 |                     },
 47 |                     required: ['uuid', 'path', 'target', 'offset']
 48 |                 }
 49 |             },
 50 |             {
 51 |                 name: 'remove_array_element',
 52 |                 description: 'Remove array element at specific index',
 53 |                 inputSchema: {
 54 |                     type: 'object',
 55 |                     properties: {
 56 |                         uuid: {
 57 |                             type: 'string',
 58 |                             description: 'Node UUID'
 59 |                         },
 60 |                         path: {
 61 |                             type: 'string',
 62 |                             description: 'Array property path'
 63 |                         },
 64 |                         index: {
 65 |                             type: 'number',
 66 |                             description: 'Target item index to remove'
 67 |                         }
 68 |                     },
 69 |                     required: ['uuid', 'path', 'index']
 70 |                 }
 71 |             },
 72 |             {
 73 |                 name: 'copy_node',
 74 |                 description: 'Copy node for later paste operation',
 75 |                 inputSchema: {
 76 |                     type: 'object',
 77 |                     properties: {
 78 |                         uuids: {
 79 |                             oneOf: [
 80 |                                 { type: 'string' },
 81 |                                 { type: 'array', items: { type: 'string' } }
 82 |                             ],
 83 |                             description: 'Node UUID or array of UUIDs to copy'
 84 |                         }
 85 |                     },
 86 |                     required: ['uuids']
 87 |                 }
 88 |             },
 89 |             {
 90 |                 name: 'paste_node',
 91 |                 description: 'Paste previously copied nodes',
 92 |                 inputSchema: {
 93 |                     type: 'object',
 94 |                     properties: {
 95 |                         target: {
 96 |                             type: 'string',
 97 |                             description: 'Target parent node UUID'
 98 |                         },
 99 |                         uuids: {
100 |                             oneOf: [
101 |                                 { type: 'string' },
102 |                                 { type: 'array', items: { type: 'string' } }
103 |                             ],
104 |                             description: 'Node UUIDs to paste'
105 |                         },
106 |                         keepWorldTransform: {
107 |                             type: 'boolean',
108 |                             description: 'Keep world transform coordinates',
109 |                             default: false
110 |                         }
111 |                     },
112 |                     required: ['target', 'uuids']
113 |                 }
114 |             },
115 |             {
116 |                 name: 'cut_node',
117 |                 description: 'Cut node (copy + mark for move)',
118 |                 inputSchema: {
119 |                     type: 'object',
120 |                     properties: {
121 |                         uuids: {
122 |                             oneOf: [
123 |                                 { type: 'string' },
124 |                                 { type: 'array', items: { type: 'string' } }
125 |                             ],
126 |                             description: 'Node UUID or array of UUIDs to cut'
127 |                         }
128 |                     },
129 |                     required: ['uuids']
130 |                 }
131 |             },
132 |             {
133 |                 name: 'reset_node_transform',
134 |                 description: 'Reset node position, rotation and scale',
135 |                 inputSchema: {
136 |                     type: 'object',
137 |                     properties: {
138 |                         uuid: {
139 |                             type: 'string',
140 |                             description: 'Node UUID'
141 |                         }
142 |                     },
143 |                     required: ['uuid']
144 |                 }
145 |             },
146 |             {
147 |                 name: 'reset_component',
148 |                 description: 'Reset component to default values',
149 |                 inputSchema: {
150 |                     type: 'object',
151 |                     properties: {
152 |                         uuid: {
153 |                             type: 'string',
154 |                             description: 'Component UUID'
155 |                         }
156 |                     },
157 |                     required: ['uuid']
158 |                 }
159 |             },
160 |             {
161 |                 name: 'restore_prefab',
162 |                 description: 'Restore prefab instance from asset',
163 |                 inputSchema: {
164 |                     type: 'object',
165 |                     properties: {
166 |                         nodeUuid: {
167 |                             type: 'string',
168 |                             description: 'Node UUID'
169 |                         },
170 |                         assetUuid: {
171 |                             type: 'string',
172 |                             description: 'Prefab asset UUID'
173 |                         }
174 |                     },
175 |                     required: ['nodeUuid', 'assetUuid']
176 |                 }
177 |             },
178 |             {
179 |                 name: 'execute_component_method',
180 |                 description: 'Execute method on component',
181 |                 inputSchema: {
182 |                     type: 'object',
183 |                     properties: {
184 |                         uuid: {
185 |                             type: 'string',
186 |                             description: 'Component UUID'
187 |                         },
188 |                         name: {
189 |                             type: 'string',
190 |                             description: 'Method name'
191 |                         },
192 |                         args: {
193 |                             type: 'array',
194 |                             description: 'Method arguments',
195 |                             default: []
196 |                         }
197 |                     },
198 |                     required: ['uuid', 'name']
199 |                 }
200 |             },
201 |             {
202 |                 name: 'execute_scene_script',
203 |                 description: 'Execute scene script method',
204 |                 inputSchema: {
205 |                     type: 'object',
206 |                     properties: {
207 |                         name: {
208 |                             type: 'string',
209 |                             description: 'Plugin name'
210 |                         },
211 |                         method: {
212 |                             type: 'string',
213 |                             description: 'Method name'
214 |                         },
215 |                         args: {
216 |                             type: 'array',
217 |                             description: 'Method arguments',
218 |                             default: []
219 |                         }
220 |                     },
221 |                     required: ['name', 'method']
222 |                 }
223 |             },
224 |             {
225 |                 name: 'scene_snapshot',
226 |                 description: 'Create scene state snapshot',
227 |                 inputSchema: {
228 |                     type: 'object',
229 |                     properties: {}
230 |                 }
231 |             },
232 |             {
233 |                 name: 'scene_snapshot_abort',
234 |                 description: 'Abort scene snapshot creation',
235 |                 inputSchema: {
236 |                     type: 'object',
237 |                     properties: {}
238 |                 }
239 |             },
240 |             {
241 |                 name: 'begin_undo_recording',
242 |                 description: 'Begin recording undo data',
243 |                 inputSchema: {
244 |                     type: 'object',
245 |                     properties: {
246 |                         nodeUuid: {
247 |                             type: 'string',
248 |                             description: 'Node UUID to record'
249 |                         }
250 |                     },
251 |                     required: ['nodeUuid']
252 |                 }
253 |             },
254 |             {
255 |                 name: 'end_undo_recording',
256 |                 description: 'End recording undo data',
257 |                 inputSchema: {
258 |                     type: 'object',
259 |                     properties: {
260 |                         undoId: {
261 |                             type: 'string',
262 |                             description: 'Undo recording ID from begin_undo_recording'
263 |                         }
264 |                     },
265 |                     required: ['undoId']
266 |                 }
267 |             },
268 |             {
269 |                 name: 'cancel_undo_recording',
270 |                 description: 'Cancel undo recording',
271 |                 inputSchema: {
272 |                     type: 'object',
273 |                     properties: {
274 |                         undoId: {
275 |                             type: 'string',
276 |                             description: 'Undo recording ID to cancel'
277 |                         }
278 |                     },
279 |                     required: ['undoId']
280 |                 }
281 |             },
282 |             {
283 |                 name: 'soft_reload_scene',
284 |                 description: 'Soft reload current scene',
285 |                 inputSchema: {
286 |                     type: 'object',
287 |                     properties: {}
288 |                 }
289 |             },
290 |             {
291 |                 name: 'query_scene_ready',
292 |                 description: 'Check if scene is ready',
293 |                 inputSchema: {
294 |                     type: 'object',
295 |                     properties: {}
296 |                 }
297 |             },
298 |             {
299 |                 name: 'query_scene_dirty',
300 |                 description: 'Check if scene has unsaved changes',
301 |                 inputSchema: {
302 |                     type: 'object',
303 |                     properties: {}
304 |                 }
305 |             },
306 |             {
307 |                 name: 'query_scene_classes',
308 |                 description: 'Query all registered classes',
309 |                 inputSchema: {
310 |                     type: 'object',
311 |                     properties: {
312 |                         extends: {
313 |                             type: 'string',
314 |                             description: 'Filter classes that extend this base class'
315 |                         }
316 |                     }
317 |                 }
318 |             },
319 |             {
320 |                 name: 'query_scene_components',
321 |                 description: 'Query available scene components',
322 |                 inputSchema: {
323 |                     type: 'object',
324 |                     properties: {}
325 |                 }
326 |             },
327 |             {
328 |                 name: 'query_component_has_script',
329 |                 description: 'Check if component has script',
330 |                 inputSchema: {
331 |                     type: 'object',
332 |                     properties: {
333 |                         className: {
334 |                             type: 'string',
335 |                             description: 'Script class name to check'
336 |                         }
337 |                     },
338 |                     required: ['className']
339 |                 }
340 |             },
341 |             {
342 |                 name: 'query_nodes_by_asset_uuid',
343 |                 description: 'Find nodes that use specific asset UUID',
344 |                 inputSchema: {
345 |                     type: 'object',
346 |                     properties: {
347 |                         assetUuid: {
348 |                             type: 'string',
349 |                             description: 'Asset UUID to search for'
350 |                         }
351 |                     },
352 |                     required: ['assetUuid']
353 |                 }
354 |             }
355 |         ];
356 |     }
357 | 
358 |     async execute(toolName: string, args: any): Promise<ToolResponse> {
359 |         switch (toolName) {
360 |             case 'reset_node_property':
361 |                 return await this.resetNodeProperty(args.uuid, args.path);
362 |             case 'move_array_element':
363 |                 return await this.moveArrayElement(args.uuid, args.path, args.target, args.offset);
364 |             case 'remove_array_element':
365 |                 return await this.removeArrayElement(args.uuid, args.path, args.index);
366 |             case 'copy_node':
367 |                 return await this.copyNode(args.uuids);
368 |             case 'paste_node':
369 |                 return await this.pasteNode(args.target, args.uuids, args.keepWorldTransform);
370 |             case 'cut_node':
371 |                 return await this.cutNode(args.uuids);
372 |             case 'reset_node_transform':
373 |                 return await this.resetNodeTransform(args.uuid);
374 |             case 'reset_component':
375 |                 return await this.resetComponent(args.uuid);
376 |             case 'restore_prefab':
377 |                 return await this.restorePrefab(args.nodeUuid, args.assetUuid);
378 |             case 'execute_component_method':
379 |                 return await this.executeComponentMethod(args.uuid, args.name, args.args);
380 |             case 'execute_scene_script':
381 |                 return await this.executeSceneScript(args.name, args.method, args.args);
382 |             case 'scene_snapshot':
383 |                 return await this.sceneSnapshot();
384 |             case 'scene_snapshot_abort':
385 |                 return await this.sceneSnapshotAbort();
386 |             case 'begin_undo_recording':
387 |                 return await this.beginUndoRecording(args.nodeUuid);
388 |             case 'end_undo_recording':
389 |                 return await this.endUndoRecording(args.undoId);
390 |             case 'cancel_undo_recording':
391 |                 return await this.cancelUndoRecording(args.undoId);
392 |             case 'soft_reload_scene':
393 |                 return await this.softReloadScene();
394 |             case 'query_scene_ready':
395 |                 return await this.querySceneReady();
396 |             case 'query_scene_dirty':
397 |                 return await this.querySceneDirty();
398 |             case 'query_scene_classes':
399 |                 return await this.querySceneClasses(args.extends);
400 |             case 'query_scene_components':
401 |                 return await this.querySceneComponents();
402 |             case 'query_component_has_script':
403 |                 return await this.queryComponentHasScript(args.className);
404 |             case 'query_nodes_by_asset_uuid':
405 |                 return await this.queryNodesByAssetUuid(args.assetUuid);
406 |             default:
407 |                 throw new Error(`Unknown tool: ${toolName}`);
408 |         }
409 |     }
410 | 
411 |     private async resetNodeProperty(uuid: string, path: string): Promise<ToolResponse> {
412 |         return new Promise((resolve) => {
413 |             Editor.Message.request('scene', 'reset-property', { 
414 |                 uuid, 
415 |                 path, 
416 |                 dump: { value: null } 
417 |             }).then(() => {
418 |                 resolve({
419 |                     success: true,
420 |                     message: `Property '${path}' reset to default value`
421 |                 });
422 |             }).catch((err: Error) => {
423 |                 resolve({ success: false, error: err.message });
424 |             });
425 |         });
426 |     }
427 | 
428 |     private async moveArrayElement(uuid: string, path: string, target: number, offset: number): Promise<ToolResponse> {
429 |         return new Promise((resolve) => {
430 |             Editor.Message.request('scene', 'move-array-element', {
431 |                 uuid,
432 |                 path,
433 |                 target,
434 |                 offset
435 |             }).then(() => {
436 |                 resolve({
437 |                     success: true,
438 |                     message: `Array element at index ${target} moved by ${offset}`
439 |                 });
440 |             }).catch((err: Error) => {
441 |                 resolve({ success: false, error: err.message });
442 |             });
443 |         });
444 |     }
445 | 
446 |     private async removeArrayElement(uuid: string, path: string, index: number): Promise<ToolResponse> {
447 |         return new Promise((resolve) => {
448 |             Editor.Message.request('scene', 'remove-array-element', {
449 |                 uuid,
450 |                 path,
451 |                 index
452 |             }).then(() => {
453 |                 resolve({
454 |                     success: true,
455 |                     message: `Array element at index ${index} removed`
456 |                 });
457 |             }).catch((err: Error) => {
458 |                 resolve({ success: false, error: err.message });
459 |             });
460 |         });
461 |     }
462 | 
463 |     private async copyNode(uuids: string | string[]): Promise<ToolResponse> {
464 |         return new Promise((resolve) => {
465 |             Editor.Message.request('scene', 'copy-node', uuids).then((result: string | string[]) => {
466 |                 resolve({
467 |                     success: true,
468 |                     data: {
469 |                         copiedUuids: result,
470 |                         message: 'Node(s) copied successfully'
471 |                     }
472 |                 });
473 |             }).catch((err: Error) => {
474 |                 resolve({ success: false, error: err.message });
475 |             });
476 |         });
477 |     }
478 | 
479 |     private async pasteNode(target: string, uuids: string | string[], keepWorldTransform: boolean = false): Promise<ToolResponse> {
480 |         return new Promise((resolve) => {
481 |             Editor.Message.request('scene', 'paste-node', {
482 |                 target,
483 |                 uuids,
484 |                 keepWorldTransform
485 |             }).then((result: string | string[]) => {
486 |                 resolve({
487 |                     success: true,
488 |                     data: {
489 |                         newUuids: result,
490 |                         message: 'Node(s) pasted successfully'
491 |                     }
492 |                 });
493 |             }).catch((err: Error) => {
494 |                 resolve({ success: false, error: err.message });
495 |             });
496 |         });
497 |     }
498 | 
499 |     private async cutNode(uuids: string | string[]): Promise<ToolResponse> {
500 |         return new Promise((resolve) => {
501 |             Editor.Message.request('scene', 'cut-node', uuids).then((result: any) => {
502 |                 resolve({
503 |                     success: true,
504 |                     data: {
505 |                         cutUuids: result,
506 |                         message: 'Node(s) cut successfully'
507 |                     }
508 |                 });
509 |             }).catch((err: Error) => {
510 |                 resolve({ success: false, error: err.message });
511 |             });
512 |         });
513 |     }
514 | 
515 |     private async resetNodeTransform(uuid: string): Promise<ToolResponse> {
516 |         return new Promise((resolve) => {
517 |             Editor.Message.request('scene', 'reset-node', { uuid }).then(() => {
518 |                 resolve({
519 |                     success: true,
520 |                     message: 'Node transform reset to default'
521 |                 });
522 |             }).catch((err: Error) => {
523 |                 resolve({ success: false, error: err.message });
524 |             });
525 |         });
526 |     }
527 | 
528 |     private async resetComponent(uuid: string): Promise<ToolResponse> {
529 |         return new Promise((resolve) => {
530 |             Editor.Message.request('scene', 'reset-component', { uuid }).then(() => {
531 |                 resolve({
532 |                     success: true,
533 |                     message: 'Component reset to default values'
534 |                 });
535 |             }).catch((err: Error) => {
536 |                 resolve({ success: false, error: err.message });
537 |             });
538 |         });
539 |     }
540 | 
541 |     private async restorePrefab(nodeUuid: string, assetUuid: string): Promise<ToolResponse> {
542 |         return new Promise((resolve) => {
543 |             (Editor.Message.request as any)('scene', 'restore-prefab', nodeUuid, assetUuid).then(() => {
544 |                 resolve({
545 |                     success: true,
546 |                     message: 'Prefab restored successfully'
547 |                 });
548 |             }).catch((err: Error) => {
549 |                 resolve({ success: false, error: err.message });
550 |             });
551 |         });
552 |     }
553 | 
554 |     private async executeComponentMethod(uuid: string, name: string, args: any[] = []): Promise<ToolResponse> {
555 |         return new Promise((resolve) => {
556 |             Editor.Message.request('scene', 'execute-component-method', {
557 |                 uuid,
558 |                 name,
559 |                 args
560 |             }).then((result: any) => {
561 |                 resolve({
562 |                     success: true,
563 |                     data: {
564 |                         result: result,
565 |                         message: `Method '${name}' executed successfully`
566 |                     }
567 |                 });
568 |             }).catch((err: Error) => {
569 |                 resolve({ success: false, error: err.message });
570 |             });
571 |         });
572 |     }
573 | 
574 |     private async executeSceneScript(name: string, method: string, args: any[] = []): Promise<ToolResponse> {
575 |         return new Promise((resolve) => {
576 |             Editor.Message.request('scene', 'execute-scene-script', {
577 |                 name,
578 |                 method,
579 |                 args
580 |             }).then((result: any) => {
581 |                 resolve({
582 |                     success: true,
583 |                     data: result
584 |                 });
585 |             }).catch((err: Error) => {
586 |                 resolve({ success: false, error: err.message });
587 |             });
588 |         });
589 |     }
590 | 
591 |     private async sceneSnapshot(): Promise<ToolResponse> {
592 |         return new Promise((resolve) => {
593 |             Editor.Message.request('scene', 'snapshot').then(() => {
594 |                 resolve({
595 |                     success: true,
596 |                     message: 'Scene snapshot created'
597 |                 });
598 |             }).catch((err: Error) => {
599 |                 resolve({ success: false, error: err.message });
600 |             });
601 |         });
602 |     }
603 | 
604 |     private async sceneSnapshotAbort(): Promise<ToolResponse> {
605 |         return new Promise((resolve) => {
606 |             Editor.Message.request('scene', 'snapshot-abort').then(() => {
607 |                 resolve({
608 |                     success: true,
609 |                     message: 'Scene snapshot aborted'
610 |                 });
611 |             }).catch((err: Error) => {
612 |                 resolve({ success: false, error: err.message });
613 |             });
614 |         });
615 |     }
616 | 
617 |     private async beginUndoRecording(nodeUuid: string): Promise<ToolResponse> {
618 |         return new Promise((resolve) => {
619 |             Editor.Message.request('scene', 'begin-recording', nodeUuid).then((undoId: string) => {
620 |                 resolve({
621 |                     success: true,
622 |                     data: {
623 |                         undoId: undoId,
624 |                         message: 'Undo recording started'
625 |                     }
626 |                 });
627 |             }).catch((err: Error) => {
628 |                 resolve({ success: false, error: err.message });
629 |             });
630 |         });
631 |     }
632 | 
633 |     private async endUndoRecording(undoId: string): Promise<ToolResponse> {
634 |         return new Promise((resolve) => {
635 |             Editor.Message.request('scene', 'end-recording', undoId).then(() => {
636 |                 resolve({
637 |                     success: true,
638 |                     message: 'Undo recording ended'
639 |                 });
640 |             }).catch((err: Error) => {
641 |                 resolve({ success: false, error: err.message });
642 |             });
643 |         });
644 |     }
645 | 
646 |     private async cancelUndoRecording(undoId: string): Promise<ToolResponse> {
647 |         return new Promise((resolve) => {
648 |             Editor.Message.request('scene', 'cancel-recording', undoId).then(() => {
649 |                 resolve({
650 |                     success: true,
651 |                     message: 'Undo recording cancelled'
652 |                 });
653 |             }).catch((err: Error) => {
654 |                 resolve({ success: false, error: err.message });
655 |             });
656 |         });
657 |     }
658 | 
659 |     private async softReloadScene(): Promise<ToolResponse> {
660 |         return new Promise((resolve) => {
661 |             Editor.Message.request('scene', 'soft-reload').then(() => {
662 |                 resolve({
663 |                     success: true,
664 |                     message: 'Scene soft reloaded successfully'
665 |                 });
666 |             }).catch((err: Error) => {
667 |                 resolve({ success: false, error: err.message });
668 |             });
669 |         });
670 |     }
671 | 
672 |     private async querySceneReady(): Promise<ToolResponse> {
673 |         return new Promise((resolve) => {
674 |             Editor.Message.request('scene', 'query-is-ready').then((ready: boolean) => {
675 |                 resolve({
676 |                     success: true,
677 |                     data: {
678 |                         ready: ready,
679 |                         message: ready ? 'Scene is ready' : 'Scene is not ready'
680 |                     }
681 |                 });
682 |             }).catch((err: Error) => {
683 |                 resolve({ success: false, error: err.message });
684 |             });
685 |         });
686 |     }
687 | 
688 |     private async querySceneDirty(): Promise<ToolResponse> {
689 |         return new Promise((resolve) => {
690 |             Editor.Message.request('scene', 'query-dirty').then((dirty: boolean) => {
691 |                 resolve({
692 |                     success: true,
693 |                     data: {
694 |                         dirty: dirty,
695 |                         message: dirty ? 'Scene has unsaved changes' : 'Scene is clean'
696 |                     }
697 |                 });
698 |             }).catch((err: Error) => {
699 |                 resolve({ success: false, error: err.message });
700 |             });
701 |         });
702 |     }
703 | 
704 |     private async querySceneClasses(extendsClass?: string): Promise<ToolResponse> {
705 |         return new Promise((resolve) => {
706 |             const options: any = {};
707 |             if (extendsClass) {
708 |                 options.extends = extendsClass;
709 |             }
710 | 
711 |             Editor.Message.request('scene', 'query-classes', options).then((classes: any[]) => {
712 |                 resolve({
713 |                     success: true,
714 |                     data: {
715 |                         classes: classes,
716 |                         count: classes.length,
717 |                         extendsFilter: extendsClass
718 |                     }
719 |                 });
720 |             }).catch((err: Error) => {
721 |                 resolve({ success: false, error: err.message });
722 |             });
723 |         });
724 |     }
725 | 
726 |     private async querySceneComponents(): Promise<ToolResponse> {
727 |         return new Promise((resolve) => {
728 |             Editor.Message.request('scene', 'query-components').then((components: any[]) => {
729 |                 resolve({
730 |                     success: true,
731 |                     data: {
732 |                         components: components,
733 |                         count: components.length
734 |                     }
735 |                 });
736 |             }).catch((err: Error) => {
737 |                 resolve({ success: false, error: err.message });
738 |             });
739 |         });
740 |     }
741 | 
742 |     private async queryComponentHasScript(className: string): Promise<ToolResponse> {
743 |         return new Promise((resolve) => {
744 |             Editor.Message.request('scene', 'query-component-has-script', className).then((hasScript: boolean) => {
745 |                 resolve({
746 |                     success: true,
747 |                     data: {
748 |                         className: className,
749 |                         hasScript: hasScript,
750 |                         message: hasScript ? `Component '${className}' has script` : `Component '${className}' does not have script`
751 |                     }
752 |                 });
753 |             }).catch((err: Error) => {
754 |                 resolve({ success: false, error: err.message });
755 |             });
756 |         });
757 |     }
758 | 
759 |     private async queryNodesByAssetUuid(assetUuid: string): Promise<ToolResponse> {
760 |         return new Promise((resolve) => {
761 |             Editor.Message.request('scene', 'query-nodes-by-asset-uuid', assetUuid).then((nodeUuids: string[]) => {
762 |                 resolve({
763 |                     success: true,
764 |                     data: {
765 |                         assetUuid: assetUuid,
766 |                         nodeUuids: nodeUuids,
767 |                         count: nodeUuids.length,
768 |                         message: `Found ${nodeUuids.length} nodes using asset`
769 |                     }
770 |                 });
771 |             }).catch((err: Error) => {
772 |                 resolve({ success: false, error: err.message });
773 |             });
774 |         });
775 |     }
776 | }
```
Page 2/5FirstPrevNextLast