初次提交
This commit is contained in:
264
source/tools/broadcast-tools.ts
Normal file
264
source/tools/broadcast-tools.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
import { ToolDefinition, ToolResponse, ToolExecutor } from '../types';
|
||||
|
||||
export class BroadcastTools implements ToolExecutor {
|
||||
private listeners: Map<string, Function[]> = new Map();
|
||||
private messageLog: Array<{ message: string; data: any; timestamp: number }> = [];
|
||||
|
||||
constructor() {
|
||||
this.setupBroadcastListeners();
|
||||
}
|
||||
|
||||
getTools(): ToolDefinition[] {
|
||||
return [
|
||||
{
|
||||
name: 'get_broadcast_log',
|
||||
description: 'Get recent broadcast messages log',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Number of recent messages to return',
|
||||
default: 50
|
||||
},
|
||||
messageType: {
|
||||
type: 'string',
|
||||
description: 'Filter by message type (optional)'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'listen_broadcast',
|
||||
description: 'Start listening for specific broadcast messages',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
messageType: {
|
||||
type: 'string',
|
||||
description: 'Message type to listen for'
|
||||
}
|
||||
},
|
||||
required: ['messageType']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'stop_listening',
|
||||
description: 'Stop listening for specific broadcast messages',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
messageType: {
|
||||
type: 'string',
|
||||
description: 'Message type to stop listening for'
|
||||
}
|
||||
},
|
||||
required: ['messageType']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'clear_broadcast_log',
|
||||
description: 'Clear the broadcast messages log',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_active_listeners',
|
||||
description: 'Get list of active broadcast listeners',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async execute(toolName: string, args: any): Promise<ToolResponse> {
|
||||
switch (toolName) {
|
||||
case 'get_broadcast_log':
|
||||
return await this.getBroadcastLog(args.limit, args.messageType);
|
||||
case 'listen_broadcast':
|
||||
return await this.listenBroadcast(args.messageType);
|
||||
case 'stop_listening':
|
||||
return await this.stopListening(args.messageType);
|
||||
case 'clear_broadcast_log':
|
||||
return await this.clearBroadcastLog();
|
||||
case 'get_active_listeners':
|
||||
return await this.getActiveListeners();
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${toolName}`);
|
||||
}
|
||||
}
|
||||
|
||||
private setupBroadcastListeners(): void {
|
||||
// 设置预定义的重要广播消息监听
|
||||
const importantMessages = [
|
||||
'build-worker:ready',
|
||||
'build-worker:closed',
|
||||
'scene:ready',
|
||||
'scene:close',
|
||||
'scene:light-probe-edit-mode-changed',
|
||||
'scene:light-probe-bounding-box-edit-mode-changed',
|
||||
'asset-db:ready',
|
||||
'asset-db:close',
|
||||
'asset-db:asset-add',
|
||||
'asset-db:asset-change',
|
||||
'asset-db:asset-delete'
|
||||
];
|
||||
|
||||
importantMessages.forEach(messageType => {
|
||||
this.addBroadcastListener(messageType);
|
||||
});
|
||||
}
|
||||
|
||||
private addBroadcastListener(messageType: string): void {
|
||||
const listener = (data: any) => {
|
||||
this.messageLog.push({
|
||||
message: messageType,
|
||||
data: data,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// 保持日志大小在合理范围内
|
||||
if (this.messageLog.length > 1000) {
|
||||
this.messageLog = this.messageLog.slice(-500);
|
||||
}
|
||||
|
||||
console.log(`[Broadcast] ${messageType}:`, data);
|
||||
};
|
||||
|
||||
if (!this.listeners.has(messageType)) {
|
||||
this.listeners.set(messageType, []);
|
||||
}
|
||||
this.listeners.get(messageType)!.push(listener);
|
||||
|
||||
// 注册 Editor 消息监听 - 暂时注释掉,Editor.Message API可能不支持
|
||||
// Editor.Message.on(messageType, listener);
|
||||
console.log(`[BroadcastTools] Added listener for ${messageType} (simulated)`);
|
||||
}
|
||||
|
||||
private removeBroadcastListener(messageType: string): void {
|
||||
const listeners = this.listeners.get(messageType);
|
||||
if (listeners) {
|
||||
listeners.forEach(listener => {
|
||||
// Editor.Message.off(messageType, listener);
|
||||
console.log(`[BroadcastTools] Removed listener for ${messageType} (simulated)`);
|
||||
});
|
||||
this.listeners.delete(messageType);
|
||||
}
|
||||
}
|
||||
|
||||
private async getBroadcastLog(limit: number = 50, messageType?: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
let filteredLog = this.messageLog;
|
||||
|
||||
if (messageType) {
|
||||
filteredLog = this.messageLog.filter(entry => entry.message === messageType);
|
||||
}
|
||||
|
||||
const recentLog = filteredLog.slice(-limit).map(entry => ({
|
||||
...entry,
|
||||
timestamp: new Date(entry.timestamp).toISOString()
|
||||
}));
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
log: recentLog,
|
||||
count: recentLog.length,
|
||||
totalCount: filteredLog.length,
|
||||
filter: messageType || 'all',
|
||||
message: 'Broadcast log retrieved successfully'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async listenBroadcast(messageType: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
if (!this.listeners.has(messageType)) {
|
||||
this.addBroadcastListener(messageType);
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
messageType: messageType,
|
||||
message: `Started listening for broadcast: ${messageType}`
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
messageType: messageType,
|
||||
message: `Already listening for broadcast: ${messageType}`
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
resolve({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async stopListening(messageType: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
if (this.listeners.has(messageType)) {
|
||||
this.removeBroadcastListener(messageType);
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
messageType: messageType,
|
||||
message: `Stopped listening for broadcast: ${messageType}`
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
messageType: messageType,
|
||||
message: `Was not listening for broadcast: ${messageType}`
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
resolve({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async clearBroadcastLog(): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
const previousCount = this.messageLog.length;
|
||||
this.messageLog = [];
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
clearedCount: previousCount,
|
||||
message: 'Broadcast log cleared successfully'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getActiveListeners(): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
const activeListeners = Array.from(this.listeners.keys()).map(messageType => ({
|
||||
messageType: messageType,
|
||||
listenerCount: this.listeners.get(messageType)?.length || 0
|
||||
}));
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
listeners: activeListeners,
|
||||
count: activeListeners.length,
|
||||
message: 'Active listeners retrieved successfully'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
445
source/tools/component-tools.ts
Normal file
445
source/tools/component-tools.ts
Normal file
@@ -0,0 +1,445 @@
|
||||
import { ToolDefinition, ToolResponse, ToolExecutor, ComponentInfo } from '../types';
|
||||
|
||||
export class ComponentTools implements ToolExecutor {
|
||||
getTools(): ToolDefinition[] {
|
||||
return [
|
||||
{
|
||||
name: 'add_component',
|
||||
description: 'Add a component to a specific node. The component will be added to the exact node specified by nodeUuid.',
|
||||
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.'
|
||||
},
|
||||
componentType: {
|
||||
type: 'string',
|
||||
description: 'Component type (e.g., cc.Sprite, cc.Label, cc.Button)'
|
||||
}
|
||||
},
|
||||
required: ['nodeUuid', 'componentType']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'remove_component',
|
||||
description: 'Remove a component from a node',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
nodeUuid: {
|
||||
type: 'string',
|
||||
description: 'Node UUID'
|
||||
},
|
||||
componentType: {
|
||||
type: 'string',
|
||||
description: 'Component type to remove'
|
||||
}
|
||||
},
|
||||
required: ['nodeUuid', 'componentType']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_components',
|
||||
description: 'Get all components of a node',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
nodeUuid: {
|
||||
type: 'string',
|
||||
description: 'Node UUID'
|
||||
}
|
||||
},
|
||||
required: ['nodeUuid']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_component_info',
|
||||
description: 'Get specific component information',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
nodeUuid: {
|
||||
type: 'string',
|
||||
description: 'Node UUID'
|
||||
},
|
||||
componentType: {
|
||||
type: 'string',
|
||||
description: 'Component type to get info for'
|
||||
}
|
||||
},
|
||||
required: ['nodeUuid', 'componentType']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'set_component_property',
|
||||
description: 'Set component property value',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
nodeUuid: {
|
||||
type: 'string',
|
||||
description: 'Node UUID'
|
||||
},
|
||||
componentType: {
|
||||
type: 'string',
|
||||
description: 'Component type'
|
||||
},
|
||||
property: {
|
||||
type: 'string',
|
||||
description: 'Property name'
|
||||
},
|
||||
value: {
|
||||
description: 'Property value'
|
||||
}
|
||||
},
|
||||
required: ['nodeUuid', 'componentType', 'property', 'value']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'attach_script',
|
||||
description: 'Attach a script component to a node',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
nodeUuid: {
|
||||
type: 'string',
|
||||
description: 'Node UUID'
|
||||
},
|
||||
scriptPath: {
|
||||
type: 'string',
|
||||
description: 'Script asset path (e.g., db://assets/scripts/MyScript.ts)'
|
||||
}
|
||||
},
|
||||
required: ['nodeUuid', 'scriptPath']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_available_components',
|
||||
description: 'Get list of available component types',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
category: {
|
||||
type: 'string',
|
||||
description: 'Component category filter',
|
||||
enum: ['all', 'renderer', 'ui', 'physics', 'animation', 'audio'],
|
||||
default: 'all'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async execute(toolName: string, args: any): Promise<ToolResponse> {
|
||||
switch (toolName) {
|
||||
case 'add_component':
|
||||
return await this.addComponent(args.nodeUuid, args.componentType);
|
||||
case 'remove_component':
|
||||
return await this.removeComponent(args.nodeUuid, args.componentType);
|
||||
case 'get_components':
|
||||
return await this.getComponents(args.nodeUuid);
|
||||
case 'get_component_info':
|
||||
return await this.getComponentInfo(args.nodeUuid, args.componentType);
|
||||
case 'set_component_property':
|
||||
return await this.setComponentProperty(args);
|
||||
case 'attach_script':
|
||||
return await this.attachScript(args.nodeUuid, args.scriptPath);
|
||||
case 'get_available_components':
|
||||
return await this.getAvailableComponents(args.category);
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${toolName}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async addComponent(nodeUuid: string, componentType: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 尝试直接使用 Editor API 添加组件
|
||||
Editor.Message.request('scene', 'create-component', {
|
||||
uuid: nodeUuid,
|
||||
component: componentType
|
||||
}).then((result: any) => {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
componentId: result,
|
||||
message: `Component '${componentType}' added successfully`
|
||||
}
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
// 备用方案:使用场景脚本
|
||||
const options = {
|
||||
name: 'cocos-mcp-server',
|
||||
method: 'addComponentToNode',
|
||||
args: [nodeUuid, componentType]
|
||||
};
|
||||
|
||||
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
||||
resolve(result);
|
||||
}).catch((err2: Error) => {
|
||||
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async removeComponent(nodeUuid: string, componentType: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
const options = {
|
||||
name: 'cocos-mcp-server',
|
||||
method: 'removeComponentFromNode',
|
||||
args: [nodeUuid, componentType]
|
||||
};
|
||||
|
||||
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
||||
resolve(result);
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getComponents(nodeUuid: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 优先尝试直接使用 Editor API 查询节点信息
|
||||
Editor.Message.request('scene', 'query-node', nodeUuid).then((nodeData: any) => {
|
||||
if (nodeData && nodeData.__comps__) {
|
||||
const components = nodeData.__comps__.map((comp: any) => ({
|
||||
type: comp.__type__ || 'Unknown',
|
||||
enabled: comp.enabled !== undefined ? comp.enabled : true,
|
||||
properties: this.extractComponentProperties(comp)
|
||||
}));
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
nodeUuid: nodeUuid,
|
||||
components: components
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve({ success: false, error: 'Node not found or no components data' });
|
||||
}
|
||||
}).catch((err: Error) => {
|
||||
// 备用方案:使用场景脚本
|
||||
const options = {
|
||||
name: 'cocos-mcp-server',
|
||||
method: 'getNodeInfo',
|
||||
args: [nodeUuid]
|
||||
};
|
||||
|
||||
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
||||
if (result.success) {
|
||||
resolve({
|
||||
success: true,
|
||||
data: result.data.components
|
||||
});
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
}).catch((err2: Error) => {
|
||||
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getComponentInfo(nodeUuid: string, componentType: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 优先尝试直接使用 Editor API 查询节点信息
|
||||
Editor.Message.request('scene', 'query-node', nodeUuid).then((nodeData: any) => {
|
||||
if (nodeData && nodeData.__comps__) {
|
||||
const component = nodeData.__comps__.find((comp: any) => comp.__type__ === componentType);
|
||||
|
||||
if (component) {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
nodeUuid: nodeUuid,
|
||||
componentType: componentType,
|
||||
enabled: component.enabled !== undefined ? component.enabled : true,
|
||||
properties: this.extractComponentProperties(component)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve({ success: false, error: `Component '${componentType}' not found on node` });
|
||||
}
|
||||
} else {
|
||||
resolve({ success: false, error: 'Node not found or no components data' });
|
||||
}
|
||||
}).catch((err: Error) => {
|
||||
// 备用方案:使用场景脚本
|
||||
const options = {
|
||||
name: 'cocos-mcp-server',
|
||||
method: 'getNodeInfo',
|
||||
args: [nodeUuid]
|
||||
};
|
||||
|
||||
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
||||
if (result.success && result.data.components) {
|
||||
const component = result.data.components.find((comp: any) => comp.type === componentType);
|
||||
if (component) {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
nodeUuid: nodeUuid,
|
||||
componentType: componentType,
|
||||
...component
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve({ success: false, error: `Component '${componentType}' not found on node` });
|
||||
}
|
||||
} else {
|
||||
resolve({ success: false, error: result.error || 'Failed to get component info' });
|
||||
}
|
||||
}).catch((err2: Error) => {
|
||||
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private extractComponentProperties(component: any): Record<string, any> {
|
||||
const properties: Record<string, any> = {};
|
||||
const excludeKeys = ['__type__', 'enabled', 'node', '_id'];
|
||||
|
||||
for (const key in component) {
|
||||
if (!excludeKeys.includes(key) && !key.startsWith('_')) {
|
||||
properties[key] = component[key];
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private async setComponentProperty(args: any): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 首先获取节点信息以找到正确的组件索引
|
||||
Editor.Message.request('scene', 'query-node', args.nodeUuid).then((nodeData: any) => {
|
||||
if (!nodeData || !nodeData.__comps__) {
|
||||
throw new Error('Node not found or no components data');
|
||||
}
|
||||
|
||||
// 查找组件索引
|
||||
let componentIndex = -1;
|
||||
for (let i = 0; i < nodeData.__comps__.length; i++) {
|
||||
const comp = nodeData.__comps__[i];
|
||||
if (comp.__type__ === args.componentType) {
|
||||
componentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (componentIndex === -1) {
|
||||
throw new Error(`Component '${args.componentType}' not found on node`);
|
||||
}
|
||||
|
||||
// 使用正确的组件索引路径
|
||||
const propertyPath = `__comps__.${componentIndex}.${args.property}`;
|
||||
|
||||
return Editor.Message.request('scene', 'set-property', {
|
||||
uuid: args.nodeUuid,
|
||||
path: propertyPath,
|
||||
dump: {
|
||||
value: args.value
|
||||
}
|
||||
});
|
||||
}).then(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: `Component property '${args.property}' updated successfully`
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
// 备用方案:使用场景脚本
|
||||
const options = {
|
||||
name: 'cocos-mcp-server',
|
||||
method: 'setComponentProperty',
|
||||
args: [args.nodeUuid, args.componentType, args.property, args.value]
|
||||
};
|
||||
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
||||
resolve(result);
|
||||
}).catch((err2: Error) => {
|
||||
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async attachScript(nodeUuid: string, scriptPath: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 从脚本路径提取组件类名
|
||||
const scriptName = scriptPath.split('/').pop()?.replace('.ts', '').replace('.js', '');
|
||||
if (!scriptName) {
|
||||
resolve({ success: false, error: 'Invalid script path' });
|
||||
return;
|
||||
}
|
||||
|
||||
// 首先尝试直接使用脚本名称作为组件类型
|
||||
Editor.Message.request('scene', 'create-component', {
|
||||
uuid: nodeUuid,
|
||||
component: scriptName // 使用脚本名称而非UUID
|
||||
}).then((result: any) => {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
componentId: result,
|
||||
scriptPath: scriptPath,
|
||||
componentName: scriptName,
|
||||
message: `Script '${scriptName}' attached successfully`
|
||||
}
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
// 备用方案:使用场景脚本
|
||||
const options = {
|
||||
name: 'cocos-mcp-server',
|
||||
method: 'attachScript',
|
||||
args: [nodeUuid, scriptPath]
|
||||
};
|
||||
|
||||
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
||||
resolve(result);
|
||||
}).catch((err2: Error) => {
|
||||
resolve({
|
||||
success: false,
|
||||
error: `Failed to attach script '${scriptName}': ${err.message}`,
|
||||
instruction: 'Please ensure the script is properly compiled and exported as a Component class. You can also manually attach the script through the Properties panel in the editor.'
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getAvailableComponents(category: string = 'all'): Promise<ToolResponse> {
|
||||
const componentCategories: Record<string, string[]> = {
|
||||
renderer: ['cc.Sprite', 'cc.Label', 'cc.RichText', 'cc.Mask', 'cc.Graphics'],
|
||||
ui: ['cc.Button', 'cc.Toggle', 'cc.Slider', 'cc.ScrollView', 'cc.EditBox', 'cc.ProgressBar'],
|
||||
physics: ['cc.RigidBody2D', 'cc.BoxCollider2D', 'cc.CircleCollider2D', 'cc.PolygonCollider2D'],
|
||||
animation: ['cc.Animation', 'cc.AnimationClip', 'cc.SkeletalAnimation'],
|
||||
audio: ['cc.AudioSource'],
|
||||
layout: ['cc.Layout', 'cc.Widget', 'cc.PageView', 'cc.PageViewIndicator'],
|
||||
effects: ['cc.MotionStreak', 'cc.ParticleSystem2D'],
|
||||
camera: ['cc.Camera'],
|
||||
light: ['cc.Light', 'cc.DirectionalLight', 'cc.PointLight', 'cc.SpotLight']
|
||||
};
|
||||
|
||||
let components: string[] = [];
|
||||
|
||||
if (category === 'all') {
|
||||
for (const cat in componentCategories) {
|
||||
components = components.concat(componentCategories[cat]);
|
||||
}
|
||||
} else if (componentCategories[category]) {
|
||||
components = componentCategories[category];
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
category: category,
|
||||
components: components
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
351
source/tools/debug-tools.ts
Normal file
351
source/tools/debug-tools.ts
Normal file
@@ -0,0 +1,351 @@
|
||||
import { ToolDefinition, ToolResponse, ToolExecutor, ConsoleMessage, PerformanceStats, ValidationResult, ValidationIssue } from '../types';
|
||||
|
||||
export class DebugTools implements ToolExecutor {
|
||||
private consoleMessages: ConsoleMessage[] = [];
|
||||
private readonly maxMessages = 1000;
|
||||
|
||||
constructor() {
|
||||
this.setupConsoleCapture();
|
||||
}
|
||||
|
||||
private setupConsoleCapture(): void {
|
||||
// Intercept Editor console messages
|
||||
// Note: Editor.Message.addBroadcastListener may not be available in all versions
|
||||
// This is a placeholder for console capture implementation
|
||||
console.log('Console capture setup - implementation depends on Editor API availability');
|
||||
}
|
||||
|
||||
private addConsoleMessage(message: any): void {
|
||||
this.consoleMessages.push({
|
||||
timestamp: new Date().toISOString(),
|
||||
...message
|
||||
});
|
||||
|
||||
// Keep only latest messages
|
||||
if (this.consoleMessages.length > this.maxMessages) {
|
||||
this.consoleMessages.shift();
|
||||
}
|
||||
}
|
||||
|
||||
getTools(): ToolDefinition[] {
|
||||
return [
|
||||
{
|
||||
name: 'get_console_logs',
|
||||
description: 'Get editor console logs',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: {
|
||||
type: 'number',
|
||||
description: 'Number of recent logs to retrieve',
|
||||
default: 100
|
||||
},
|
||||
filter: {
|
||||
type: 'string',
|
||||
description: 'Filter logs by type',
|
||||
enum: ['all', 'log', 'warn', 'error', 'info'],
|
||||
default: 'all'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'clear_console',
|
||||
description: 'Clear editor console',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'execute_script',
|
||||
description: 'Execute JavaScript in scene context',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
script: {
|
||||
type: 'string',
|
||||
description: 'JavaScript code to execute'
|
||||
}
|
||||
},
|
||||
required: ['script']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_node_tree',
|
||||
description: 'Get detailed node tree for debugging',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
rootUuid: {
|
||||
type: 'string',
|
||||
description: 'Root node UUID (optional, uses scene root if not provided)'
|
||||
},
|
||||
maxDepth: {
|
||||
type: 'number',
|
||||
description: 'Maximum tree depth',
|
||||
default: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_performance_stats',
|
||||
description: 'Get performance statistics',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'validate_scene',
|
||||
description: 'Validate current scene for issues',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
checkMissingAssets: {
|
||||
type: 'boolean',
|
||||
description: 'Check for missing asset references',
|
||||
default: true
|
||||
},
|
||||
checkPerformance: {
|
||||
type: 'boolean',
|
||||
description: 'Check for performance issues',
|
||||
default: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_editor_info',
|
||||
description: 'Get editor and environment information',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async execute(toolName: string, args: any): Promise<ToolResponse> {
|
||||
switch (toolName) {
|
||||
case 'get_console_logs':
|
||||
return await this.getConsoleLogs(args.limit, args.filter);
|
||||
case 'clear_console':
|
||||
return await this.clearConsole();
|
||||
case 'execute_script':
|
||||
return await this.executeScript(args.script);
|
||||
case 'get_node_tree':
|
||||
return await this.getNodeTree(args.rootUuid, args.maxDepth);
|
||||
case 'get_performance_stats':
|
||||
return await this.getPerformanceStats();
|
||||
case 'validate_scene':
|
||||
return await this.validateScene(args);
|
||||
case 'get_editor_info':
|
||||
return await this.getEditorInfo();
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${toolName}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async getConsoleLogs(limit: number = 100, filter: string = 'all'): Promise<ToolResponse> {
|
||||
let logs = this.consoleMessages;
|
||||
|
||||
if (filter !== 'all') {
|
||||
logs = logs.filter(log => log.type === filter);
|
||||
}
|
||||
|
||||
const recentLogs = logs.slice(-limit);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
total: logs.length,
|
||||
returned: recentLogs.length,
|
||||
logs: recentLogs
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async clearConsole(): Promise<ToolResponse> {
|
||||
this.consoleMessages = [];
|
||||
|
||||
try {
|
||||
// Note: Editor.Message.send may not return a promise in all versions
|
||||
Editor.Message.send('console', 'clear');
|
||||
return {
|
||||
success: true,
|
||||
message: 'Console cleared successfully'
|
||||
};
|
||||
} catch (err: any) {
|
||||
return { success: false, error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
private async executeScript(script: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('scene', 'execute-script', {
|
||||
script: script
|
||||
}).then((result: any) => {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
result: result,
|
||||
message: 'Script executed successfully'
|
||||
}
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getNodeTree(rootUuid?: string, maxDepth: number = 10): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
const buildTree = async (nodeUuid: string, depth: number = 0): Promise<any> => {
|
||||
if (depth >= maxDepth) {
|
||||
return { truncated: true };
|
||||
}
|
||||
|
||||
try {
|
||||
const nodeData = await Editor.Message.request('scene', 'query-node', nodeUuid);
|
||||
|
||||
const tree = {
|
||||
uuid: nodeData.uuid,
|
||||
name: nodeData.name,
|
||||
active: nodeData.active,
|
||||
components: (nodeData as any).components ? (nodeData as any).components.map((c: any) => c.__type__) : [],
|
||||
childCount: nodeData.children ? nodeData.children.length : 0,
|
||||
children: [] as any[]
|
||||
};
|
||||
|
||||
if (nodeData.children && nodeData.children.length > 0) {
|
||||
for (const childId of nodeData.children) {
|
||||
const childTree = await buildTree(childId, depth + 1);
|
||||
tree.children.push(childTree);
|
||||
}
|
||||
}
|
||||
|
||||
return tree;
|
||||
} catch (err: any) {
|
||||
return { error: err.message };
|
||||
}
|
||||
};
|
||||
|
||||
if (rootUuid) {
|
||||
buildTree(rootUuid).then(tree => {
|
||||
resolve({ success: true, data: tree });
|
||||
});
|
||||
} else {
|
||||
Editor.Message.request('scene', 'query-hierarchy').then(async (hierarchy: any) => {
|
||||
const trees = [];
|
||||
for (const rootNode of hierarchy.children) {
|
||||
const tree = await buildTree(rootNode.uuid);
|
||||
trees.push(tree);
|
||||
}
|
||||
resolve({ success: true, data: trees });
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async getPerformanceStats(): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('scene', 'query-performance').then((stats: any) => {
|
||||
const perfStats: PerformanceStats = {
|
||||
nodeCount: stats.nodeCount || 0,
|
||||
componentCount: stats.componentCount || 0,
|
||||
drawCalls: stats.drawCalls || 0,
|
||||
triangles: stats.triangles || 0,
|
||||
memory: stats.memory || {}
|
||||
};
|
||||
resolve({ success: true, data: perfStats });
|
||||
}).catch(() => {
|
||||
// Fallback to basic stats
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
message: 'Performance stats not available in edit mode'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async validateScene(options: any): Promise<ToolResponse> {
|
||||
const issues: ValidationIssue[] = [];
|
||||
|
||||
try {
|
||||
// Check for missing assets
|
||||
if (options.checkMissingAssets) {
|
||||
const assetCheck = await Editor.Message.request('scene', 'check-missing-assets');
|
||||
if (assetCheck && assetCheck.missing) {
|
||||
issues.push({
|
||||
type: 'error',
|
||||
category: 'assets',
|
||||
message: `Found ${assetCheck.missing.length} missing asset references`,
|
||||
details: assetCheck.missing
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check for performance issues
|
||||
if (options.checkPerformance) {
|
||||
const hierarchy = await Editor.Message.request('scene', 'query-hierarchy');
|
||||
const nodeCount = this.countNodes(hierarchy.children);
|
||||
|
||||
if (nodeCount > 1000) {
|
||||
issues.push({
|
||||
type: 'warning',
|
||||
category: 'performance',
|
||||
message: `High node count: ${nodeCount} nodes (recommended < 1000)`,
|
||||
suggestion: 'Consider using object pooling or scene optimization'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const result: ValidationResult = {
|
||||
valid: issues.length === 0,
|
||||
issueCount: issues.length,
|
||||
issues: issues
|
||||
};
|
||||
|
||||
return { success: true, data: result };
|
||||
} catch (err: any) {
|
||||
return { success: false, error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
private countNodes(nodes: any[]): number {
|
||||
let count = nodes.length;
|
||||
for (const node of nodes) {
|
||||
if (node.children) {
|
||||
count += this.countNodes(node.children);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private async getEditorInfo(): Promise<ToolResponse> {
|
||||
const info = {
|
||||
editor: {
|
||||
version: (Editor as any).versions?.editor || 'Unknown',
|
||||
cocosVersion: (Editor as any).versions?.cocos || 'Unknown',
|
||||
platform: process.platform,
|
||||
arch: process.arch,
|
||||
nodeVersion: process.version
|
||||
},
|
||||
project: {
|
||||
name: Editor.Project.name,
|
||||
path: Editor.Project.path,
|
||||
uuid: Editor.Project.uuid
|
||||
},
|
||||
memory: process.memoryUsage(),
|
||||
uptime: process.uptime()
|
||||
};
|
||||
|
||||
return { success: true, data: info };
|
||||
}
|
||||
}
|
||||
526
source/tools/node-tools.ts
Normal file
526
source/tools/node-tools.ts
Normal file
@@ -0,0 +1,526 @@
|
||||
import { ToolDefinition, ToolResponse, ToolExecutor, NodeInfo } from '../types';
|
||||
|
||||
export class NodeTools implements ToolExecutor {
|
||||
getTools(): ToolDefinition[] {
|
||||
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.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Node name'
|
||||
},
|
||||
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.'
|
||||
},
|
||||
nodeType: {
|
||||
type: 'string',
|
||||
description: 'Node type: Node, 2DNode, 3DNode',
|
||||
enum: ['Node', '2DNode', '3DNode'],
|
||||
default: 'Node'
|
||||
},
|
||||
siblingIndex: {
|
||||
type: 'number',
|
||||
description: 'Sibling index for ordering (-1 means append at end)',
|
||||
default: -1
|
||||
}
|
||||
},
|
||||
required: ['name']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_node_info',
|
||||
description: 'Get node information by UUID',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
uuid: {
|
||||
type: 'string',
|
||||
description: 'Node UUID'
|
||||
}
|
||||
},
|
||||
required: ['uuid']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'find_nodes',
|
||||
description: 'Find nodes by name pattern',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pattern: {
|
||||
type: 'string',
|
||||
description: 'Name pattern to search'
|
||||
},
|
||||
exactMatch: {
|
||||
type: 'boolean',
|
||||
description: 'Exact match or partial match',
|
||||
default: false
|
||||
}
|
||||
},
|
||||
required: ['pattern']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'find_node_by_name',
|
||||
description: 'Find first node by exact name',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Node name to find'
|
||||
}
|
||||
},
|
||||
required: ['name']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_all_nodes',
|
||||
description: 'Get all nodes in the scene with their UUIDs',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'set_node_property',
|
||||
description: 'Set node property value',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
uuid: {
|
||||
type: 'string',
|
||||
description: 'Node UUID'
|
||||
},
|
||||
property: {
|
||||
type: 'string',
|
||||
description: 'Property name (e.g., position, rotation, scale, active)'
|
||||
},
|
||||
value: {
|
||||
description: 'Property value'
|
||||
}
|
||||
},
|
||||
required: ['uuid', 'property', 'value']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'delete_node',
|
||||
description: 'Delete a node from scene',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
uuid: {
|
||||
type: 'string',
|
||||
description: 'Node UUID to delete'
|
||||
}
|
||||
},
|
||||
required: ['uuid']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'move_node',
|
||||
description: 'Move node to new parent',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
nodeUuid: {
|
||||
type: 'string',
|
||||
description: 'Node UUID to move'
|
||||
},
|
||||
newParentUuid: {
|
||||
type: 'string',
|
||||
description: 'New parent node UUID'
|
||||
},
|
||||
siblingIndex: {
|
||||
type: 'number',
|
||||
description: 'Sibling index in new parent',
|
||||
default: -1
|
||||
}
|
||||
},
|
||||
required: ['nodeUuid', 'newParentUuid']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'duplicate_node',
|
||||
description: 'Duplicate a node',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
uuid: {
|
||||
type: 'string',
|
||||
description: 'Node UUID to duplicate'
|
||||
},
|
||||
includeChildren: {
|
||||
type: 'boolean',
|
||||
description: 'Include children nodes',
|
||||
default: true
|
||||
}
|
||||
},
|
||||
required: ['uuid']
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async execute(toolName: string, args: any): Promise<ToolResponse> {
|
||||
switch (toolName) {
|
||||
case 'create_node':
|
||||
return await this.createNode(args);
|
||||
case 'get_node_info':
|
||||
return await this.getNodeInfo(args.uuid);
|
||||
case 'find_nodes':
|
||||
return await this.findNodes(args.pattern, args.exactMatch);
|
||||
case 'find_node_by_name':
|
||||
return await this.findNodeByName(args.name);
|
||||
case 'get_all_nodes':
|
||||
return await this.getAllNodes();
|
||||
case 'set_node_property':
|
||||
return await this.setNodeProperty(args.uuid, args.property, args.value);
|
||||
case 'delete_node':
|
||||
return await this.deleteNode(args.uuid);
|
||||
case 'move_node':
|
||||
return await this.moveNode(args.nodeUuid, args.newParentUuid, args.siblingIndex);
|
||||
case 'duplicate_node':
|
||||
return await this.duplicateNode(args.uuid, args.includeChildren);
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${toolName}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async createNode(args: any): Promise<ToolResponse> {
|
||||
return new Promise(async (resolve) => {
|
||||
// 如果指定了父节点,先验证父节点是否存在
|
||||
if (args.parentUuid) {
|
||||
try {
|
||||
const parentNode = await Editor.Message.request('scene', 'query-node', args.parentUuid);
|
||||
if (!parentNode) {
|
||||
resolve({
|
||||
success: false,
|
||||
error: `Parent node with UUID '${args.parentUuid}' not found`
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
resolve({
|
||||
success: false,
|
||||
error: `Failed to verify parent node: ${err}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const nodeData: any = {
|
||||
name: args.name,
|
||||
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
|
||||
}).then(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
uuid: nodeUuid,
|
||||
name: args.name,
|
||||
parentUuid: args.parentUuid,
|
||||
message: `Node '${args.name}' created under specified parent`
|
||||
}
|
||||
});
|
||||
}).catch(() => {
|
||||
// 即使移动失败,节点已创建,返回成功但带警告
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
uuid: nodeUuid,
|
||||
name: args.name,
|
||||
message: `Node '${args.name}' created but may not be under specified parent`,
|
||||
warning: 'Failed to move node to specified parent'
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
uuid: nodeUuid,
|
||||
name: args.name,
|
||||
message: `Node '${args.name}' created successfully`
|
||||
}
|
||||
});
|
||||
}
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
} else {
|
||||
// 没有指定父节点,使用默认行为
|
||||
Editor.Message.request('scene', 'create-node', nodeData).then((result: any) => {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
uuid: result,
|
||||
name: args.name,
|
||||
message: `Node '${args.name}' created at current selection`
|
||||
}
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async getNodeInfo(uuid: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('scene', 'query-node', uuid).then((nodeData: any) => {
|
||||
if (!nodeData) {
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Node not found or invalid response'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据实际返回的数据结构解析节点信息
|
||||
const info: NodeInfo = {
|
||||
uuid: nodeData.uuid?.value || uuid,
|
||||
name: nodeData.name?.value || 'Unknown',
|
||||
active: nodeData.active?.value !== undefined ? nodeData.active.value : true,
|
||||
position: nodeData.position?.value || { x: 0, y: 0, z: 0 },
|
||||
rotation: nodeData.rotation?.value || { x: 0, y: 0, z: 0 },
|
||||
scale: nodeData.scale?.value || { x: 1, y: 1, z: 1 },
|
||||
parent: nodeData.parent?.value?.uuid || null,
|
||||
children: nodeData.children || [],
|
||||
components: (nodeData.__comps__ || []).map((comp: any) => ({
|
||||
type: comp.__type__ || 'Unknown',
|
||||
enabled: comp.enabled !== undefined ? comp.enabled : true
|
||||
})),
|
||||
layer: nodeData.layer?.value || 1073741824,
|
||||
mobility: nodeData.mobility?.value || 0
|
||||
};
|
||||
resolve({ success: true, data: info });
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
}));
|
||||
resolve({ success: true, data: nodes });
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async findNodeByName(name: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 优先尝试使用 Editor API 查询节点树并搜索
|
||||
Editor.Message.request('scene', 'query-node-tree').then((tree: any) => {
|
||||
const foundNode = this.searchNodeInTree(tree, name);
|
||||
if (foundNode) {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
uuid: foundNode.uuid,
|
||||
name: foundNode.name,
|
||||
path: this.getNodePath(foundNode)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve({ success: false, error: `Node '${name}' not found` });
|
||||
}
|
||||
}).catch((err: Error) => {
|
||||
// 备用方案:使用场景脚本
|
||||
const options = {
|
||||
name: 'cocos-mcp-server',
|
||||
method: 'findNodeByName',
|
||||
args: [name]
|
||||
};
|
||||
|
||||
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
||||
resolve(result);
|
||||
}).catch((err2: Error) => {
|
||||
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private searchNodeInTree(node: any, targetName: string): any {
|
||||
if (node.name === targetName) {
|
||||
return node;
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
const found = this.searchNodeInTree(child, targetName);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async getAllNodes(): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 尝试查询场景节点树
|
||||
Editor.Message.request('scene', 'query-node-tree').then((tree: any) => {
|
||||
const nodes: any[] = [];
|
||||
|
||||
const traverseTree = (node: any) => {
|
||||
nodes.push({
|
||||
uuid: node.uuid,
|
||||
name: node.name,
|
||||
type: node.type,
|
||||
active: node.active,
|
||||
path: this.getNodePath(node)
|
||||
});
|
||||
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
traverseTree(child);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (tree && tree.children) {
|
||||
traverseTree(tree);
|
||||
}
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
totalNodes: nodes.length,
|
||||
nodes: nodes
|
||||
}
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
// 备用方案:使用场景脚本
|
||||
const options = {
|
||||
name: 'cocos-mcp-server',
|
||||
method: 'getAllNodes',
|
||||
args: []
|
||||
};
|
||||
|
||||
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
||||
resolve(result);
|
||||
}).catch((err2: Error) => {
|
||||
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getNodePath(node: any): string {
|
||||
const path = [node.name];
|
||||
let current = node.parent;
|
||||
while (current && current.name !== 'Canvas') {
|
||||
path.unshift(current.name);
|
||||
current = current.parent;
|
||||
}
|
||||
return path.join('/');
|
||||
}
|
||||
|
||||
private async setNodeProperty(uuid: string, property: string, value: any): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 尝试直接使用 Editor API 设置节点属性
|
||||
Editor.Message.request('scene', 'set-property', {
|
||||
uuid: uuid,
|
||||
path: property,
|
||||
dump: {
|
||||
value: value
|
||||
}
|
||||
}).then(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: `Property '${property}' updated successfully`
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
// 如果直接设置失败,尝试使用场景脚本
|
||||
const options = {
|
||||
name: 'cocos-mcp-server',
|
||||
method: 'setNodeProperty',
|
||||
args: [uuid, property, value]
|
||||
};
|
||||
|
||||
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
||||
resolve(result);
|
||||
}).catch((err2: Error) => {
|
||||
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async deleteNode(uuid: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('scene', 'remove-node', { uuid: uuid }).then(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: 'Node deleted successfully'
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async moveNode(nodeUuid: string, newParentUuid: string, siblingIndex: number = -1): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('scene', 'move-node', {
|
||||
uuid: nodeUuid,
|
||||
parent: newParentUuid,
|
||||
index: siblingIndex
|
||||
}).then(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: 'Node moved successfully'
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async duplicateNode(uuid: string, includeChildren: boolean = true): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('scene', 'duplicate-node', uuid).then((result: any) => {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
newUuid: result.uuid,
|
||||
message: 'Node duplicated successfully'
|
||||
}
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
359
source/tools/prefab-tools.ts
Normal file
359
source/tools/prefab-tools.ts
Normal file
@@ -0,0 +1,359 @@
|
||||
import { ToolDefinition, ToolResponse, ToolExecutor, PrefabInfo } from '../types';
|
||||
|
||||
export class PrefabTools implements ToolExecutor {
|
||||
getTools(): ToolDefinition[] {
|
||||
return [
|
||||
{
|
||||
name: 'get_prefab_list',
|
||||
description: 'Get all prefabs in the project',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
folder: {
|
||||
type: 'string',
|
||||
description: 'Folder path to search (optional)',
|
||||
default: 'db://assets'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'load_prefab',
|
||||
description: 'Load a prefab by path',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
prefabPath: {
|
||||
type: 'string',
|
||||
description: 'Prefab asset path'
|
||||
}
|
||||
},
|
||||
required: ['prefabPath']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'instantiate_prefab',
|
||||
description: 'Instantiate a prefab in the scene',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
prefabPath: {
|
||||
type: 'string',
|
||||
description: 'Prefab asset path'
|
||||
},
|
||||
parentUuid: {
|
||||
type: 'string',
|
||||
description: 'Parent node UUID (optional)'
|
||||
},
|
||||
position: {
|
||||
type: 'object',
|
||||
description: 'Initial position',
|
||||
properties: {
|
||||
x: { type: 'number' },
|
||||
y: { type: 'number' },
|
||||
z: { type: 'number' }
|
||||
}
|
||||
}
|
||||
},
|
||||
required: ['prefabPath']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'create_prefab',
|
||||
description: 'Create a prefab from a node',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
nodeUuid: {
|
||||
type: 'string',
|
||||
description: 'Source node UUID'
|
||||
},
|
||||
savePath: {
|
||||
type: 'string',
|
||||
description: 'Path to save the prefab'
|
||||
},
|
||||
prefabName: {
|
||||
type: 'string',
|
||||
description: 'Prefab name'
|
||||
}
|
||||
},
|
||||
required: ['nodeUuid', 'savePath', 'prefabName']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'create_prefab_from_node',
|
||||
description: 'Create a prefab from a node (alias for create_prefab)',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
nodeUuid: {
|
||||
type: 'string',
|
||||
description: 'Source node UUID'
|
||||
},
|
||||
prefabPath: {
|
||||
type: 'string',
|
||||
description: 'Path to save the prefab'
|
||||
}
|
||||
},
|
||||
required: ['nodeUuid', 'prefabPath']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'update_prefab',
|
||||
description: 'Update an existing prefab',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
prefabPath: {
|
||||
type: 'string',
|
||||
description: 'Prefab asset path'
|
||||
},
|
||||
nodeUuid: {
|
||||
type: 'string',
|
||||
description: 'Node UUID with changes'
|
||||
}
|
||||
},
|
||||
required: ['prefabPath', 'nodeUuid']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'revert_prefab',
|
||||
description: 'Revert prefab instance to original',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
nodeUuid: {
|
||||
type: 'string',
|
||||
description: 'Prefab instance node UUID'
|
||||
}
|
||||
},
|
||||
required: ['nodeUuid']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_prefab_info',
|
||||
description: 'Get detailed prefab information',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
prefabPath: {
|
||||
type: 'string',
|
||||
description: 'Prefab asset path'
|
||||
}
|
||||
},
|
||||
required: ['prefabPath']
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async execute(toolName: string, args: any): Promise<ToolResponse> {
|
||||
switch (toolName) {
|
||||
case 'get_prefab_list':
|
||||
return await this.getPrefabList(args.folder);
|
||||
case 'load_prefab':
|
||||
return await this.loadPrefab(args.prefabPath);
|
||||
case 'instantiate_prefab':
|
||||
return await this.instantiatePrefab(args);
|
||||
case 'create_prefab':
|
||||
return await this.createPrefab(args);
|
||||
case 'create_prefab_from_node':
|
||||
return await this.createPrefabFromNode(args);
|
||||
case 'update_prefab':
|
||||
return await this.updatePrefab(args.prefabPath, args.nodeUuid);
|
||||
case 'revert_prefab':
|
||||
return await this.revertPrefab(args.nodeUuid);
|
||||
case 'get_prefab_info':
|
||||
return await this.getPrefabInfo(args.prefabPath);
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${toolName}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async getPrefabList(folder: string = 'db://assets'): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
const pattern = folder.endsWith('/') ?
|
||||
`${folder}**/*.prefab` : `${folder}/**/*.prefab`;
|
||||
|
||||
Editor.Message.request('asset-db', 'query-assets', {
|
||||
pattern: pattern
|
||||
}).then((results: any[]) => {
|
||||
const prefabs: PrefabInfo[] = results.map(asset => ({
|
||||
name: asset.name,
|
||||
path: asset.url,
|
||||
uuid: asset.uuid,
|
||||
folder: asset.url.substring(0, asset.url.lastIndexOf('/'))
|
||||
}));
|
||||
resolve({ success: true, data: prefabs });
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async loadPrefab(prefabPath: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
|
||||
if (!assetInfo) {
|
||||
throw new Error('Prefab not found');
|
||||
}
|
||||
|
||||
return Editor.Message.request('scene', 'load-asset', {
|
||||
uuid: assetInfo.uuid
|
||||
});
|
||||
}).then((prefabData: any) => {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
uuid: prefabData.uuid,
|
||||
name: prefabData.name,
|
||||
message: 'Prefab loaded successfully'
|
||||
}
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async instantiatePrefab(args: any): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('asset-db', 'query-asset-info', args.prefabPath).then((assetInfo: any) => {
|
||||
if (!assetInfo) {
|
||||
throw new Error('Prefab not found');
|
||||
}
|
||||
|
||||
const instantiateData: any = {
|
||||
prefab: assetInfo.uuid
|
||||
};
|
||||
|
||||
if (args.parentUuid) {
|
||||
instantiateData.parent = args.parentUuid;
|
||||
}
|
||||
|
||||
if (args.position) {
|
||||
instantiateData.position = args.position;
|
||||
}
|
||||
|
||||
return Editor.Message.request('scene', 'instantiate-prefab', instantiateData);
|
||||
}).then((result: any) => {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
nodeUuid: result.uuid,
|
||||
name: result.name,
|
||||
message: 'Prefab instantiated successfully'
|
||||
}
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async createPrefab(args: any): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 支持 prefabPath 和 savePath 两种参数名
|
||||
const pathParam = args.prefabPath || args.savePath;
|
||||
if (!pathParam) {
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Missing prefab path parameter. Please provide either prefabPath or savePath.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const fullPath = pathParam.endsWith('.prefab') ?
|
||||
pathParam : `${pathParam}/${args.prefabName || 'NewPrefab'}.prefab`;
|
||||
|
||||
// 预制体创建需要特殊的Editor API支持
|
||||
// 目前Cocos Creator 3.8的MCP插件环境下,预制体创建功能受限
|
||||
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Prefab creation is not supported in the current MCP plugin environment',
|
||||
instruction: 'Please create prefabs manually by dragging nodes from the scene to the assets folder in the Cocos Creator editor',
|
||||
data: {
|
||||
nodeUuid: args.nodeUuid,
|
||||
requestedPath: fullPath,
|
||||
suggestion: 'You can manually drag the node from the scene to the assets folder to create a prefab.'
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async updatePrefab(prefabPath: string, nodeUuid: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
|
||||
if (!assetInfo) {
|
||||
throw new Error('Prefab not found');
|
||||
}
|
||||
|
||||
return Editor.Message.request('scene', 'apply-prefab', {
|
||||
node: nodeUuid,
|
||||
prefab: assetInfo.uuid
|
||||
});
|
||||
}).then(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: 'Prefab updated successfully'
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async revertPrefab(nodeUuid: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('scene', 'revert-prefab', {
|
||||
node: nodeUuid
|
||||
}).then(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: 'Prefab instance reverted successfully'
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getPrefabInfo(prefabPath: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('asset-db', 'query-asset-info', prefabPath).then((assetInfo: any) => {
|
||||
if (!assetInfo) {
|
||||
throw new Error('Prefab not found');
|
||||
}
|
||||
|
||||
return Editor.Message.request('asset-db', 'query-asset-meta', assetInfo.uuid);
|
||||
}).then((metaInfo: any) => {
|
||||
const info: PrefabInfo = {
|
||||
name: metaInfo.name,
|
||||
uuid: metaInfo.uuid,
|
||||
path: prefabPath,
|
||||
folder: prefabPath.substring(0, prefabPath.lastIndexOf('/')),
|
||||
createTime: metaInfo.createTime,
|
||||
modifyTime: metaInfo.modifyTime,
|
||||
dependencies: metaInfo.depends || []
|
||||
};
|
||||
resolve({ success: true, data: info });
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async createPrefabFromNode(args: any): Promise<ToolResponse> {
|
||||
// 从 prefabPath 提取名称
|
||||
const prefabPath = args.prefabPath;
|
||||
const prefabName = prefabPath.split('/').pop()?.replace('.prefab', '') || 'NewPrefab';
|
||||
|
||||
// 调用原来的 createPrefab 方法
|
||||
return await this.createPrefab({
|
||||
nodeUuid: args.nodeUuid,
|
||||
savePath: prefabPath,
|
||||
prefabName: prefabName
|
||||
});
|
||||
}
|
||||
}
|
||||
163
source/tools/preferences-tools.ts
Normal file
163
source/tools/preferences-tools.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { ToolDefinition, ToolResponse, ToolExecutor } from '../types';
|
||||
|
||||
export class PreferencesTools implements ToolExecutor {
|
||||
getTools(): ToolDefinition[] {
|
||||
return [
|
||||
{
|
||||
name: 'get_preferences',
|
||||
description: 'Get editor preferences',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
description: 'Specific preference key to get (optional)'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'set_preferences',
|
||||
description: 'Set editor preferences',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
description: 'Preference key to set'
|
||||
},
|
||||
value: {
|
||||
description: 'Preference value to set'
|
||||
}
|
||||
},
|
||||
required: ['key', 'value']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_global_preferences',
|
||||
description: 'Get global editor preferences',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
description: 'Global preference key to get (optional)'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'set_global_preferences',
|
||||
description: 'Set global editor preferences',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
description: 'Global preference key to set'
|
||||
},
|
||||
value: {
|
||||
description: 'Global preference value to set'
|
||||
}
|
||||
},
|
||||
required: ['key', 'value']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_recent_projects',
|
||||
description: 'Get recently opened projects',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'clear_recent_projects',
|
||||
description: 'Clear recently opened projects list',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
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();
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${toolName}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async getPreferences(key?: string): 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'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async setPreferences(key: string, value: any): 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'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getGlobalPreferences(key?: string): 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'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async setGlobalPreferences(key: string, value: any): 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'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getRecentProjects(): 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'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async clearRecentProjects(): 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'
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
890
source/tools/project-tools.ts
Normal file
890
source/tools/project-tools.ts
Normal file
@@ -0,0 +1,890 @@
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
const previewConfig = {
|
||||
platform: platform,
|
||||
scenes: [] // Will use current scene
|
||||
};
|
||||
|
||||
Editor.Message.request('preview', 'start', previewConfig).then(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: `Project is running in ${platform} mode`
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async buildProject(args: any): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
const buildOptions = {
|
||||
platform: args.platform,
|
||||
debug: args.debug !== false,
|
||||
sourceMaps: args.debug !== false,
|
||||
buildPath: `build/${args.platform}`
|
||||
};
|
||||
|
||||
Editor.Message.request('builder', 'build', buildOptions).then(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: `Project built for ${args.platform}`,
|
||||
data: { buildPath: buildOptions.buildPath }
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getProjectInfo(): Promise<ToolResponse> {
|
||||
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'
|
||||
};
|
||||
|
||||
Editor.Message.request('project', 'query-info').then((additionalInfo: any) => {
|
||||
Object.assign(info, 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<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 使用正确的 project API 查询项目配置
|
||||
const configMap: Record<string, string> = {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
let pattern = `${folder}/**/*`;
|
||||
|
||||
// 添加类型过滤
|
||||
if (type !== 'all') {
|
||||
const typeExtensions: Record<string, string> = {
|
||||
'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}`;
|
||||
}
|
||||
}
|
||||
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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<ToolResponse> {
|
||||
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 });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
465
source/tools/scene-tools.ts
Normal file
465
source/tools/scene-tools.ts
Normal file
@@ -0,0 +1,465 @@
|
||||
import { ToolDefinition, ToolResponse, ToolExecutor, SceneInfo } from '../types';
|
||||
|
||||
export class SceneTools implements ToolExecutor {
|
||||
getTools(): ToolDefinition[] {
|
||||
return [
|
||||
{
|
||||
name: 'get_current_scene',
|
||||
description: 'Get current scene information',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_scene_list',
|
||||
description: 'Get all scenes in the project',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'open_scene',
|
||||
description: 'Open a scene by path',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
scenePath: {
|
||||
type: 'string',
|
||||
description: 'The scene file path'
|
||||
}
|
||||
},
|
||||
required: ['scenePath']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'save_scene',
|
||||
description: 'Save current scene',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'create_scene',
|
||||
description: 'Create a new scene asset',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
sceneName: {
|
||||
type: 'string',
|
||||
description: 'Name of the new scene'
|
||||
},
|
||||
savePath: {
|
||||
type: 'string',
|
||||
description: 'Path to save the scene (e.g., db://assets/scenes/NewScene.scene)'
|
||||
}
|
||||
},
|
||||
required: ['sceneName', 'savePath']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'save_scene_as',
|
||||
description: 'Save scene as new file',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
path: {
|
||||
type: 'string',
|
||||
description: 'Path to save the scene'
|
||||
}
|
||||
},
|
||||
required: ['path']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'close_scene',
|
||||
description: 'Close current scene',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'get_scene_hierarchy',
|
||||
description: 'Get the complete hierarchy of current scene',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
includeComponents: {
|
||||
type: 'boolean',
|
||||
description: 'Include component information',
|
||||
default: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
async execute(toolName: string, args: any): Promise<ToolResponse> {
|
||||
switch (toolName) {
|
||||
case 'get_current_scene':
|
||||
return await this.getCurrentScene();
|
||||
case 'get_scene_list':
|
||||
return await this.getSceneList();
|
||||
case 'open_scene':
|
||||
return await this.openScene(args.scenePath);
|
||||
case 'save_scene':
|
||||
return await this.saveScene();
|
||||
case 'create_scene':
|
||||
return await this.createScene(args.sceneName, args.savePath);
|
||||
case 'save_scene_as':
|
||||
return await this.saveSceneAs(args.path);
|
||||
case 'close_scene':
|
||||
return await this.closeScene();
|
||||
case 'get_scene_hierarchy':
|
||||
return await this.getSceneHierarchy(args.includeComponents);
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${toolName}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async getCurrentScene(): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 直接使用 query-node-tree 来获取场景信息(这个方法已经验证可用)
|
||||
Editor.Message.request('scene', 'query-node-tree').then((tree: any) => {
|
||||
if (tree && tree.uuid) {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
name: tree.name || 'Current Scene',
|
||||
uuid: tree.uuid,
|
||||
type: tree.type || 'cc.Scene',
|
||||
active: tree.active !== undefined ? tree.active : true,
|
||||
nodeCount: tree.children ? tree.children.length : 0
|
||||
}
|
||||
});
|
||||
} else {
|
||||
resolve({ success: false, error: 'No scene data available' });
|
||||
}
|
||||
}).catch((err: Error) => {
|
||||
// 备用方案:使用场景脚本
|
||||
const options = {
|
||||
name: 'cocos-mcp-server',
|
||||
method: 'getCurrentSceneInfo',
|
||||
args: []
|
||||
};
|
||||
|
||||
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
||||
resolve(result);
|
||||
}).catch((err2: Error) => {
|
||||
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getSceneList(): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('asset-db', 'query-assets', {
|
||||
pattern: 'db://assets/**/*.scene'
|
||||
}).then((results: any[]) => {
|
||||
const scenes: SceneInfo[] = results.map(asset => ({
|
||||
name: asset.name,
|
||||
path: asset.url,
|
||||
uuid: asset.uuid
|
||||
}));
|
||||
resolve({ success: true, data: scenes });
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async openScene(scenePath: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 首先获取场景的UUID
|
||||
Editor.Message.request('asset-db', 'query-uuid', scenePath).then((uuid: string | null) => {
|
||||
if (!uuid) {
|
||||
throw new Error('Scene not found');
|
||||
}
|
||||
|
||||
// 使用正确的 scene API 打开场景 (需要UUID)
|
||||
return Editor.Message.request('scene', 'open-scene', uuid);
|
||||
}).then(() => {
|
||||
resolve({ success: true, message: `Scene opened: ${scenePath}` });
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async saveScene(): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('scene', 'save-scene').then(() => {
|
||||
resolve({ success: true, message: 'Scene saved successfully' });
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async createScene(sceneName: string, savePath: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 确保路径以.scene结尾
|
||||
const fullPath = savePath.endsWith('.scene') ? savePath : `${savePath}/${sceneName}.scene`;
|
||||
|
||||
// 使用正确的Cocos Creator 3.8场景格式
|
||||
const sceneContent = JSON.stringify([
|
||||
{
|
||||
"__type__": "cc.SceneAsset",
|
||||
"_name": sceneName,
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_native": "",
|
||||
"scene": {
|
||||
"__id__": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Scene",
|
||||
"_name": sceneName,
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": null,
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [],
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"autoReleaseAssets": false,
|
||||
"_globals": {
|
||||
"__id__": 2
|
||||
},
|
||||
"_id": "scene"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.SceneGlobals",
|
||||
"ambient": {
|
||||
"__id__": 3
|
||||
},
|
||||
"skybox": {
|
||||
"__id__": 4
|
||||
},
|
||||
"fog": {
|
||||
"__id__": 5
|
||||
},
|
||||
"octree": {
|
||||
"__id__": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.AmbientInfo",
|
||||
"_skyColorHDR": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.5,
|
||||
"z": 0.8,
|
||||
"w": 0.520833
|
||||
},
|
||||
"_skyColor": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.5,
|
||||
"z": 0.8,
|
||||
"w": 0.520833
|
||||
},
|
||||
"_skyIllumHDR": 20000,
|
||||
"_skyIllum": 20000,
|
||||
"_groundAlbedoHDR": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.2,
|
||||
"z": 0.2,
|
||||
"w": 1
|
||||
},
|
||||
"_groundAlbedo": {
|
||||
"__type__": "cc.Vec4",
|
||||
"x": 0.2,
|
||||
"y": 0.2,
|
||||
"z": 0.2,
|
||||
"w": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"__type__": "cc.SkyboxInfo",
|
||||
"_envLightingType": 0,
|
||||
"_envmapHDR": null,
|
||||
"_envmap": null,
|
||||
"_envmapLodCount": 0,
|
||||
"_diffuseMapHDR": null,
|
||||
"_diffuseMap": null,
|
||||
"_enabled": false,
|
||||
"_useHDR": true,
|
||||
"_editableMaterial": null,
|
||||
"_reflectionHDR": null,
|
||||
"_reflectionMap": null,
|
||||
"_rotationAngle": 0
|
||||
},
|
||||
{
|
||||
"__type__": "cc.FogInfo",
|
||||
"_type": 0,
|
||||
"_fogColor": {
|
||||
"__type__": "cc.Color",
|
||||
"r": 200,
|
||||
"g": 200,
|
||||
"b": 200,
|
||||
"a": 255
|
||||
},
|
||||
"_enabled": false,
|
||||
"_fogDensity": 0.3,
|
||||
"_fogStart": 0.5,
|
||||
"_fogEnd": 300,
|
||||
"_fogAtten": 5,
|
||||
"_fogTop": 1.5,
|
||||
"_fogRange": 1.2,
|
||||
"_accurate": false
|
||||
},
|
||||
{
|
||||
"__type__": "cc.OctreeInfo",
|
||||
"_enabled": false,
|
||||
"_minPos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": -1024,
|
||||
"y": -1024,
|
||||
"z": -1024
|
||||
},
|
||||
"_maxPos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1024,
|
||||
"y": 1024,
|
||||
"z": 1024
|
||||
},
|
||||
"_depth": 8
|
||||
}
|
||||
], null, 2);
|
||||
|
||||
Editor.Message.request('asset-db', 'create-asset', fullPath, sceneContent).then((result: any) => {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
uuid: result.uuid,
|
||||
url: result.url,
|
||||
name: sceneName,
|
||||
message: `Scene '${sceneName}' created successfully`
|
||||
}
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async getSceneHierarchy(includeComponents: boolean = false): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// 优先尝试使用 Editor API 查询场景节点树
|
||||
Editor.Message.request('scene', 'query-node-tree').then((tree: any) => {
|
||||
if (tree) {
|
||||
const hierarchy = this.buildHierarchy(tree, includeComponents);
|
||||
resolve({
|
||||
success: true,
|
||||
data: hierarchy
|
||||
});
|
||||
} else {
|
||||
resolve({ success: false, error: 'No scene hierarchy available' });
|
||||
}
|
||||
}).catch((err: Error) => {
|
||||
// 备用方案:使用场景脚本
|
||||
const options = {
|
||||
name: 'cocos-mcp-server',
|
||||
method: 'getSceneHierarchy',
|
||||
args: [includeComponents]
|
||||
};
|
||||
|
||||
Editor.Message.request('scene', 'execute-scene-script', options).then((result: any) => {
|
||||
resolve(result);
|
||||
}).catch((err2: Error) => {
|
||||
resolve({ success: false, error: `Direct API failed: ${err.message}, Scene script failed: ${err2.message}` });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private buildHierarchy(node: any, includeComponents: boolean): any {
|
||||
const nodeInfo: any = {
|
||||
uuid: node.uuid,
|
||||
name: node.name,
|
||||
type: node.type,
|
||||
active: node.active,
|
||||
children: []
|
||||
};
|
||||
|
||||
if (includeComponents && node.__comps__) {
|
||||
nodeInfo.components = node.__comps__.map((comp: any) => ({
|
||||
type: comp.__type__ || 'Unknown',
|
||||
enabled: comp.enabled !== undefined ? comp.enabled : true
|
||||
}));
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
nodeInfo.children = node.children.map((child: any) =>
|
||||
this.buildHierarchy(child, includeComponents)
|
||||
);
|
||||
}
|
||||
|
||||
return nodeInfo;
|
||||
}
|
||||
|
||||
private async saveSceneAs(path: string): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
// save-as-scene API 不接受路径参数,会弹出对话框让用户选择
|
||||
(Editor.Message.request as any)('scene', 'save-as-scene').then(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
path: path,
|
||||
message: `Scene save-as dialog opened`
|
||||
}
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async closeScene(): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
Editor.Message.request('scene', 'close-scene').then(() => {
|
||||
resolve({
|
||||
success: true,
|
||||
message: 'Scene closed successfully'
|
||||
});
|
||||
}).catch((err: Error) => {
|
||||
resolve({ success: false, error: err.message });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
247
source/tools/server-tools.ts
Normal file
247
source/tools/server-tools.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import { ToolDefinition, ToolResponse, ToolExecutor } from '../types';
|
||||
|
||||
export class ServerTools implements ToolExecutor {
|
||||
getTools(): ToolDefinition[] {
|
||||
return [
|
||||
{
|
||||
name: 'get_server_info',
|
||||
description: 'Get server information',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'broadcast_custom_message',
|
||||
description: 'Broadcast a custom message',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message: {
|
||||
type: 'string',
|
||||
description: 'Message name'
|
||||
},
|
||||
data: {
|
||||
description: 'Message data (optional)'
|
||||
}
|
||||
},
|
||||
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',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {}
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
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();
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${toolName}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async getServerInfo(): 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
|
||||
};
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
server: info,
|
||||
message: 'Server information retrieved successfully'
|
||||
}
|
||||
});
|
||||
} catch (err: any) {
|
||||
resolve({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async broadcastCustomMessage(message: string, data?: any): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
if (data !== undefined) {
|
||||
Editor.Message.broadcast(message, data);
|
||||
} else {
|
||||
Editor.Message.broadcast(message);
|
||||
}
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
message: message,
|
||||
data: data,
|
||||
result: 'Message broadcasted successfully'
|
||||
}
|
||||
});
|
||||
} catch (err: any) {
|
||||
resolve({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async getEditorVersion(): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
const version = {
|
||||
editor: (Editor as any).versions?.editor || 'Unknown',
|
||||
cocos: (Editor as any).versions?.cocos || 'Unknown',
|
||||
node: process.version
|
||||
};
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
version: version,
|
||||
message: 'Editor version retrieved successfully'
|
||||
}
|
||||
});
|
||||
} catch (err: any) {
|
||||
resolve({ success: false, error: err.message });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async getProjectName(): Promise<ToolResponse> {
|
||||
return new Promise((resolve) => {
|
||||
try {
|
||||
const name = Editor.Project.name;
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
name: name,
|
||||
message: 'Project name 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;
|
||||
resolve({
|
||||
success: true,
|
||||
data: {
|
||||
path: path,
|
||||
message: 'Project path retrieved successfully'
|
||||
}
|
||||
});
|
||||
} 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