更新大量官方api工具,目前已支持超过150个工具调用,支持除了预制体操作之外的所有cocos 操作。同时mcp端口更健壮,ai使用起来准确率更高。修复大量bug
This commit is contained in:
308
source/examples/prefab-instantiation-example.ts
Normal file
308
source/examples/prefab-instantiation-example.ts
Normal 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();
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
223
source/test/prefab-tools-test.ts
Normal file
223
source/test/prefab-tools-test.ts
Normal 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();
|
||||
}
|
||||
614
source/tools/asset-advanced-tools.ts
Normal file
614
source/tools/asset-advanced-tools.ts
Normal 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, '&').replace(/</g, '<').replace(/>/g, '>');
|
||||
xml += ` <${key}>${xmlValue}</${key}>\n`;
|
||||
}
|
||||
xml += ' </asset>\n';
|
||||
}
|
||||
|
||||
xml += '</assets>';
|
||||
return xml;
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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]}`;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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.'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
399
source/tools/reference-image-tools.ts
Normal file
399
source/tools/reference-image-tools.ts
Normal 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 });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
776
source/tools/scene-advanced-tools.ts
Normal file
776
source/tools/scene-advanced-tools.ts
Normal 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 });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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[]) => {
|
||||
|
||||
628
source/tools/scene-view-tools.ts
Normal file
628
source/tools/scene-view-tools.ts
Normal 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}`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user