import { ToolDefinition, ToolResponse, ToolExecutor, ProjectInfo, AssetInfo } from '../types'; import * as fs from 'fs'; import * as path from 'path'; export class ProjectTools implements ToolExecutor { getTools(): ToolDefinition[] { return [ { name: 'run_project', description: 'Run the project in preview mode', inputSchema: { type: 'object', properties: { platform: { type: 'string', description: 'Target platform', enum: ['browser', 'simulator', 'preview'], default: 'browser' } } } }, { name: 'build_project', description: 'Build the project', inputSchema: { type: 'object', properties: { platform: { type: 'string', description: 'Build platform', enum: ['web-mobile', 'web-desktop', 'ios', 'android', 'windows', 'mac'] }, debug: { type: 'boolean', description: 'Debug build', default: true } }, required: ['platform'] } }, { name: 'get_project_info', description: 'Get project information', inputSchema: { type: 'object', properties: {} } }, { name: 'get_project_settings', description: 'Get project settings', inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'Settings category', enum: ['general', 'physics', 'render', 'assets'], default: 'general' } } } }, { name: 'refresh_assets', description: 'Refresh asset database', inputSchema: { type: 'object', properties: { folder: { type: 'string', description: 'Specific folder to refresh (optional)' } } } }, { name: 'import_asset', description: 'Import an asset file', inputSchema: { type: 'object', properties: { sourcePath: { type: 'string', description: 'Source file path' }, targetFolder: { type: 'string', description: 'Target folder in assets' } }, required: ['sourcePath', 'targetFolder'] } }, { name: 'get_asset_info', description: 'Get asset information', inputSchema: { type: 'object', properties: { assetPath: { type: 'string', description: 'Asset path (db://assets/...)' } }, required: ['assetPath'] } }, { name: 'get_assets', description: 'Get assets by type', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Asset type filter', enum: ['all', 'scene', 'prefab', 'script', 'texture', 'material', 'mesh', 'audio', 'animation'], default: 'all' }, folder: { type: 'string', description: 'Folder to search in', default: 'db://assets' } } } }, { name: 'get_build_settings', description: 'Get build settings - shows current limitations', inputSchema: { type: 'object', properties: {} } }, { name: 'open_build_panel', description: 'Open the build panel in the editor', inputSchema: { type: 'object', properties: {} } }, { name: 'check_builder_status', description: 'Check if builder worker is ready', inputSchema: { type: 'object', properties: {} } }, { name: 'start_preview_server', description: 'Start preview server', inputSchema: { type: 'object', properties: { port: { type: 'number', description: 'Preview server port', default: 7456 } } } }, { name: 'stop_preview_server', description: 'Stop preview server', inputSchema: { type: 'object', properties: {} } }, { name: 'create_asset', description: 'Create a new asset file or folder', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'Asset URL (e.g., db://assets/newfile.json)' }, content: { type: 'string', description: 'File content (null for folder)', default: null }, overwrite: { type: 'boolean', description: 'Overwrite existing file', default: false } }, required: ['url'] } }, { name: 'copy_asset', description: 'Copy an asset to another location', inputSchema: { type: 'object', properties: { source: { type: 'string', description: 'Source asset URL' }, target: { type: 'string', description: 'Target location URL' }, overwrite: { type: 'boolean', description: 'Overwrite existing file', default: false } }, required: ['source', 'target'] } }, { name: 'move_asset', description: 'Move an asset to another location', inputSchema: { type: 'object', properties: { source: { type: 'string', description: 'Source asset URL' }, target: { type: 'string', description: 'Target location URL' }, overwrite: { type: 'boolean', description: 'Overwrite existing file', default: false } }, required: ['source', 'target'] } }, { name: 'delete_asset', description: 'Delete an asset', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'Asset URL to delete' } }, required: ['url'] } }, { name: 'save_asset', description: 'Save asset content', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'Asset URL' }, content: { type: 'string', description: 'Asset content' } }, required: ['url', 'content'] } }, { name: 'reimport_asset', description: 'Reimport an asset', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'Asset URL to reimport' } }, required: ['url'] } }, { name: 'query_asset_path', description: 'Get asset disk path', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'Asset URL' } }, required: ['url'] } }, { name: 'query_asset_uuid', description: 'Get asset UUID from URL', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'Asset URL' } }, required: ['url'] } }, { name: 'query_asset_url', description: 'Get asset URL from UUID', inputSchema: { type: 'object', properties: { uuid: { type: 'string', description: 'Asset UUID' } }, required: ['uuid'] } } ]; } async execute(toolName: string, args: any): Promise { switch (toolName) { case 'run_project': return await this.runProject(args.platform); case 'build_project': return await this.buildProject(args); case 'get_project_info': return await this.getProjectInfo(); case 'get_project_settings': return await this.getProjectSettings(args.category); case 'refresh_assets': return await this.refreshAssets(args.folder); case 'import_asset': return await this.importAsset(args.sourcePath, args.targetFolder); case 'get_asset_info': return await this.getAssetInfo(args.assetPath); case 'get_assets': return await this.getAssets(args.type, args.folder); case 'get_build_settings': return await this.getBuildSettings(); case 'open_build_panel': return await this.openBuildPanel(); case 'check_builder_status': return await this.checkBuilderStatus(); case 'start_preview_server': return await this.startPreviewServer(args.port); case 'stop_preview_server': return await this.stopPreviewServer(); case 'create_asset': return await this.createAsset(args.url, args.content, args.overwrite); case 'copy_asset': return await this.copyAsset(args.source, args.target, args.overwrite); case 'move_asset': return await this.moveAsset(args.source, args.target, args.overwrite); case 'delete_asset': return await this.deleteAsset(args.url); case 'save_asset': return await this.saveAsset(args.url, args.content); case 'reimport_asset': return await this.reimportAsset(args.url); case 'query_asset_path': return await this.queryAssetPath(args.url); case 'query_asset_uuid': return await this.queryAssetUuid(args.url); case 'query_asset_url': return await this.queryAssetUrl(args.uuid); default: throw new Error(`Unknown tool: ${toolName}`); } } private async runProject(platform: string = 'browser'): Promise { return new Promise((resolve) => { const previewConfig = { platform: platform, scenes: [] // Will use current scene }; // 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: `Build panel opened. Preview functionality requires manual setup.` }); }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async buildProject(args: any): Promise { return new Promise((resolve) => { const buildOptions = { platform: args.platform, debug: args.debug !== false, sourceMaps: args.debug !== false, buildPath: `build/${args.platform}` }; // 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: `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 }); }); }); } private async getProjectInfo(): Promise { return new Promise((resolve) => { const info: ProjectInfo = { name: Editor.Project.name, path: Editor.Project.path, uuid: Editor.Project.uuid, version: (Editor.Project as any).version || '1.0.0', cocosVersion: (Editor as any).versions?.cocos || 'Unknown' }; // 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 resolve({ success: true, data: info }); }); }); } private async getProjectSettings(category: string = 'general'): Promise { return new Promise((resolve) => { // 使用正确的 project API 查询项目配置 const configMap: Record = { general: 'project', physics: 'physics', render: 'render', assets: 'asset-db' }; const configName = configMap[category] || 'project'; Editor.Message.request('project', 'query-config', configName).then((settings: any) => { resolve({ success: true, data: { category: category, config: settings, message: `${category} settings retrieved successfully` } }); }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async refreshAssets(folder?: string): Promise { return new Promise((resolve) => { // 使用正确的 asset-db API 刷新资源 const targetPath = folder || 'db://assets'; Editor.Message.request('asset-db', 'refresh-asset', targetPath).then(() => { resolve({ success: true, message: `Assets refreshed in: ${targetPath}` }); }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async importAsset(sourcePath: string, targetFolder: string): Promise { return new Promise((resolve) => { if (!fs.existsSync(sourcePath)) { resolve({ success: false, error: 'Source file not found' }); return; } const fileName = path.basename(sourcePath); const targetPath = targetFolder.startsWith('db://') ? targetFolder : `db://assets/${targetFolder}`; Editor.Message.request('asset-db', 'import-asset', sourcePath, `${targetPath}/${fileName}`).then((result: any) => { resolve({ success: true, data: { uuid: result.uuid, path: result.url, message: `Asset imported: ${fileName}` } }); }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async getAssetInfo(assetPath: string): Promise { return new Promise((resolve) => { Editor.Message.request('asset-db', 'query-asset-info', assetPath).then((assetInfo: any) => { if (!assetInfo) { throw new Error('Asset not found'); } const info: AssetInfo = { name: assetInfo.name, uuid: assetInfo.uuid, path: assetInfo.url, type: assetInfo.type, size: assetInfo.size, isDirectory: assetInfo.isDirectory }; if (assetInfo.meta) { info.meta = { ver: assetInfo.meta.ver, importer: assetInfo.meta.importer }; } resolve({ success: true, data: info }); }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async getAssets(type: string = 'all', folder: string = 'db://assets'): Promise { return new Promise((resolve) => { let pattern = `${folder}/**/*`; // 添加类型过滤 if (type !== 'all') { const typeExtensions: Record = { 'scene': '.scene', 'prefab': '.prefab', 'script': '.{ts,js}', 'texture': '.{png,jpg,jpeg,gif,tga,bmp,psd}', 'material': '.mtl', 'mesh': '.{fbx,obj,dae}', 'audio': '.{mp3,ogg,wav,m4a}', 'animation': '.{anim,clip}' }; const extension = typeExtensions[type]; if (extension) { pattern = `${folder}/**/*${extension}`; } } // 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, path: asset.url, type: asset.type, size: asset.size || 0, isDirectory: asset.isDirectory || false })); resolve({ success: true, data: { type: type, folder: folder, count: assets.length, assets: assets } }); }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async getBuildSettings(): Promise { return new Promise((resolve) => { // 检查构建器是否准备就绪 Editor.Message.request('builder', 'query-worker-ready').then((ready: boolean) => { resolve({ success: true, data: { builderReady: ready, message: 'Build settings are limited in MCP plugin environment', availableActions: [ 'Open build panel with open_build_panel', 'Check builder status with check_builder_status', 'Start preview server with start_preview_server', 'Stop preview server with stop_preview_server' ], limitation: 'Full build configuration requires direct Editor UI access' } }); }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async openBuildPanel(): Promise { return new Promise((resolve) => { Editor.Message.request('builder', 'open').then(() => { resolve({ success: true, message: 'Build panel opened successfully' }); }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async checkBuilderStatus(): Promise { return new Promise((resolve) => { Editor.Message.request('builder', 'query-worker-ready').then((ready: boolean) => { resolve({ success: true, data: { ready: ready, status: ready ? 'Builder worker is ready' : 'Builder worker is not ready', message: 'Builder status checked successfully' } }); }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async startPreviewServer(port: number = 7456): Promise { return new Promise((resolve) => { resolve({ success: false, error: 'Preview server control is not supported through MCP API', instruction: 'Please start the preview server manually using the editor menu: Project > Preview, or use the preview panel in the editor' }); }); } private async stopPreviewServer(): Promise { return new Promise((resolve) => { resolve({ success: false, error: 'Preview server control is not supported through MCP API', instruction: 'Please stop the preview server manually using the preview panel in the editor' }); }); } private async createAsset(url: string, content: string | null = null, overwrite: boolean = false): Promise { return new Promise((resolve) => { const options = { overwrite: overwrite, rename: !overwrite }; Editor.Message.request('asset-db', 'create-asset', url, content, options).then((result: any) => { if (result && result.uuid) { resolve({ success: true, data: { uuid: result.uuid, url: result.url, message: content === null ? 'Folder created successfully' : 'File created successfully' } }); } else { resolve({ success: true, data: { url: url, message: content === null ? 'Folder created successfully' : 'File created successfully' } }); } }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async copyAsset(source: string, target: string, overwrite: boolean = false): Promise { return new Promise((resolve) => { const options = { overwrite: overwrite, rename: !overwrite }; Editor.Message.request('asset-db', 'copy-asset', source, target, options).then((result: any) => { if (result && result.uuid) { resolve({ success: true, data: { uuid: result.uuid, url: result.url, message: 'Asset copied successfully' } }); } else { resolve({ success: true, data: { source: source, target: target, message: 'Asset copied successfully' } }); } }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async moveAsset(source: string, target: string, overwrite: boolean = false): Promise { return new Promise((resolve) => { const options = { overwrite: overwrite, rename: !overwrite }; Editor.Message.request('asset-db', 'move-asset', source, target, options).then((result: any) => { if (result && result.uuid) { resolve({ success: true, data: { uuid: result.uuid, url: result.url, message: 'Asset moved successfully' } }); } else { resolve({ success: true, data: { source: source, target: target, message: 'Asset moved successfully' } }); } }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async deleteAsset(url: string): Promise { return new Promise((resolve) => { Editor.Message.request('asset-db', 'delete-asset', url).then((result: any) => { resolve({ success: true, data: { url: url, message: 'Asset deleted successfully' } }); }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async saveAsset(url: string, content: string): Promise { return new Promise((resolve) => { Editor.Message.request('asset-db', 'save-asset', url, content).then((result: any) => { if (result && result.uuid) { resolve({ success: true, data: { uuid: result.uuid, url: result.url, message: 'Asset saved successfully' } }); } else { resolve({ success: true, data: { url: url, message: 'Asset saved successfully' } }); } }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async reimportAsset(url: string): Promise { return new Promise((resolve) => { Editor.Message.request('asset-db', 'reimport-asset', url).then(() => { resolve({ success: true, data: { url: url, message: 'Asset reimported successfully' } }); }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async queryAssetPath(url: string): Promise { return new Promise((resolve) => { Editor.Message.request('asset-db', 'query-path', url).then((path: string | null) => { if (path) { resolve({ success: true, data: { url: url, path: path, message: 'Asset path retrieved successfully' } }); } else { resolve({ success: false, error: 'Asset path not found' }); } }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async queryAssetUuid(url: string): Promise { return new Promise((resolve) => { Editor.Message.request('asset-db', 'query-uuid', url).then((uuid: string | null) => { if (uuid) { resolve({ success: true, data: { url: url, uuid: uuid, message: 'Asset UUID retrieved successfully' } }); } else { resolve({ success: false, error: 'Asset UUID not found' }); } }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } private async queryAssetUrl(uuid: string): Promise { return new Promise((resolve) => { Editor.Message.request('asset-db', 'query-url', uuid).then((url: string | null) => { if (url) { resolve({ success: true, data: { uuid: uuid, url: url, message: 'Asset URL retrieved successfully' } }); } else { resolve({ success: false, error: 'Asset URL not found' }); } }).catch((err: Error) => { resolve({ success: false, error: err.message }); }); }); } }