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;
}
}