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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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