2026-01-29 13:47:38 +08:00
"use strict" ;
2026-02-03 19:55:51 +08:00
const { IpcManager } = require ( "./dist/IpcManager" ) ;
2026-01-29 13:47:38 +08:00
const http = require ( "http" ) ;
2026-02-10 09:14:50 +08:00
const pathModule = require ( "path" ) ;
2026-02-02 14:34:34 +08:00
const fs = require ( "fs" ) ;
2026-02-04 01:57:12 +08:00
const crypto = require ( "crypto" ) ;
2026-01-29 14:26:28 +08:00
2026-01-29 14:53:06 +08:00
let logBuffer = [ ] ; // 存储所有日志
let mcpServer = null ;
2026-02-01 13:30:11 +08:00
let isSceneBusy = false ;
2026-01-29 14:53:06 +08:00
let serverConfig = {
port : 3456 ,
active : false ,
} ;
2026-02-07 23:14:12 +08:00
/ * *
* 封装日志函数 , 同时发送给面板 、 保存到内存并在编辑器控制台打印
* @ param { 'info' | 'success' | 'warn' | 'error' } type 日志类型
* @ param { string } message 日志内容
* /
2026-01-29 14:53:06 +08:00
function addLog ( type , message ) {
const logEntry = {
time : new Date ( ) . toLocaleTimeString ( ) ,
type : type ,
content : message ,
} ;
logBuffer . push ( logEntry ) ;
Editor . Ipc . sendToPanel ( "mcp-bridge" , "mcp-bridge:on-log" , logEntry ) ;
2026-02-02 14:34:34 +08:00
// 【修改】确保所有日志都输出到编辑器控制台,以便用户查看
2026-01-29 14:53:06 +08:00
if ( type === "error" ) {
2026-02-02 14:34:34 +08:00
Editor . error ( ` [MCP] ${ message } ` ) ;
} else if ( type === "warn" ) {
Editor . warn ( ` [MCP] ${ message } ` ) ;
} else {
2026-01-29 14:53:06 +08:00
}
}
2026-02-07 23:14:12 +08:00
/ * *
* 获取完整的日志内容 ( 文本格式 )
* @ returns { string } 拼接后的日志字符串
* /
2026-02-02 14:34:34 +08:00
function getLogContent ( ) {
return logBuffer . map ( entry => ` [ ${ entry . time } ] [ ${ entry . type } ] ${ entry . content } ` ) . join ( '\n' ) ;
}
2026-02-07 23:14:12 +08:00
/ * *
* 生成新场景的 JSON 模板数据
* @ returns { string } 场景数据的 JSON 字符串
* /
2026-01-29 14:26:28 +08:00
const getNewSceneTemplate = ( ) => {
// 尝试获取 UUID 生成函数
let newId = "" ;
if ( Editor . Utils && Editor . Utils . uuid ) {
newId = Editor . Utils . uuid ( ) ;
} else if ( Editor . Utils && Editor . Utils . UuidUtils && Editor . Utils . UuidUtils . uuid ) {
newId = Editor . Utils . UuidUtils . uuid ( ) ;
} else {
// 兜底方案:如果找不到编辑器 API, 生成一个随机字符串
newId = Math . random ( ) . toString ( 36 ) . substring ( 2 , 15 ) ;
}
const sceneData = [
{
_ _type _ _ : "cc.SceneAsset" ,
_name : "" ,
_objFlags : 0 ,
_native : "" ,
scene : { _ _id _ _ : 1 } ,
} ,
{
_ _id _ _ : 1 ,
_ _type _ _ : "cc.Scene" ,
_name : "" ,
_objFlags : 0 ,
_parent : null ,
_children : [ ] ,
_active : true ,
_level : 0 ,
_components : [ ] ,
autoReleaseAssets : false ,
_id : newId ,
} ,
] ;
return JSON . stringify ( sceneData ) ;
} ;
2026-01-29 13:47:38 +08:00
2026-02-07 23:14:12 +08:00
/ * *
* 获取所有支持的 MCP 工具列表定义
* @ returns { Array < Object > } 工具定义数组
* /
2026-01-29 15:55:38 +08:00
const getToolsList = ( ) => {
2026-02-10 00:38:38 +08:00
const globalPrecautions = "【AI 安全守则】: 1. 执行任何写操作前必须先通过 get_scene_hierarchy 或 manage_components(get) 验证主体存在。 2. 严禁基于假设盲目猜测属性名。 3. 资源属性(如 cc.Prefab) 必须通过 UUID 进行赋值。" ;
2026-01-29 15:55:38 +08:00
return [
{
name : "get_selected_node" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 获取当前编辑器中选中的节点 ID。建议获取后立即调用 get_scene_hierarchy 确认该节点是否仍存在于当前场景中。 ` ,
2026-01-29 15:55:38 +08:00
inputSchema : { type : "object" , properties : { } } ,
} ,
{
name : "set_node_name" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 修改指定节点的名称 ` ,
2026-01-29 15:55:38 +08:00
inputSchema : {
type : "object" ,
properties : {
id : { type : "string" , description : "节点的 UUID" } ,
newName : { type : "string" , description : "新的节点名称" } ,
} ,
required : [ "id" , "newName" ] ,
} ,
} ,
{
name : "save_scene" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 保存当前场景的修改 ` ,
2026-01-29 15:55:38 +08:00
inputSchema : { type : "object" , properties : { } } ,
} ,
{
name : "get_scene_hierarchy" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 获取当前场景的完整节点树结构(包括 UUID、名称和层级关系) ` ,
2026-01-29 15:55:38 +08:00
inputSchema : { type : "object" , properties : { } } ,
} ,
{
name : "update_node_transform" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 修改节点的坐标、缩放或颜色。执行前必须调用 get_scene_hierarchy 确保 node ID 有效。 ` ,
2026-01-29 15:55:38 +08:00
inputSchema : {
type : "object" ,
properties : {
id : { type : "string" , description : "节点 UUID" } ,
x : { type : "number" } ,
y : { type : "number" } ,
2026-02-10 14:00:02 +08:00
width : { type : "number" } ,
height : { type : "number" } ,
2026-01-29 15:55:38 +08:00
scaleX : { type : "number" } ,
scaleY : { type : "number" } ,
color : { type : "string" , description : "HEX 颜色代码如 #FF0000" } ,
} ,
required : [ "id" ] ,
} ,
} ,
{
name : "create_scene" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 在 assets 目录下创建一个新的场景文件。创建并通过 open_scene 打开后,请务必初始化基础节点(如 Canvas 和 Camera) 。 ` ,
2026-01-29 15:55:38 +08:00
inputSchema : {
type : "object" ,
properties : {
sceneName : { type : "string" , description : "场景名称" } ,
} ,
required : [ "sceneName" ] ,
} ,
} ,
{
name : "create_prefab" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 将场景中的某个节点保存为预制体资源 ` ,
2026-01-29 15:55:38 +08:00
inputSchema : {
type : "object" ,
properties : {
nodeId : { type : "string" , description : "节点 UUID" } ,
prefabName : { type : "string" , description : "预制体名称" } ,
} ,
required : [ "nodeId" , "prefabName" ] ,
} ,
} ,
{
name : "open_scene" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 打开场景文件。注意: 这是一个异步且耗时的操作, 打开后请等待几秒。重要: 如果是新创建或空的场景, 请务必先创建并初始化基础节点( Canvas/Camera) 。 ` ,
2026-01-29 15:55:38 +08:00
inputSchema : {
type : "object" ,
properties : {
url : {
type : "string" ,
description : "场景资源路径,如 db://assets/NewScene.fire" ,
} ,
} ,
required : [ "url" ] ,
} ,
} ,
{
name : "create_node" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 在当前场景中创建一个新节点。重要提示: 1. 如果指定 parentId, 必须先通过 get_scene_hierarchy 确保该父节点真实存在且未被删除。2. 类型说明:'sprite' (100x100 尺寸 + 默认贴图), 'button' (150x50 尺寸 + 深色底图 + Button组件), 'label' (120x40 尺寸 + Label组件), 'empty' (纯空节点)。 ` ,
2026-01-29 15:55:38 +08:00
inputSchema : {
type : "object" ,
properties : {
name : { type : "string" , description : "节点名称" } ,
parentId : {
type : "string" ,
description : "父节点 UUID (可选,不传则挂在场景根部)" ,
} ,
type : {
type : "string" ,
2026-02-07 23:14:12 +08:00
enum : [ "empty" , "sprite" , "label" , "button" ] ,
2026-01-29 15:55:38 +08:00
description : "节点预设类型" ,
} ,
} ,
required : [ "name" ] ,
} ,
} ,
2026-01-31 16:48:21 +08:00
{
name : "manage_components" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 管理节点组件。重要提示: 1. 操作前必须调用 get_scene_hierarchy 确认 nodeId 对应的节点仍然存在。2. 添加前先用 'get' 检查是否已存在。3. 添加 cc.Sprite 后必须设置 spriteFrame 属性, 否则节点不显示。4. 创建按钮时,请确保目标节点有足够的 width 和 height 作为点击区域。5. 赋值或更新属性前, 必须确保目标属性在组件上真实存在, 严禁盲目操作不存在的属性。6. 对于资源类属性(如 cc.Prefab, sp.SkeletonData) , 传递资源的 UUID。插件会自动进行异步加载并正确序列化, 避免 Inspector 出现 Type Error。 ` ,
2026-01-31 16:48:21 +08:00
inputSchema : {
type : "object" ,
properties : {
nodeId : { type : "string" , description : "节点 UUID" } ,
2026-02-03 19:55:51 +08:00
action : { type : "string" , enum : [ "add" , "remove" , "update" , "get" ] , description : "操作类型 (add: 添加组件, remove: 移除组件, update: 更新组件属性, get: 获取组件列表)" } ,
componentType : { type : "string" , description : "组件类型,如 cc.Sprite (add/update 操作需要)" } ,
componentId : { type : "string" , description : "组件 ID (remove/update 操作可选)" } ,
properties : { type : "object" , description : "组件属性 (add/update 操作使用). 支持智能解析: 如果属性类型是组件但提供了节点UUID, 会自动查找对应组件。" } ,
2026-01-31 16:48:21 +08:00
} ,
required : [ "nodeId" , "action" ] ,
} ,
} ,
{
name : "manage_script" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 管理脚本文件。注意:创建或修改脚本后,编辑器需要时间进行编译(通常几秒钟)。新脚本在编译完成前无法作为组件添加到节点。建议在 create 后调用 refresh_editor, 或等待一段时间后再使用 manage_components。 ` ,
2026-01-31 16:48:21 +08:00
inputSchema : {
type : "object" ,
properties : {
action : { type : "string" , enum : [ "create" , "delete" , "read" , "write" ] , description : "操作类型" } ,
path : { type : "string" , description : "脚本路径,如 db://assets/scripts/NewScript.js" } ,
content : { type : "string" , description : "脚本内容 (用于 create 和 write 操作)" } ,
name : { type : "string" , description : "脚本名称 (用于 create 操作)" } ,
} ,
required : [ "action" , "path" ] ,
} ,
} ,
{
name : "batch_execute" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 批处理执行多个操作 ` ,
2026-01-31 16:48:21 +08:00
inputSchema : {
type : "object" ,
properties : {
operations : {
type : "array" ,
items : {
type : "object" ,
properties : {
tool : { type : "string" , description : "工具名称" } ,
params : { type : "object" , description : "工具参数" } ,
} ,
required : [ "tool" , "params" ] ,
} ,
description : "操作列表" ,
} ,
} ,
required : [ "operations" ] ,
} ,
} ,
{
name : "manage_asset" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 管理资源 ` ,
2026-01-31 16:48:21 +08:00
inputSchema : {
type : "object" ,
properties : {
action : { type : "string" , enum : [ "create" , "delete" , "move" , "get_info" ] , description : "操作类型" } ,
path : { type : "string" , description : "资源路径,如 db://assets/textures" } ,
targetPath : { type : "string" , description : "目标路径 (用于 move 操作)" } ,
content : { type : "string" , description : "资源内容 (用于 create 操作)" } ,
} ,
required : [ "action" , "path" ] ,
} ,
} ,
2026-01-31 19:36:55 +08:00
{
name : "scene_management" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 场景管理 ` ,
2026-01-31 19:36:55 +08:00
inputSchema : {
type : "object" ,
properties : {
2026-02-01 13:30:11 +08:00
action : {
type : "string" ,
enum : [ "create" , "delete" , "duplicate" , "get_info" ] ,
description : "操作类型" ,
} ,
2026-01-31 19:36:55 +08:00
path : { type : "string" , description : "场景路径,如 db://assets/scenes/NewScene.fire" } ,
targetPath : { type : "string" , description : "目标路径 (用于 duplicate 操作)" } ,
name : { type : "string" , description : "场景名称 (用于 create 操作)" } ,
} ,
required : [ "action" , "path" ] ,
} ,
} ,
{
name : "prefab_management" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 预制体管理 ` ,
2026-01-31 19:36:55 +08:00
inputSchema : {
type : "object" ,
properties : {
2026-02-01 13:30:11 +08:00
action : {
type : "string" ,
enum : [ "create" , "update" , "instantiate" , "get_info" ] ,
description : "操作类型" ,
} ,
2026-01-31 19:36:55 +08:00
path : { type : "string" , description : "预制体路径,如 db://assets/prefabs/NewPrefab.prefab" } ,
nodeId : { type : "string" , description : "节点 ID (用于 create 操作)" } ,
parentId : { type : "string" , description : "父节点 ID (用于 instantiate 操作)" } ,
} ,
required : [ "action" , "path" ] ,
} ,
} ,
2026-02-01 13:30:11 +08:00
{
name : "manage_editor" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 管理编辑器 ` ,
2026-02-01 13:30:11 +08:00
inputSchema : {
type : "object" ,
properties : {
action : {
type : "string" ,
enum : [ "get_selection" , "set_selection" , "refresh_editor" ] ,
description : "操作类型" ,
} ,
target : {
type : "string" ,
enum : [ "node" , "asset" ] ,
description : "目标类型 (用于 set_selection 操作)" ,
} ,
properties : { type : "object" , description : "操作属性" } ,
} ,
required : [ "action" ] ,
} ,
} ,
{
name : "find_gameobjects" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 查找游戏对象 ` ,
2026-02-01 13:30:11 +08:00
inputSchema : {
type : "object" ,
properties : {
conditions : { type : "object" , description : "查找条件" } ,
recursive : { type : "boolean" , default : true , description : "是否递归查找" } ,
} ,
required : [ "conditions" ] ,
} ,
} ,
{
name : "manage_material" ,
2026-02-10 09:14:50 +08:00
description : ` ${ globalPrecautions } 管理材质。支持创建、获取信息以及更新 Shader、Defines 和 Uniforms 参数。 ` ,
2026-02-01 13:30:11 +08:00
inputSchema : {
type : "object" ,
properties : {
2026-02-10 09:14:50 +08:00
action : { type : "string" , enum : [ "create" , "delete" , "get_info" , "update" ] , description : "操作类型" } ,
2026-02-01 13:30:11 +08:00
path : { type : "string" , description : "材质路径,如 db://assets/materials/NewMaterial.mat" } ,
2026-02-10 09:14:50 +08:00
properties : {
type : "object" ,
description : "材质属性 (add/update 操作使用)" ,
properties : {
shaderUuid : { type : "string" , description : "关联的 Shader (Effect) UUID" } ,
defines : { type : "object" , description : "预编译宏定义" } ,
uniforms : { type : "object" , description : "Uniform 参数列表" }
}
} ,
2026-02-01 13:30:11 +08:00
} ,
required : [ "action" , "path" ] ,
} ,
} ,
{
name : "manage_texture" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 管理纹理 ` ,
2026-02-01 13:30:11 +08:00
inputSchema : {
type : "object" ,
properties : {
2026-02-10 14:00:02 +08:00
action : { type : "string" , enum : [ "create" , "delete" , "get_info" , "update" ] , description : "操作类型" } ,
2026-02-01 13:30:11 +08:00
path : { type : "string" , description : "纹理路径,如 db://assets/textures/NewTexture.png" } ,
properties : { type : "object" , description : "纹理属性" } ,
} ,
required : [ "action" , "path" ] ,
} ,
} ,
2026-02-10 09:14:50 +08:00
{
name : "manage_shader" ,
description : ` ${ globalPrecautions } 管理着色器 (Effect)。支持创建、读取、更新、删除和获取信息。 ` ,
inputSchema : {
type : "object" ,
properties : {
action : { type : "string" , enum : [ "create" , "delete" , "read" , "write" , "get_info" ] , description : "操作类型" } ,
path : { type : "string" , description : "着色器路径,如 db://assets/effects/NewEffect.effect" } ,
content : { type : "string" , description : "着色器内容 (create/write)" } ,
} ,
required : [ "action" , "path" ] ,
} ,
} ,
2026-02-01 13:30:11 +08:00
{
name : "execute_menu_item" ,
2026-02-10 23:20:56 +08:00
description : ` ${ globalPrecautions } 执行菜单项。对于节点删除,请使用 "delete-node:UUID" 格式以确保精确执行。对于保存、撤销等操作,请优先使用专用工具 (save_scene, manage_undo)。 ` ,
2026-02-01 13:30:11 +08:00
inputSchema : {
type : "object" ,
properties : {
2026-02-10 23:20:56 +08:00
menuPath : { type : "string" , description : "菜单项路径 (支持 'Project/Build' 或 'delete-node:UUID')" } ,
2026-02-01 13:30:11 +08:00
} ,
required : [ "menuPath" ] ,
} ,
} ,
{
name : "apply_text_edits" ,
2026-02-10 23:20:56 +08:00
description : ` ${ globalPrecautions } 对文件应用文本编辑。**专用于修改脚本源代码 (.js, .ts) 或文本文件**。如果要修改场景节点属性,请使用 'manage_components'。 ` ,
2026-02-01 13:30:11 +08:00
inputSchema : {
type : "object" ,
properties : {
2026-02-10 23:20:56 +08:00
edits : {
type : "array" ,
items : {
type : "object" ,
properties : {
type : { type : "string" , enum : [ "insert" , "delete" , "replace" ] , description : "操作类型" } ,
start : { type : "number" , description : "起始偏移量 (字符索引)" } ,
end : { type : "number" , description : "结束偏移量 (delete/replace 用)" } ,
position : { type : "number" , description : "插入位置 (insert 用)" } ,
text : { type : "string" , description : "要插入或替换的文本" } ,
} ,
} ,
description : "编辑操作列表。请严格使用偏移量(offset)而非行号。" ,
} ,
filePath : { type : "string" , description : "文件路径 (db://...)" } ,
2026-02-01 13:30:11 +08:00
} ,
required : [ "filePath" , "edits" ] ,
} ,
} ,
{
name : "read_console" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 读取控制台 ` ,
2026-02-01 13:30:11 +08:00
inputSchema : {
type : "object" ,
properties : {
limit : { type : "number" , description : "输出限制" } ,
type : { type : "string" , enum : [ "log" , "error" , "warn" ] , description : "输出类型" } ,
} ,
} ,
} ,
{
name : "validate_script" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 验证脚本 ` ,
2026-02-01 13:30:11 +08:00
inputSchema : {
type : "object" ,
properties : {
filePath : { type : "string" , description : "脚本路径" } ,
} ,
required : [ "filePath" ] ,
} ,
} ,
{
name : "find_in_file" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 在项目中全局搜索文本内容 ` ,
2026-02-01 13:30:11 +08:00
inputSchema : {
type : "object" ,
properties : {
query : { type : "string" , description : "搜索关键词" } ,
extensions : {
type : "array" ,
items : { type : "string" } ,
description : "文件后缀列表 (例如 ['.js', '.ts'])" ,
default : [ ".js" , ".ts" , ".json" , ".fire" , ".prefab" , ".xml" , ".txt" , ".md" ]
} ,
includeSubpackages : { type : "boolean" , default : true , description : "是否搜索子包 (暂时默认搜索 assets 目录)" }
} ,
required : [ "query" ]
}
} ,
{
name : "manage_undo" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 管理编辑器的撤销和重做历史 ` ,
2026-02-01 13:30:11 +08:00
inputSchema : {
type : "object" ,
properties : {
action : {
type : "string" ,
enum : [ "undo" , "redo" , "begin_group" , "end_group" , "cancel_group" ] ,
description : "操作类型"
} ,
description : { type : "string" , description : "撤销组的描述 (用于 begin_group)" }
} ,
required : [ "action" ]
}
} ,
{
name : "manage_vfx" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 管理全场景特效 (粒子系统)。重要提示:在创建或更新前,必须通过 get_scene_hierarchy 或 manage_components 确认父节点或目标节点的有效性。严禁对不存在的对象进行操作。 ` ,
2026-02-01 13:30:11 +08:00
inputSchema : {
type : "object" ,
properties : {
action : {
type : "string" ,
enum : [ "create" , "update" , "get_info" ] ,
description : "操作类型"
} ,
nodeId : { type : "string" , description : "节点 UUID (用于 update/get_info)" } ,
properties : {
type : "object" ,
description : "粒子系统属性 (用于 create/update)" ,
properties : {
duration : { type : "number" , description : "发射时长" } ,
emissionRate : { type : "number" , description : "发射速率" } ,
life : { type : "number" , description : "生命周期" } ,
lifeVar : { type : "number" , description : "生命周期变化" } ,
startColor : { type : "string" , description : "起始颜色 (Hex)" } ,
endColor : { type : "string" , description : "结束颜色 (Hex)" } ,
startSize : { type : "number" , description : "起始大小" } ,
endSize : { type : "number" , description : "结束大小" } ,
speed : { type : "number" , description : "速度" } ,
angle : { type : "number" , description : "角度" } ,
gravity : { type : "object" , properties : { x : { type : "number" } , y : { type : "number" } } } ,
file : { type : "string" , description : "粒子文件路径 (plist) 或 texture 路径" }
}
} ,
name : { type : "string" , description : "节点名称 (用于 create)" } ,
parentId : { type : "string" , description : "父节点 ID (用于 create)" }
} ,
required : [ "action" ]
}
2026-02-04 01:57:12 +08:00
} ,
{
name : "get_sha" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 获取指定文件的 SHA-256 哈希值 ` ,
2026-02-04 01:57:12 +08:00
inputSchema : {
type : "object" ,
properties : {
path : { type : "string" , description : "文件路径,如 db://assets/scripts/Test.ts" }
} ,
required : [ "path" ]
}
} ,
{
name : "manage_animation" ,
2026-02-10 00:38:38 +08:00
description : ` ${ globalPrecautions } 管理节点的动画组件。重要提示:在执行 play/pause 等操作前,必须先确认节点及其 Animation 组件存在。严禁操作空引用。 ` ,
2026-02-04 01:57:12 +08:00
inputSchema : {
type : "object" ,
properties : {
action : {
type : "string" ,
enum : [ "get_list" , "get_info" , "play" , "stop" , "pause" , "resume" ] ,
description : "操作类型"
} ,
nodeId : { type : "string" , description : "节点 UUID" } ,
clipName : { type : "string" , description : "动画剪辑名称 (用于 play)" }
} ,
required : [ "action" , "nodeId" ]
}
2026-02-01 13:30:11 +08:00
}
2026-01-29 15:55:38 +08:00
] ;
} ;
2026-02-01 13:30:11 +08:00
2026-01-29 13:47:38 +08:00
module . exports = {
"scene-script" : "scene-script.js" ,
2026-02-07 23:14:12 +08:00
/ * *
* 插件加载时的回调
* /
2026-01-29 13:47:38 +08:00
load ( ) {
2026-01-29 14:53:06 +08:00
addLog ( "info" , "MCP Bridge Plugin Loaded" ) ;
// 读取配置
let profile = this . getProfile ( ) ;
serverConfig . port = profile . get ( "last-port" ) || 3456 ;
let autoStart = profile . get ( "auto-start" ) ;
if ( autoStart ) {
addLog ( "info" , "Auto-start is enabled. Initializing server..." ) ;
// 延迟一点启动,确保编辑器环境完全就绪
setTimeout ( ( ) => {
this . startServer ( serverConfig . port ) ;
} , 1000 ) ;
}
} ,
2026-02-07 23:14:12 +08:00
/ * *
* 获取插件配置文件的辅助函数
* @ returns { Object } Editor . Profile 实例
* /
2026-01-29 14:53:06 +08:00
getProfile ( ) {
// 'local' 表示存储在项目本地( local/mcp-bridge.json)
return Editor . Profile . load ( "profile://local/mcp-bridge.json" , "mcp-bridge" ) ;
2026-01-29 13:47:38 +08:00
} ,
2026-02-07 23:14:12 +08:00
/ * *
* 插件卸载时的回调
* /
2026-01-29 13:47:38 +08:00
unload ( ) {
2026-01-29 14:53:06 +08:00
this . stopServer ( ) ;
2026-01-29 13:47:38 +08:00
} ,
2026-02-07 23:14:12 +08:00
/ * *
* 启动 HTTP 服务器
* @ param { number } port 监听端口
* /
2026-01-29 14:53:06 +08:00
startServer ( port ) {
if ( mcpServer ) this . stopServer ( ) ;
2026-01-29 13:47:38 +08:00
2026-01-29 14:53:06 +08:00
try {
mcpServer = http . createServer ( ( req , res ) => {
res . setHeader ( "Content-Type" , "application/json" ) ;
2026-01-29 15:55:38 +08:00
res . setHeader ( "Access-Control-Allow-Origin" , "*" ) ;
2026-01-29 13:47:38 +08:00
2026-01-29 14:53:06 +08:00
let body = "" ;
req . on ( "data" , ( chunk ) => {
body += chunk ;
} ) ;
req . on ( "end" , ( ) => {
2026-01-29 15:55:38 +08:00
const url = req . url ;
if ( url === "/list-tools" ) {
const tools = getToolsList ( ) ;
addLog ( "info" , ` AI Client requested tool list ` ) ;
// 明确返回成功结构
res . writeHead ( 200 ) ;
return res . end ( JSON . stringify ( { tools : tools } ) ) ;
2026-02-02 14:34:34 +08:00
res . writeHead ( 200 ) ;
return res . end ( JSON . stringify ( { tools : tools } ) ) ;
}
if ( url === "/list-resources" ) {
const resources = this . getResourcesList ( ) ;
addLog ( "info" , ` AI Client requested resource list ` ) ;
res . writeHead ( 200 ) ;
return res . end ( JSON . stringify ( { resources : resources } ) ) ;
}
if ( url === "/read-resource" ) {
try {
const { uri } = JSON . parse ( body || "{}" ) ;
addLog ( "mcp" , ` READ -> [ ${ uri } ] ` ) ;
this . handleReadResource ( uri , ( err , content ) => {
if ( err ) {
addLog ( "error" , ` 读取失败: ${ err } ` ) ;
res . writeHead ( 500 ) ;
return res . end ( JSON . stringify ( { error : err } ) ) ;
}
addLog ( "success" , ` 读取成功: ${ uri } ` ) ;
res . writeHead ( 200 ) ;
// 返回 MCP Resource 格式: { contents: [{ uri, mimeType, text }] }
res . end ( JSON . stringify ( {
contents : [ {
uri : uri ,
mimeType : "application/json" ,
text : typeof content === 'string' ? content : JSON . stringify ( content )
} ]
} ) ) ;
} ) ;
} catch ( e ) {
res . writeHead ( 500 ) ;
res . end ( JSON . stringify ( { error : e . message } ) ) ;
}
return ;
2026-01-29 15:55:38 +08:00
}
if ( url === "/call-tool" ) {
try {
const { name , arguments : args } = JSON . parse ( body || "{}" ) ;
addLog ( "mcp" , ` REQ -> [ ${ name } ] ` ) ;
2026-01-29 14:53:06 +08:00
2026-01-29 15:55:38 +08:00
this . handleMcpCall ( name , args , ( err , result ) => {
const response = {
content : [
{
type : "text" ,
text : err
? ` Error: ${ err } `
: typeof result === "object"
? JSON . stringify ( result , null , 2 )
: result ,
} ,
] ,
} ;
2026-02-01 13:30:11 +08:00
if ( err ) {
addLog ( "error" , ` RES <- [ ${ name } ] 失败: ${ err } ` ) ;
} else {
// 成功时尝试捕获简单的结果预览(如果是字符串或简短对象)
let preview = "" ;
if ( typeof result === 'string' ) {
preview = result . length > 100 ? result . substring ( 0 , 100 ) + "..." : result ;
} else if ( typeof result === 'object' ) {
try {
const jsonStr = JSON . stringify ( result ) ;
preview = jsonStr . length > 100 ? jsonStr . substring ( 0 , 100 ) + "..." : jsonStr ;
} catch ( e ) {
preview = "Object (Circular/Unserializable)" ;
}
}
addLog ( "success" , ` RES <- [ ${ name } ] 成功 : ${ preview } ` ) ;
}
2026-01-29 15:55:38 +08:00
res . writeHead ( 200 ) ;
res . end ( JSON . stringify ( response ) ) ;
} ) ;
} catch ( e ) {
2026-02-01 13:30:11 +08:00
if ( e instanceof SyntaxError ) {
addLog ( "error" , ` JSON Parse Error: ${ e . message } ` ) ;
res . writeHead ( 400 ) ;
res . end ( JSON . stringify ( { error : "Invalid JSON" } ) ) ;
} else {
addLog ( "error" , ` Internal Server Error: ${ e . message } ` ) ;
res . writeHead ( 500 ) ;
res . end ( JSON . stringify ( { error : e . message } ) ) ;
}
2026-01-29 13:47:38 +08:00
}
2026-01-29 15:55:38 +08:00
return ;
2026-01-29 13:47:38 +08:00
}
2026-01-29 15:55:38 +08:00
// --- 兜底处理 (404) ---
res . writeHead ( 404 ) ;
res . end ( JSON . stringify ( { error : "Not Found" , url : url } ) ) ;
2026-01-29 14:53:06 +08:00
} ) ;
} ) ;
2026-01-29 15:55:38 +08:00
mcpServer . on ( "error" , ( e ) => {
addLog ( "error" , ` Server Error: ${ e . message } ` ) ;
} ) ;
2026-01-29 14:53:06 +08:00
mcpServer . listen ( port , ( ) => {
serverConfig . active = true ;
2026-01-29 15:55:38 +08:00
addLog ( "success" , ` MCP Server running at http://127.0.0.1: ${ port } ` ) ;
2026-01-29 14:53:06 +08:00
Editor . Ipc . sendToPanel ( "mcp-bridge" , "mcp-bridge:state-changed" , serverConfig ) ;
} ) ;
// 启动成功后顺便存一下端口
this . getProfile ( ) . set ( "last-port" , port ) ;
this . getProfile ( ) . save ( ) ;
} catch ( e ) {
addLog ( "error" , ` Failed to start server: ${ e . message } ` ) ;
}
} ,
stopServer ( ) {
if ( mcpServer ) {
mcpServer . close ( ) ;
mcpServer = null ;
serverConfig . active = false ;
addLog ( "warn" , "MCP Server stopped" ) ;
Editor . Ipc . sendToPanel ( "mcp-bridge" , "mcp-bridge:state-changed" , serverConfig ) ;
}
} ,
2026-02-02 14:34:34 +08:00
getResourcesList ( ) {
return [
{
uri : "cocos://hierarchy" ,
name : "Scene Hierarchy" ,
description : "当前场景层级的 JSON 快照" ,
mimeType : "application/json"
} ,
{
uri : "cocos://selection" ,
name : "Current Selection" ,
description : "当前选中节点的 UUID 列表" ,
mimeType : "application/json"
} ,
{
uri : "cocos://logs/latest" ,
name : "Editor Logs" ,
description : "最新的编辑器日志 (内存缓存)" ,
mimeType : "text/plain"
}
] ;
} ,
handleReadResource ( uri , callback ) {
let parsed ;
try {
parsed = new URL ( uri ) ;
} catch ( e ) {
return callback ( ` Invalid URI: ${ uri } ` ) ;
}
if ( parsed . protocol !== "cocos:" ) {
return callback ( ` Unsupported protocol: ${ parsed . protocol } ` ) ;
}
const type = parsed . hostname ; // hierarchy, selection, logs
switch ( type ) {
case "hierarchy" :
// 注意: query-hierarchy 是异步的
Editor . Ipc . sendToPanel ( "scene" , "scene:query-hierarchy" , ( err , sceneId , hierarchy ) => {
if ( err ) return callback ( err ) ;
callback ( null , JSON . stringify ( hierarchy , null , 2 ) ) ;
} ) ;
break ;
case "selection" :
const selection = Editor . Selection . curSelection ( "node" ) ;
callback ( null , JSON . stringify ( selection ) ) ;
break ;
case "logs" :
callback ( null , getLogContent ( ) ) ;
break ;
default :
callback ( ` Resource not found: ${ uri } ` ) ;
break ;
}
} ,
2026-02-07 23:14:12 +08:00
/ * *
* 处理来自 HTTP 的 MCP 调用请求
* @ param { string } name 工具名称
* @ param { Object } args 工具参数
* @ param { Function } callback 完成回调 ( err , result )
* /
2026-01-29 14:53:06 +08:00
handleMcpCall ( name , args , callback ) {
2026-01-31 16:48:21 +08:00
if ( isSceneBusy && ( name === "save_scene" || name === "create_node" ) ) {
2026-02-07 22:29:17 +08:00
return callback ( "编辑器正忙(正在处理场景),请稍候。" ) ;
2026-01-31 16:48:21 +08:00
}
2026-01-29 14:53:06 +08:00
switch ( name ) {
case "get_selected_node" :
const ids = Editor . Selection . curSelection ( "node" ) ;
callback ( null , ids ) ;
break ;
case "set_node_name" :
2026-02-01 13:30:11 +08:00
// 使用 scene:set-property 以支持撤销
Editor . Ipc . sendToPanel ( "scene" , "scene:set-property" , {
id : args . id ,
path : "name" ,
type : "String" ,
value : args . newName ,
isSubProp : false
} ) ;
2026-02-07 22:29:17 +08:00
callback ( null , ` 节点名称已更新为 ${ args . newName } ` ) ;
2026-01-29 14:53:06 +08:00
break ;
case "save_scene" :
2026-01-31 16:48:21 +08:00
isSceneBusy = true ;
2026-02-07 22:29:17 +08:00
addLog ( "info" , "准备保存场景... 等待 UI 同步。" ) ;
2026-02-04 01:57:12 +08:00
Editor . Ipc . sendToPanel ( "scene" , "scene:stash-and-save" ) ;
isSceneBusy = false ;
2026-02-07 22:29:17 +08:00
addLog ( "info" , "安全保存已完成。" ) ;
callback ( null , "场景保存成功。" ) ;
2026-01-29 14:53:06 +08:00
break ;
case "get_scene_hierarchy" :
Editor . Scene . callSceneScript ( "mcp-bridge" , "get-hierarchy" , callback ) ;
break ;
case "update_node_transform" :
2026-02-02 14:34:34 +08:00
// 直接调用场景脚本更新属性,绕过可能导致 "Unknown object" 的复杂 Undo 系统
Editor . Scene . callSceneScript ( "mcp-bridge" , "update-node-transform" , args , ( err , result ) => {
if ( err ) {
addLog ( "error" , ` Transform update failed: ${ err } ` ) ;
callback ( err ) ;
} else {
2026-02-07 22:29:17 +08:00
callback ( null , "变换信息已更新" ) ;
2026-02-01 13:30:11 +08:00
}
2026-02-02 14:34:34 +08:00
} ) ;
2026-01-29 14:53:06 +08:00
break ;
case "create_scene" :
const sceneUrl = ` db://assets/ ${ args . sceneName } .fire ` ;
if ( Editor . assetdb . exists ( sceneUrl ) ) {
2026-02-07 22:29:17 +08:00
return callback ( "场景已存在" ) ;
2026-01-29 14:53:06 +08:00
}
Editor . assetdb . create ( sceneUrl , getNewSceneTemplate ( ) , ( err ) => {
2026-02-07 22:29:17 +08:00
callback ( err , err ? null : ` 标准场景已创建于 ${ sceneUrl } ` ) ;
2026-01-29 14:53:06 +08:00
} ) ;
break ;
case "create_prefab" :
const prefabUrl = ` db://assets/ ${ args . prefabName } .prefab ` ;
Editor . Ipc . sendToMain ( "scene:create-prefab" , args . nodeId , prefabUrl ) ;
2026-02-07 22:29:17 +08:00
callback ( null , ` 命令已发送:正在创建预制体 ' ${ args . prefabName } ' ` ) ;
2026-01-29 14:53:06 +08:00
break ;
case "open_scene" :
2026-01-31 16:48:21 +08:00
isSceneBusy = true ; // 锁定
2026-01-29 14:53:06 +08:00
const openUuid = Editor . assetdb . urlToUuid ( args . url ) ;
if ( openUuid ) {
Editor . Ipc . sendToMain ( "scene:open-by-uuid" , openUuid ) ;
2026-01-31 16:48:21 +08:00
setTimeout ( ( ) => {
isSceneBusy = false ;
2026-02-07 22:29:17 +08:00
callback ( null , ` 成功:正在打开场景 ${ args . url } ` ) ;
2026-01-31 16:48:21 +08:00
} , 2000 ) ;
2026-01-29 14:53:06 +08:00
} else {
2026-01-31 16:48:21 +08:00
isSceneBusy = false ;
2026-02-07 22:29:17 +08:00
callback ( ` 找不到路径为 ${ args . url } 的资源 ` ) ;
2026-01-29 14:53:06 +08:00
}
break ;
case "create_node" :
2026-02-07 23:14:12 +08:00
if ( args . type === "sprite" || args . type === "button" ) {
const splashUuid = Editor . assetdb . urlToUuid ( "db://internal/image/default_sprite_splash.png/default_sprite_splash" ) ;
args . defaultSpriteUuid = splashUuid ;
}
2026-01-29 14:53:06 +08:00
Editor . Scene . callSceneScript ( "mcp-bridge" , "create-node" , args , callback ) ;
break ;
2026-01-31 16:48:21 +08:00
case "manage_components" :
Editor . Scene . callSceneScript ( "mcp-bridge" , "manage-components" , args , callback ) ;
break ;
case "manage_script" :
this . manageScript ( args , callback ) ;
break ;
case "batch_execute" :
this . batchExecute ( args , callback ) ;
break ;
case "manage_asset" :
this . manageAsset ( args , callback ) ;
break ;
2026-01-31 19:36:55 +08:00
case "scene_management" :
this . sceneManagement ( args , callback ) ;
break ;
case "prefab_management" :
this . prefabManagement ( args , callback ) ;
break ;
2026-02-01 13:30:11 +08:00
case "manage_editor" :
this . manageEditor ( args , callback ) ;
break ;
2026-02-04 01:57:12 +08:00
case "get_sha" :
this . getSha ( args , callback ) ;
break ;
case "manage_animation" :
this . manageAnimation ( args , callback ) ;
break ;
2026-02-01 13:30:11 +08:00
case "find_gameobjects" :
Editor . Scene . callSceneScript ( "mcp-bridge" , "find-gameobjects" , args , callback ) ;
break ;
case "manage_material" :
this . manageMaterial ( args , callback ) ;
break ;
case "manage_texture" :
this . manageTexture ( args , callback ) ;
break ;
2026-02-10 09:14:50 +08:00
case "manage_shader" :
this . manageShader ( args , callback ) ;
break ;
2026-02-01 13:30:11 +08:00
case "execute_menu_item" :
this . executeMenuItem ( args , callback ) ;
break ;
case "apply_text_edits" :
this . applyTextEdits ( args , callback ) ;
break ;
case "read_console" :
this . readConsole ( args , callback ) ;
break ;
case "validate_script" :
this . validateScript ( args , callback ) ;
break ;
case "find_in_file" :
this . findInFile ( args , callback ) ;
break ;
case "manage_undo" :
this . manageUndo ( args , callback ) ;
break ;
case "manage_vfx" :
// 【修复】在主进程预先解析 URL 为 UUID, 因为渲染进程(scene-script)无法访问 Editor.assetdb
if ( args . properties && args . properties . file ) {
if ( typeof args . properties . file === 'string' && args . properties . file . startsWith ( "db://" ) ) {
const uuid = Editor . assetdb . urlToUuid ( args . properties . file ) ;
if ( uuid ) {
args . properties . file = uuid ; // 替换为 UUID
} else {
console . warn ( ` Failed to resolve path to UUID: ${ args . properties . file } ` ) ;
}
}
}
// 预先获取默认贴图 UUID (尝试多个可能的路径)
const defaultPaths = [
"db://internal/image/default_sprite_splash" ,
"db://internal/image/default_sprite_splash.png" ,
"db://internal/image/default_particle" ,
"db://internal/image/default_particle.png"
] ;
for ( const path of defaultPaths ) {
const uuid = Editor . assetdb . urlToUuid ( path ) ;
if ( uuid ) {
args . defaultSpriteUuid = uuid ;
addLog ( "info" , ` [mcp-bridge] Resolved Default Sprite UUID: ${ uuid } from ${ path } ` ) ;
break ;
}
}
if ( ! args . defaultSpriteUuid ) {
addLog ( "warn" , "[mcp-bridge] Failed to resolve any default sprite UUID." ) ;
}
Editor . Scene . callSceneScript ( "mcp-bridge" , "manage-vfx" , args , callback ) ;
break ;
2026-01-29 14:53:06 +08:00
default :
callback ( ` Unknown tool: ${ name } ` ) ;
break ;
}
} ,
2026-01-31 16:48:21 +08:00
2026-02-07 23:14:12 +08:00
/ * *
* 管理项目中的脚本文件 ( TS / JS )
* @ param { Object } args 参数
* @ param { Function } callback 完成回调
* /
2026-01-31 16:48:21 +08:00
manageScript ( args , callback ) {
2026-02-02 14:34:34 +08:00
const { action , path : scriptPath , content } = args ;
2026-01-31 16:48:21 +08:00
switch ( action ) {
case "create" :
2026-02-02 14:34:34 +08:00
if ( Editor . assetdb . exists ( scriptPath ) ) {
return callback ( ` Script already exists at ${ scriptPath } ` ) ;
2026-01-31 16:48:21 +08:00
}
// 确保父目录存在
2026-02-02 14:34:34 +08:00
const absolutePath = Editor . assetdb . urlToFspath ( scriptPath ) ;
const dirPath = path . dirname ( absolutePath ) ;
2026-01-31 16:48:21 +08:00
if ( ! fs . existsSync ( dirPath ) ) {
fs . mkdirSync ( dirPath , { recursive : true } ) ;
}
2026-02-01 13:30:11 +08:00
Editor . assetdb . create (
2026-02-02 14:34:34 +08:00
scriptPath ,
2026-02-01 13:30:11 +08:00
content ||
` const { ccclass, property } = cc._decorator;
2026-01-31 16:48:21 +08:00
@ ccclass
export default class NewScript extends cc . Component {
@ property ( cc . Label )
label : cc . Label = null ;
@ property
text : string = 'hello' ;
// LIFE-CYCLE CALLBACKS:
onLoad ( ) { }
start ( ) { }
update ( dt ) { }
2026-02-01 13:30:11 +08:00
} ` ,
( err ) => {
2026-02-03 19:55:51 +08:00
if ( err ) {
callback ( err ) ;
} else {
// 【关键修复】创建脚本后,必须刷新 AssetDB 并等待完成,
// 否则后续立即挂载脚本的操作(manage_components)会因找不到脚本 UUID 而失败。
Editor . assetdb . refresh ( scriptPath , ( refreshErr ) => {
if ( refreshErr ) {
addLog ( "warn" , ` Refresh failed after script creation: ${ refreshErr } ` ) ;
}
callback ( null , ` Script created at ${ scriptPath } ` ) ;
} ) ;
}
2026-02-01 13:30:11 +08:00
} ,
) ;
2026-01-31 16:48:21 +08:00
break ;
case "delete" :
2026-02-02 14:34:34 +08:00
if ( ! Editor . assetdb . exists ( scriptPath ) ) {
return callback ( ` Script not found at ${ scriptPath } ` ) ;
2026-01-31 16:48:21 +08:00
}
2026-02-02 14:34:34 +08:00
Editor . assetdb . delete ( [ scriptPath ] , ( err ) => {
callback ( err , err ? null : ` Script deleted at ${ scriptPath } ` ) ;
2026-01-31 16:48:21 +08:00
} ) ;
break ;
case "read" :
2026-02-02 14:34:34 +08:00
// 使用 fs 读取,绕过 assetdb.loadAny
const readFsPath = Editor . assetdb . urlToFspath ( scriptPath ) ;
if ( ! readFsPath || ! fs . existsSync ( readFsPath ) ) {
return callback ( ` Script not found at ${ scriptPath } ` ) ;
}
try {
const content = fs . readFileSync ( readFsPath , "utf-8" ) ;
callback ( null , content ) ;
} catch ( e ) {
callback ( ` Failed to read script: ${ e . message } ` ) ;
}
2026-01-31 16:48:21 +08:00
break ;
case "write" :
2026-02-02 14:34:34 +08:00
// 使用 fs 写入 + refresh, 确保覆盖成功
const writeFsPath = Editor . assetdb . urlToFspath ( scriptPath ) ;
if ( ! writeFsPath ) {
return callback ( ` Invalid path: ${ scriptPath } ` ) ;
}
try {
fs . writeFileSync ( writeFsPath , content , "utf-8" ) ;
Editor . assetdb . refresh ( scriptPath , ( err ) => {
if ( err ) addLog ( "warn" , ` Refresh failed after write: ${ err } ` ) ;
callback ( null , ` Script updated at ${ scriptPath } ` ) ;
} ) ;
} catch ( e ) {
callback ( ` Failed to write script: ${ e . message } ` ) ;
}
2026-01-31 16:48:21 +08:00
break ;
default :
2026-02-10 09:14:50 +08:00
callback ( ` 未知的脚本操作类型: ${ action } ` ) ;
2026-01-31 16:48:21 +08:00
break ;
}
} ,
2026-02-07 23:14:12 +08:00
/ * *
* 批量执行多个 MCP 工具操作
* @ param { Object } args 参数 ( operations 数组 )
* @ param { Function } callback 完成回调
* /
2026-01-31 16:48:21 +08:00
batchExecute ( args , callback ) {
const { operations } = args ;
const results = [ ] ;
let completed = 0 ;
if ( ! operations || operations . length === 0 ) {
2026-02-10 09:14:50 +08:00
return callback ( "未提供任何操作指令" ) ;
2026-01-31 16:48:21 +08:00
}
operations . forEach ( ( operation , index ) => {
this . handleMcpCall ( operation . tool , operation . params , ( err , result ) => {
results [ index ] = { tool : operation . tool , error : err , result : result } ;
completed ++ ;
if ( completed === operations . length ) {
callback ( null , results ) ;
}
} ) ;
} ) ;
} ,
2026-02-07 23:14:12 +08:00
/ * *
* 通用的资源管理函数 ( 创建 、 删除 、 移动等 )
* @ param { Object } args 参数
* @ param { Function } callback 完成回调
* /
2026-01-31 16:48:21 +08:00
manageAsset ( args , callback ) {
const { action , path , targetPath , content } = args ;
switch ( action ) {
case "create" :
if ( Editor . assetdb . exists ( path ) ) {
return callback ( ` Asset already exists at ${ path } ` ) ;
}
// 确保父目录存在
2026-02-01 13:30:11 +08:00
const fs = require ( "fs" ) ;
const pathModule = require ( "path" ) ;
2026-01-31 16:48:21 +08:00
const absolutePath = Editor . assetdb . urlToFspath ( path ) ;
const dirPath = pathModule . dirname ( absolutePath ) ;
if ( ! fs . existsSync ( dirPath ) ) {
fs . mkdirSync ( dirPath , { recursive : true } ) ;
}
2026-02-01 13:30:11 +08:00
Editor . assetdb . create ( path , content || "" , ( err ) => {
2026-01-31 16:48:21 +08:00
callback ( err , err ? null : ` Asset created at ${ path } ` ) ;
} ) ;
break ;
case "delete" :
if ( ! Editor . assetdb . exists ( path ) ) {
return callback ( ` Asset not found at ${ path } ` ) ;
}
Editor . assetdb . delete ( [ path ] , ( err ) => {
callback ( err , err ? null : ` Asset deleted at ${ path } ` ) ;
} ) ;
break ;
case "move" :
if ( ! Editor . assetdb . exists ( path ) ) {
return callback ( ` Asset not found at ${ path } ` ) ;
}
if ( Editor . assetdb . exists ( targetPath ) ) {
return callback ( ` Target asset already exists at ${ targetPath } ` ) ;
}
Editor . assetdb . move ( path , targetPath , ( err ) => {
callback ( err , err ? null : ` Asset moved from ${ path } to ${ targetPath } ` ) ;
} ) ;
break ;
2026-02-02 14:34:34 +08:00
2026-01-31 16:48:21 +08:00
case "get_info" :
2026-02-01 13:30:11 +08:00
try {
if ( ! Editor . assetdb . exists ( path ) ) {
return callback ( ` Asset not found: ${ path } ` ) ;
}
const uuid = Editor . assetdb . urlToUuid ( path ) ;
2026-02-02 14:34:34 +08:00
const info = Editor . assetdb . assetInfoByUuid ( uuid ) ;
if ( info ) {
callback ( null , info ) ;
} else {
2026-02-10 09:14:50 +08:00
// 备选方案:如果 API 未返回信息但资源确实存在
2026-02-02 14:34:34 +08:00
callback ( null , { url : path , uuid : uuid , exists : true } ) ;
}
2026-02-01 13:30:11 +08:00
} catch ( e ) {
callback ( ` Error getting asset info: ${ e . message } ` ) ;
}
2026-01-31 16:48:21 +08:00
break ;
default :
2026-02-10 09:14:50 +08:00
callback ( ` 未知的资源管理操作: ${ action } ` ) ;
2026-01-31 16:48:21 +08:00
break ;
2026-02-01 13:30:11 +08:00
}
} ,
2026-01-31 19:36:55 +08:00
2026-02-07 23:14:12 +08:00
/ * *
* 场景相关的资源管理 ( 创建 、 克隆场景等 )
* @ param { Object } args 参数
* @ param { Function } callback 完成回调
* /
2026-02-01 13:30:11 +08:00
sceneManagement ( args , callback ) {
const { action , path , targetPath , name } = args ;
2026-01-31 19:36:55 +08:00
2026-02-01 13:30:11 +08:00
switch ( action ) {
case "create" :
if ( Editor . assetdb . exists ( path ) ) {
return callback ( ` Scene already exists at ${ path } ` ) ;
}
// 确保父目录存在
const absolutePath = Editor . assetdb . urlToFspath ( path ) ;
const dirPath = pathModule . dirname ( absolutePath ) ;
if ( ! fs . existsSync ( dirPath ) ) {
fs . mkdirSync ( dirPath , { recursive : true } ) ;
}
Editor . assetdb . create ( path , getNewSceneTemplate ( ) , ( err ) => {
callback ( err , err ? null : ` Scene created at ${ path } ` ) ;
} ) ;
break ;
2026-01-31 19:36:55 +08:00
2026-02-01 13:30:11 +08:00
case "delete" :
if ( ! Editor . assetdb . exists ( path ) ) {
return callback ( ` Scene not found at ${ path } ` ) ;
}
Editor . assetdb . delete ( [ path ] , ( err ) => {
callback ( err , err ? null : ` Scene deleted at ${ path } ` ) ;
} ) ;
break ;
2026-01-31 19:36:55 +08:00
2026-02-01 13:30:11 +08:00
case "duplicate" :
if ( ! Editor . assetdb . exists ( path ) ) {
return callback ( ` Scene not found at ${ path } ` ) ;
}
if ( ! targetPath ) {
return callback ( ` Target path is required for duplicate operation ` ) ;
}
if ( Editor . assetdb . exists ( targetPath ) ) {
return callback ( ` Target scene already exists at ${ targetPath } ` ) ;
}
2026-02-10 09:14:50 +08:00
// 【修复】Cocos 2.4.x 主进程中 Editor.assetdb 没有 loadAny
// 直接使用 fs 读取物理文件
try {
const sourceFsPath = Editor . assetdb . urlToFspath ( path ) ;
if ( ! sourceFsPath || ! fs . existsSync ( sourceFsPath ) ) {
return callback ( ` Failed to locate source scene file: ${ path } ` ) ;
2026-01-31 19:36:55 +08:00
}
2026-02-10 09:14:50 +08:00
const content = fs . readFileSync ( sourceFsPath , "utf-8" ) ;
2026-02-01 13:30:11 +08:00
// 确保目标目录存在
const targetAbsolutePath = Editor . assetdb . urlToFspath ( targetPath ) ;
const targetDirPath = pathModule . dirname ( targetAbsolutePath ) ;
if ( ! fs . existsSync ( targetDirPath ) ) {
fs . mkdirSync ( targetDirPath , { recursive : true } ) ;
2026-01-31 19:36:55 +08:00
}
2026-02-01 13:30:11 +08:00
// 创建复制的场景
Editor . assetdb . create ( targetPath , content , ( err ) => {
2026-02-10 09:14:50 +08:00
if ( err ) return callback ( err ) ;
// 【增加】关键刷新,确保数据库能查到新文件
Editor . assetdb . refresh ( targetPath , ( refreshErr ) => {
callback ( refreshErr , refreshErr ? null : ` Scene duplicated from ${ path } to ${ targetPath } ` ) ;
} ) ;
2026-01-31 19:36:55 +08:00
} ) ;
2026-02-10 09:14:50 +08:00
} catch ( e ) {
callback ( ` Duplicate failed: ${ e . message } ` ) ;
}
2026-02-01 13:30:11 +08:00
break ;
2026-01-31 19:36:55 +08:00
2026-02-01 13:30:11 +08:00
case "get_info" :
2026-02-02 14:34:34 +08:00
if ( Editor . assetdb . exists ( path ) ) {
const uuid = Editor . assetdb . urlToUuid ( path ) ;
const info = Editor . assetdb . assetInfoByUuid ( uuid ) ;
callback ( null , info || { url : path , uuid : uuid , exists : true } ) ;
} else {
callback ( ` Scene not found: ${ path } ` ) ;
}
2026-02-01 13:30:11 +08:00
break ;
2026-01-31 19:36:55 +08:00
2026-02-01 13:30:11 +08:00
default :
2026-01-31 19:36:55 +08:00
callback ( ` Unknown scene action: ${ action } ` ) ;
break ;
2026-02-01 13:30:11 +08:00
}
} ,
// 预制体管理
prefabManagement ( args , callback ) {
2026-02-02 14:34:34 +08:00
const { action , path : prefabPath , nodeId , parentId } = args ;
2026-02-01 13:30:11 +08:00
switch ( action ) {
case "create" :
if ( ! nodeId ) {
return callback ( ` Node ID is required for create operation ` ) ;
}
2026-02-02 14:34:34 +08:00
if ( Editor . assetdb . exists ( prefabPath ) ) {
return callback ( ` Prefab already exists at ${ prefabPath } ` ) ;
2026-02-01 13:30:11 +08:00
}
// 确保父目录存在
2026-02-02 14:34:34 +08:00
const absolutePath = Editor . assetdb . urlToFspath ( prefabPath ) ;
2026-02-10 09:14:50 +08:00
const dirPath = pathModule . dirname ( absolutePath ) ;
2026-02-01 13:30:11 +08:00
if ( ! fs . existsSync ( dirPath ) ) {
fs . mkdirSync ( dirPath , { recursive : true } ) ;
}
2026-02-02 14:34:34 +08:00
// 解析目标目录和文件名
// db://assets/folder/PrefabName.prefab -> db://assets/folder, PrefabName
const targetDir = prefabPath . substring ( 0 , prefabPath . lastIndexOf ( '/' ) ) ;
const fileName = prefabPath . substring ( prefabPath . lastIndexOf ( '/' ) + 1 ) ;
const prefabName = fileName . replace ( '.prefab' , '' ) ;
// 1. 重命名节点以匹配预制体名称
Editor . Ipc . sendToPanel ( "scene" , "scene:set-property" , {
id : nodeId ,
path : "name" ,
type : "String" ,
value : prefabName ,
isSubProp : false
} ) ;
// 2. 发送创建命令 (参数: [uuids], dirPath)
// 注意: scene:create-prefab 第三个参数必须是 db:// 目录路径
setTimeout ( ( ) => {
Editor . Ipc . sendToPanel ( "scene" , "scene:create-prefab" , [ nodeId ] , targetDir ) ;
} , 100 ) ; // 稍微延迟以确保重命名生效
callback ( null , ` Command sent: Creating prefab from node ${ nodeId } at ${ targetDir } as ${ prefabName } ` ) ;
2026-02-01 13:30:11 +08:00
break ;
case "update" :
if ( ! nodeId ) {
return callback ( ` Node ID is required for update operation ` ) ;
}
2026-02-02 14:34:34 +08:00
if ( ! Editor . assetdb . exists ( prefabPath ) ) {
return callback ( ` Prefab not found at ${ prefabPath } ` ) ;
2026-02-01 13:30:11 +08:00
}
// 更新预制体
2026-02-02 14:34:34 +08:00
Editor . Ipc . sendToPanel ( "scene" , "scene:update-prefab" , nodeId , prefabPath ) ;
callback ( null , ` Command sent: Updating prefab ${ prefabPath } from node ${ nodeId } ` ) ;
2026-02-01 13:30:11 +08:00
break ;
case "instantiate" :
2026-02-02 14:34:34 +08:00
if ( ! Editor . assetdb . exists ( prefabPath ) ) {
return callback ( ` Prefab not found at ${ prefabPath } ` ) ;
2026-02-01 13:30:11 +08:00
}
// 实例化预制体
2026-02-02 14:34:34 +08:00
const prefabUuid = Editor . assetdb . urlToUuid ( prefabPath ) ;
2026-02-01 13:30:11 +08:00
Editor . Scene . callSceneScript (
"mcp-bridge" ,
"instantiate-prefab" ,
{
2026-02-02 14:34:34 +08:00
prefabUuid : prefabUuid ,
2026-02-01 13:30:11 +08:00
parentId : parentId ,
} ,
callback ,
) ;
break ;
case "get_info" :
2026-02-02 14:34:34 +08:00
if ( Editor . assetdb . exists ( prefabPath ) ) {
const uuid = Editor . assetdb . urlToUuid ( prefabPath ) ;
const info = Editor . assetdb . assetInfoByUuid ( uuid ) ;
// 确保返回对象包含 exists: true, 以满足测试验证
const result = info || { url : prefabPath , uuid : uuid } ;
result . exists = true ;
callback ( null , result ) ;
} else {
callback ( ` Prefab not found: ${ prefabPath } ` ) ;
}
2026-02-01 13:30:11 +08:00
break ;
default :
2026-02-10 09:14:50 +08:00
callback ( ` 未知的预制体管理操作: ${ action } ` ) ;
2026-02-01 13:30:11 +08:00
}
} ,
2026-02-07 23:14:12 +08:00
/ * *
* 管理编辑器状态 ( 选中对象 、 刷新等 )
* @ param { Object } args 参数
* @ param { Function } callback 完成回调
* /
2026-02-01 13:30:11 +08:00
manageEditor ( args , callback ) {
const { action , target , properties } = args ;
switch ( action ) {
case "get_selection" :
// 获取当前选中的资源或节点
const nodeSelection = Editor . Selection . curSelection ( "node" ) ;
const assetSelection = Editor . Selection . curSelection ( "asset" ) ;
callback ( null , {
nodes : nodeSelection ,
assets : assetSelection ,
} ) ;
break ;
case "set_selection" :
// 设置选中状态
2026-02-10 09:14:50 +08:00
if ( target === "node" ) {
const ids = properties . ids || properties . nodes ;
if ( ids ) Editor . Selection . select ( "node" , ids ) ;
} else if ( target === "asset" ) {
const ids = properties . ids || properties . assets ;
if ( ids ) Editor . Selection . select ( "asset" , ids ) ;
2026-02-01 13:30:11 +08:00
}
callback ( null , "Selection updated" ) ;
break ;
case "refresh_editor" :
// 刷新编辑器
2026-02-02 10:14:17 +08:00
const refreshPath = ( properties && properties . path ) ? properties . path : 'db://assets/scripts' ;
Editor . assetdb . refresh ( refreshPath , ( err ) => {
if ( err ) {
addLog ( "error" , ` Refresh failed: ${ err } ` ) ;
callback ( err ) ;
} else {
callback ( null , ` Editor refreshed: ${ refreshPath } ` ) ;
}
} ) ;
2026-02-01 13:30:11 +08:00
break ;
default :
2026-02-10 09:14:50 +08:00
callback ( "未知的编辑器管理操作" ) ;
break ;
}
} ,
// 管理着色器 (Effect)
manageShader ( args , callback ) {
const { action , path : effectPath , content } = args ;
switch ( action ) {
case "create" :
if ( Editor . assetdb . exists ( effectPath ) ) {
return callback ( ` Effect already exists at ${ effectPath } ` ) ;
}
// 确保父目录存在
const absolutePath = Editor . assetdb . urlToFspath ( effectPath ) ;
const dirPath = pathModule . dirname ( absolutePath ) ;
if ( ! fs . existsSync ( dirPath ) ) {
fs . mkdirSync ( dirPath , { recursive : true } ) ;
}
const defaultEffect = ` CCEffect %{
techniques :
- passes :
- vert : vs
frag : fs
blendState :
targets :
- blend : true
rasterizerState :
cullMode : none
properties :
texture : { value : white }
mainColor : { value : [ 1 , 1 , 1 , 1 ] , editor : { type : color } }
} %
CCProgram vs % {
precision highp float ;
# include < cc - global >
attribute vec3 a _position ;
attribute vec2 a _uv0 ;
varying vec2 v _uv0 ;
void main ( ) {
gl _Position = cc _matViewProj * vec4 ( a _position , 1.0 ) ;
v _uv0 = a _uv0 ;
}
} %
CCProgram fs % {
precision highp float ;
uniform sampler2D texture ;
uniform Constant {
vec4 mainColor ;
} ;
varying vec2 v _uv0 ;
void main ( ) {
gl _FragColor = mainColor * texture2D ( texture , v _uv0 ) ;
}
} % ` ;
Editor . assetdb . create ( effectPath , content || defaultEffect , ( err ) => {
if ( err ) return callback ( err ) ;
Editor . assetdb . refresh ( effectPath , ( refreshErr ) => {
callback ( refreshErr , refreshErr ? null : ` Effect created at ${ effectPath } ` ) ;
} ) ;
} ) ;
break ;
case "read" :
if ( ! Editor . assetdb . exists ( effectPath ) ) {
return callback ( ` Effect not found: ${ effectPath } ` ) ;
}
const fspath = Editor . assetdb . urlToFspath ( effectPath ) ;
try {
const data = fs . readFileSync ( fspath , "utf-8" ) ;
callback ( null , data ) ;
} catch ( e ) {
callback ( ` Failed to read effect: ${ e . message } ` ) ;
}
break ;
case "write" :
if ( ! Editor . assetdb . exists ( effectPath ) ) {
return callback ( ` Effect not found: ${ effectPath } ` ) ;
}
const writeFsPath = Editor . assetdb . urlToFspath ( effectPath ) ;
try {
fs . writeFileSync ( writeFsPath , content , "utf-8" ) ;
Editor . assetdb . refresh ( effectPath , ( err ) => {
callback ( err , err ? null : ` Effect updated at ${ effectPath } ` ) ;
} ) ;
} catch ( e ) {
callback ( ` Failed to write effect: ${ e . message } ` ) ;
}
break ;
case "delete" :
if ( ! Editor . assetdb . exists ( effectPath ) ) {
return callback ( ` Effect not found: ${ effectPath } ` ) ;
}
Editor . assetdb . delete ( [ effectPath ] , ( err ) => {
callback ( err , err ? null : ` Effect deleted: ${ effectPath } ` ) ;
} ) ;
break ;
case "get_info" :
if ( Editor . assetdb . exists ( effectPath ) ) {
const uuid = Editor . assetdb . urlToUuid ( effectPath ) ;
const info = Editor . assetdb . assetInfoByUuid ( uuid ) ;
callback ( null , info || { url : effectPath , uuid : uuid , exists : true } ) ;
} else {
callback ( ` Effect not found: ${ effectPath } ` ) ;
}
break ;
default :
callback ( ` Unknown shader action: ${ action } ` ) ;
2026-02-01 13:30:11 +08:00
break ;
}
} ,
// 管理材质
manageMaterial ( args , callback ) {
2026-02-10 09:14:50 +08:00
const { action , path : matPath , properties = { } } = args ;
2026-02-01 13:30:11 +08:00
switch ( action ) {
case "create" :
2026-02-10 09:14:50 +08:00
if ( Editor . assetdb . exists ( matPath ) ) {
return callback ( ` Material already exists at ${ matPath } ` ) ;
2026-02-01 13:30:11 +08:00
}
// 确保父目录存在
2026-02-10 09:14:50 +08:00
const absolutePath = Editor . assetdb . urlToFspath ( matPath ) ;
2026-02-01 13:30:11 +08:00
const dirPath = pathModule . dirname ( absolutePath ) ;
if ( ! fs . existsSync ( dirPath ) ) {
fs . mkdirSync ( dirPath , { recursive : true } ) ;
}
2026-02-10 09:14:50 +08:00
// 构造 Cocos 2.4.x 材质内容
const materialData = {
2026-02-01 13:30:11 +08:00
_ _type _ _ : "cc.Material" ,
_name : "" ,
_objFlags : 0 ,
_native : "" ,
2026-02-10 09:14:50 +08:00
_effectAsset : properties . shaderUuid ? { _ _uuid _ _ : properties . shaderUuid } : null ,
_techniqueIndex : 0 ,
_techniqueData : {
"0" : {
defines : properties . defines || { } ,
props : properties . uniforms || { }
}
}
} ;
Editor . assetdb . create ( matPath , JSON . stringify ( materialData , null , 2 ) , ( err ) => {
if ( err ) return callback ( err ) ;
Editor . assetdb . refresh ( matPath , ( refreshErr ) => {
callback ( refreshErr , refreshErr ? null : ` Material created at ${ matPath } ` ) ;
} ) ;
2026-02-01 13:30:11 +08:00
} ) ;
break ;
2026-02-10 09:14:50 +08:00
case "update" :
if ( ! Editor . assetdb . exists ( matPath ) ) {
return callback ( ` Material not found at ${ matPath } ` ) ;
}
const fspath = Editor . assetdb . urlToFspath ( matPath ) ;
try {
const content = fs . readFileSync ( fspath , "utf-8" ) ;
const matData = JSON . parse ( content ) ;
// 确保结构存在
if ( ! matData . _techniqueData ) matData . _techniqueData = { } ;
if ( ! matData . _techniqueData [ "0" ] ) matData . _techniqueData [ "0" ] = { } ;
const tech = matData . _techniqueData [ "0" ] ;
// 更新 Shader
if ( properties . shaderUuid ) {
matData . _effectAsset = { _ _uuid _ _ : properties . shaderUuid } ;
}
// 更新 Defines
if ( properties . defines ) {
tech . defines = Object . assign ( tech . defines || { } , properties . defines ) ;
}
// 更新 Props/Uniforms
if ( properties . uniforms ) {
tech . props = Object . assign ( tech . props || { } , properties . uniforms ) ;
}
fs . writeFileSync ( fspath , JSON . stringify ( matData , null , 2 ) , "utf-8" ) ;
Editor . assetdb . refresh ( matPath , ( err ) => {
callback ( err , err ? null : ` Material updated at ${ matPath } ` ) ;
} ) ;
} catch ( e ) {
callback ( ` Failed to update material: ${ e . message } ` ) ;
}
break ;
2026-02-01 13:30:11 +08:00
case "delete" :
2026-02-10 09:14:50 +08:00
if ( ! Editor . assetdb . exists ( matPath ) ) {
return callback ( ` Material not found at ${ matPath } ` ) ;
2026-02-01 13:30:11 +08:00
}
2026-02-10 09:14:50 +08:00
Editor . assetdb . delete ( [ matPath ] , ( err ) => {
callback ( err , err ? null : ` Material deleted at ${ matPath } ` ) ;
2026-02-01 13:30:11 +08:00
} ) ;
break ;
2026-02-10 09:14:50 +08:00
2026-02-01 13:30:11 +08:00
case "get_info" :
2026-02-10 09:14:50 +08:00
if ( Editor . assetdb . exists ( matPath ) ) {
const uuid = Editor . assetdb . urlToUuid ( matPath ) ;
2026-02-02 14:34:34 +08:00
const info = Editor . assetdb . assetInfoByUuid ( uuid ) ;
2026-02-10 09:14:50 +08:00
callback ( null , info || { url : matPath , uuid : uuid , exists : true } ) ;
2026-02-02 14:34:34 +08:00
} else {
2026-02-10 09:14:50 +08:00
callback ( ` Material not found: ${ matPath } ` ) ;
2026-02-02 14:34:34 +08:00
}
2026-02-01 13:30:11 +08:00
break ;
2026-02-10 09:14:50 +08:00
2026-02-01 13:30:11 +08:00
default :
callback ( ` Unknown material action: ${ action } ` ) ;
break ;
}
} ,
// 管理纹理
manageTexture ( args , callback ) {
const { action , path , properties } = args ;
switch ( action ) {
case "create" :
if ( Editor . assetdb . exists ( path ) ) {
return callback ( ` Texture already exists at ${ path } ` ) ;
}
// 确保父目录存在
const absolutePath = Editor . assetdb . urlToFspath ( path ) ;
const dirPath = pathModule . dirname ( absolutePath ) ;
if ( ! fs . existsSync ( dirPath ) ) {
fs . mkdirSync ( dirPath , { recursive : true } ) ;
}
2026-02-10 14:00:02 +08:00
// 1. 准备文件内容 (优先使用 properties.content, 否则使用默认 1x1)
let base64Data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" ;
if ( properties && properties . content ) {
base64Data = properties . content ;
}
2026-02-10 09:14:50 +08:00
const buffer = Buffer . from ( base64Data , 'base64' ) ;
try {
2026-02-10 14:00:02 +08:00
// 2. 写入物理文件
2026-02-10 09:14:50 +08:00
fs . writeFileSync ( absolutePath , buffer ) ;
2026-02-10 14:00:02 +08:00
// 3. 刷新该资源以生成 Meta
Editor . assetdb . refresh ( path , ( err , results ) => {
2026-02-10 09:14:50 +08:00
if ( err ) return callback ( err ) ;
2026-02-10 14:00:02 +08:00
// 4. 如果有 9-slice 设置,更新 Meta
if ( properties && ( properties . border || properties . type ) ) {
const uuid = Editor . assetdb . urlToUuid ( path ) ;
if ( ! uuid ) return callback ( null , ` Texture created but UUID not found immediately. ` ) ;
// 稍微延迟确保 Meta 已生成
setTimeout ( ( ) => {
const meta = Editor . assetdb . loadMeta ( uuid ) ;
if ( meta ) {
let changed = false ;
if ( properties . type ) {
meta . type = properties . type ;
changed = true ;
}
// 设置 9-slice (border)
// 注意: Cocos 2.4 纹理 Meta 中 subMetas 下通常有一个与纹理同名的 key (或者主要的一个 key)
if ( properties . border ) {
// 确保类型是 sprite
meta . type = 'sprite' ;
// 找到 SpriteFrame 的 subMeta
const subKeys = Object . keys ( meta . subMetas ) ;
if ( subKeys . length > 0 ) {
const subMeta = meta . subMetas [ subKeys [ 0 ] ] ;
subMeta . border = properties . border ; // [top, bottom, left, right]
changed = true ;
}
}
if ( changed ) {
Editor . assetdb . saveMeta ( uuid , JSON . stringify ( meta ) , ( err ) => {
if ( err ) Editor . warn ( ` Failed to save meta for ${ path } : ${ err } ` ) ;
callback ( null , ` Texture created and meta updated at ${ path } ` ) ;
} ) ;
return ;
}
}
callback ( null , ` Texture created at ${ path } ` ) ;
} , 100 ) ;
} else {
callback ( null , ` Texture created at ${ path } ` ) ;
}
2026-02-10 09:14:50 +08:00
} ) ;
} catch ( e ) {
callback ( ` Failed to write texture file: ${ e . message } ` ) ;
}
2026-02-01 13:30:11 +08:00
break ;
case "delete" :
if ( ! Editor . assetdb . exists ( path ) ) {
return callback ( ` Texture not found at ${ path } ` ) ;
}
Editor . assetdb . delete ( [ path ] , ( err ) => {
callback ( err , err ? null : ` Texture deleted at ${ path } ` ) ;
} ) ;
break ;
case "get_info" :
2026-02-02 14:34:34 +08:00
if ( Editor . assetdb . exists ( path ) ) {
const uuid = Editor . assetdb . urlToUuid ( path ) ;
const info = Editor . assetdb . assetInfoByUuid ( uuid ) ;
callback ( null , info || { url : path , uuid : uuid , exists : true } ) ;
} else {
callback ( ` Texture not found: ${ path } ` ) ;
}
2026-02-01 13:30:11 +08:00
break ;
2026-02-10 14:00:02 +08:00
case "update" :
if ( ! Editor . assetdb . exists ( path ) ) {
return callback ( ` Texture not found at ${ path } ` ) ;
}
const uuid = Editor . assetdb . urlToUuid ( path ) ;
let meta = Editor . assetdb . loadMeta ( uuid ) ;
// Fallback: 如果 Editor.assetdb.loadMeta 失败 (API 偶尔不稳定),尝试直接读取文件系统中的 .meta 文件
if ( ! meta ) {
try {
const fspath = Editor . assetdb . urlToFspath ( path ) ;
const metaPath = fspath + ".meta" ;
if ( fs . existsSync ( metaPath ) ) {
const metaContent = fs . readFileSync ( metaPath , "utf-8" ) ;
meta = JSON . parse ( metaContent ) ;
addLog ( "info" , ` [manage_texture] Loaded meta from fs fallback: ${ metaPath } ` ) ;
}
} catch ( e ) {
addLog ( "warn" , ` [manage_texture] Meta fs fallback failed: ${ e . message } ` ) ;
}
}
if ( ! meta ) {
return callback ( ` Failed to load meta for ${ path } ` ) ;
}
let changed = false ;
if ( properties ) {
// 更新类型
if ( properties . type ) {
if ( meta . type !== properties . type ) {
meta . type = properties . type ;
changed = true ;
}
}
// 更新 9-slice border
if ( properties . border ) {
// 确保类型是 sprite
if ( meta . type !== 'sprite' ) {
meta . type = 'sprite' ;
changed = true ;
}
// 找到 SubMeta
// Cocos Meta 结构: { subMetas: { "textureName": { ... } } }
// 注意: Cocos 2.x 的 meta 结构因版本而异,旧版可能使用 border: [t, b, l, r] 数组,
// 而新版 (如 2.3.x+) 通常使用 borderTop, borderBottom 等独立字段。
// 此处逻辑实现了兼容性处理。
const subKeys = Object . keys ( meta . subMetas ) ;
if ( subKeys . length > 0 ) {
const subMeta = meta . subMetas [ subKeys [ 0 ] ] ;
const newBorder = properties . border ; // [top, bottom, left, right]
// 方式 1: standard array style
if ( subMeta . border !== undefined ) {
const oldBorder = subMeta . border ;
if ( ! oldBorder ||
oldBorder [ 0 ] !== newBorder [ 0 ] ||
oldBorder [ 1 ] !== newBorder [ 1 ] ||
oldBorder [ 2 ] !== newBorder [ 2 ] ||
oldBorder [ 3 ] !== newBorder [ 3 ] ) {
subMeta . border = newBorder ;
changed = true ;
}
}
// 方式 2: individual fields style (common in 2.3.x)
else if ( subMeta . borderTop !== undefined ) {
// top, bottom, left, right
if ( subMeta . borderTop !== newBorder [ 0 ] ||
subMeta . borderBottom !== newBorder [ 1 ] ||
subMeta . borderLeft !== newBorder [ 2 ] ||
subMeta . borderRight !== newBorder [ 3 ] ) {
subMeta . borderTop = newBorder [ 0 ] ;
subMeta . borderBottom = newBorder [ 1 ] ;
subMeta . borderLeft = newBorder [ 2 ] ;
subMeta . borderRight = newBorder [ 3 ] ;
changed = true ;
}
}
// 方式 3: 如果都没有,尝试写入 individual fields
else {
subMeta . borderTop = newBorder [ 0 ] ;
subMeta . borderBottom = newBorder [ 1 ] ;
subMeta . borderLeft = newBorder [ 2 ] ;
subMeta . borderRight = newBorder [ 3 ] ;
changed = true ;
}
}
}
}
if ( changed ) {
// 使用 saveMeta 或者 fs 写入
// 为了安全,如果 loadMeta 失败了, safeMeta 可能也会失败,所以这里尽量用 API, 不行再 fallback (暂且只用 API)
Editor . assetdb . saveMeta ( uuid , JSON . stringify ( meta ) , ( err ) => {
if ( err ) return callback ( ` Failed to save meta: ${ err } ` ) ;
callback ( null , ` Texture updated at ${ path } ` ) ;
} ) ;
} else {
callback ( null , ` No changes needed for ${ path } ` ) ;
}
break ;
2026-02-01 13:30:11 +08:00
default :
callback ( ` Unknown texture action: ${ action } ` ) ;
break ;
}
} ,
2026-02-07 23:14:12 +08:00
/ * *
* 对文件应用一系列精确的文本编辑操作
* @ param { Object } args 参数
* @ param { Function } callback 完成回调
* /
2026-02-01 13:30:11 +08:00
applyTextEdits ( args , callback ) {
const { filePath , edits } = args ;
2026-02-02 14:34:34 +08:00
// 1. 获取文件系统路径
const fspath = Editor . assetdb . urlToFspath ( filePath ) ;
if ( ! fspath ) {
return callback ( ` File not found or invalid URL: ${ filePath } ` ) ;
}
2026-01-31 19:36:55 +08:00
2026-02-02 14:34:34 +08:00
const fs = require ( "fs" ) ;
if ( ! fs . existsSync ( fspath ) ) {
return callback ( ` File does not exist: ${ fspath } ` ) ;
}
try {
// 2. 读取
let updatedContent = fs . readFileSync ( fspath , "utf-8" ) ;
// 3. 应用编辑
// 必须按倒序应用编辑,否则后续编辑的位置会偏移 (假设edits未排序, 这里简单处理, 实际上LSP通常建议客户端倒序应用或计算偏移)
// 这里假设edits已经按照位置排序或者用户负责, 如果需要严谨, 应先按 start/position 倒序排序
2026-02-03 20:04:45 +08:00
// 简单排序保险:
2026-02-02 14:34:34 +08:00
const sortedEdits = [ ... edits ] . sort ( ( a , b ) => {
const posA = a . position !== undefined ? a . position : a . start ;
const posB = b . position !== undefined ? b . position : b . start ;
2026-02-03 20:04:45 +08:00
return posB - posA ; // 从大到小
2026-02-02 14:34:34 +08:00
} ) ;
sortedEdits . forEach ( ( edit ) => {
switch ( edit . type ) {
case "insert" :
updatedContent =
updatedContent . slice ( 0 , edit . position ) +
edit . text +
updatedContent . slice ( edit . position ) ;
break ;
case "delete" :
updatedContent = updatedContent . slice ( 0 , edit . start ) + updatedContent . slice ( edit . end ) ;
break ;
case "replace" :
updatedContent =
updatedContent . slice ( 0 , edit . start ) + edit . text + updatedContent . slice ( edit . end ) ;
break ;
2026-02-01 13:30:11 +08:00
}
2026-02-02 14:34:34 +08:00
} ) ;
2026-01-31 19:36:55 +08:00
2026-02-02 14:34:34 +08:00
// 4. 写入
fs . writeFileSync ( fspath , updatedContent , "utf-8" ) ;
2026-01-31 19:36:55 +08:00
2026-02-02 14:34:34 +08:00
// 5. 通知编辑器资源变化 (重要)
Editor . assetdb . refresh ( filePath , ( err ) => {
if ( err ) addLog ( "warn" , ` Refresh failed for ${ filePath } : ${ err } ` ) ;
callback ( null , ` Text edits applied to ${ filePath } ` ) ;
2026-02-01 13:30:11 +08:00
} ) ;
2026-02-02 14:34:34 +08:00
} catch ( err ) {
callback ( ` Action failed: ${ err . message } ` ) ;
}
2026-02-01 13:30:11 +08:00
} ,
2026-01-31 19:36:55 +08:00
2026-02-01 13:30:11 +08:00
// 读取控制台
readConsole ( args , callback ) {
const { limit , type } = args ;
let filteredOutput = logBuffer ;
2026-01-31 19:36:55 +08:00
2026-02-01 13:30:11 +08:00
if ( type ) {
filteredOutput = filteredOutput . filter ( ( item ) => item . type === type ) ;
}
2026-01-31 19:36:55 +08:00
2026-02-01 13:30:11 +08:00
if ( limit ) {
filteredOutput = filteredOutput . slice ( - limit ) ;
}
callback ( null , filteredOutput ) ;
} ,
2026-02-02 10:14:17 +08:00
executeMenuItem ( args , callback ) {
const { menuPath } = args ;
if ( ! menuPath ) {
return callback ( "Menu path is required" ) ;
}
addLog ( "info" , ` Executing Menu Item: ${ menuPath } ` ) ;
2026-02-03 19:55:51 +08:00
// 菜单项映射表 (Cocos Creator 2.4.x IPC)
// 参考: IPC_MESSAGES.md
const menuMap = {
'File/New Scene' : 'scene:new-scene' ,
'File/Save Scene' : 'scene:stash-and-save' ,
'File/Save' : 'scene:stash-and-save' , // 别名
'Edit/Undo' : 'scene:undo' ,
'Edit/Redo' : 'scene:redo' ,
2026-02-10 00:38:38 +08:00
'Edit/Delete' : 'scene:delete-nodes' ,
'Delete' : 'scene:delete-nodes' ,
'delete' : 'scene:delete-nodes' ,
2026-02-03 19:55:51 +08:00
'Node/Create Empty Node' : 'scene:create-node-by-classid' , // 简化的映射,通常需要参数
'Project/Build' : 'app:build-project' ,
} ;
2026-02-03 20:04:45 +08:00
// 特殊处理 delete-node:UUID 格式
if ( menuPath . startsWith ( "delete-node:" ) ) {
const uuid = menuPath . split ( ":" ) [ 1 ] ;
if ( uuid ) {
Editor . Scene . callSceneScript ( 'mcp-bridge' , 'delete-node' , { uuid } , ( err , result ) => {
if ( err ) callback ( err ) ;
else callback ( null , result || ` Node ${ uuid } deleted via scene script ` ) ;
} ) ;
return ;
}
}
2026-02-03 19:55:51 +08:00
if ( menuMap [ menuPath ] ) {
const ipcMsg = menuMap [ menuPath ] ;
try {
2026-02-10 00:38:38 +08:00
// 获取当前选中的节点进行删除(如果该消息是删除操作)
if ( ipcMsg === 'scene:delete-nodes' ) {
const selection = Editor . Selection . curSelection ( "node" ) ;
if ( selection . length > 0 ) {
Editor . Ipc . sendToMain ( ipcMsg , selection ) ;
callback ( null , ` Menu action triggered: ${ menuPath } -> ${ ipcMsg } with ${ selection . length } nodes ` ) ;
} else {
callback ( "No nodes selected for deletion" ) ;
}
} else {
Editor . Ipc . sendToMain ( ipcMsg ) ;
callback ( null , ` Menu action triggered: ${ menuPath } -> ${ ipcMsg } ` ) ;
}
2026-02-03 19:55:51 +08:00
} catch ( err ) {
callback ( ` Failed to execute IPC ${ ipcMsg } : ${ err . message } ` ) ;
}
2026-02-02 10:14:17 +08:00
} else {
2026-02-03 19:55:51 +08:00
// 对于未在映射表中的菜单,尝试通用的 menu:click (虽然不一定有效)
// 或者直接返回不支持的警告
addLog ( "warn" , ` Menu item ' ${ menuPath } ' not found in supported map. Trying legacy fallback. ` ) ;
// 尝试通用调用
try {
// 注意: Cocos Creator 2.x 的 menu:click 通常需要 Electron 菜单 ID, 而不只是路径
// 这里做个尽力而为的尝试
Editor . Ipc . sendToMain ( 'menu:click' , menuPath ) ;
callback ( null , ` Generic menu action sent: ${ menuPath } (Success guaranteed only for supported items) ` ) ;
} catch ( e ) {
callback ( ` Failed to execute menu item: ${ menuPath } ` ) ;
}
2026-02-02 10:14:17 +08:00
}
} ,
2026-02-01 13:30:11 +08:00
// 验证脚本
validateScript ( args , callback ) {
const { filePath } = args ;
2026-02-02 14:34:34 +08:00
// 1. 获取文件系统路径
const fspath = Editor . assetdb . urlToFspath ( filePath ) ;
if ( ! fspath ) {
return callback ( ` File not found or invalid URL: ${ filePath } ` ) ;
}
2026-02-01 13:30:11 +08:00
2026-02-02 14:34:34 +08:00
// 2. 检查文件是否存在
if ( ! fs . existsSync ( fspath ) ) {
return callback ( ` File does not exist: ${ fspath } ` ) ;
}
2026-02-01 13:30:11 +08:00
2026-02-02 14:34:34 +08:00
// 3. 读取内容并验证
try {
const content = fs . readFileSync ( fspath , "utf-8" ) ;
2026-02-01 13:30:11 +08:00
2026-02-03 19:55:51 +08:00
// 检查空文件
if ( ! content || content . trim ( ) . length === 0 ) {
return callback ( null , { valid : false , message : "Script is empty" } ) ;
}
// 对于 JavaScript 脚本,使用 Function 构造器进行语法验证
2026-02-02 14:34:34 +08:00
if ( filePath . endsWith ( ".js" ) ) {
const wrapper = ` (function() { ${ content } }) ` ;
try {
2026-02-03 19:55:51 +08:00
new Function ( wrapper ) ;
callback ( null , { valid : true , message : "JavaScript syntax is valid" } ) ;
2026-02-02 14:34:34 +08:00
} catch ( syntaxErr ) {
return callback ( null , { valid : false , message : syntaxErr . message } ) ;
2026-02-01 13:30:11 +08:00
}
2026-02-02 14:34:34 +08:00
}
2026-02-03 19:55:51 +08:00
// 对于 TypeScript, 由于没有内置 TS 编译器,我们进行基础的"防呆"检查
// 并明确告知用户无法进行完整编译验证
else if ( filePath . endsWith ( ".ts" ) ) {
// 简单的正则表达式检查:是否有非法字符或明显错误结构 (示例)
// 这里暂时只做简单的括号匹配检查或直接通过,但给出一个 Warning
2026-02-03 20:04:45 +08:00
// 检查是否有 class 定义 (简单的启发式检查)
2026-02-03 19:55:51 +08:00
if ( ! content . includes ( 'class ' ) && ! content . includes ( 'interface ' ) && ! content . includes ( 'enum ' ) && ! content . includes ( 'export ' ) ) {
return callback ( null , { valid : true , message : "Warning: TypeScript file seems to lack standard definitions (class/interface), but basic syntax check is skipped due to missing compiler." } ) ;
}
2026-02-02 14:34:34 +08:00
2026-02-03 19:55:51 +08:00
callback ( null , { valid : true , message : "TypeScript basic check passed. (Full compilation validation requires editor build)" } ) ;
} else {
callback ( null , { valid : true , message : "Unknown script type, validation skipped." } ) ;
}
2026-02-02 14:34:34 +08:00
} catch ( err ) {
callback ( null , { valid : false , message : ` Read Error: ${ err . message } ` } ) ;
}
2026-02-01 13:30:11 +08:00
} ,
// 暴露给 MCP 或面板的 API 封装
2026-01-29 14:53:06 +08:00
messages : {
2026-02-03 19:55:51 +08:00
"scan-ipc-messages" ( event ) {
try {
const msgs = IpcManager . getIpcMessages ( ) ;
if ( event . reply ) event . reply ( null , msgs ) ;
} catch ( e ) {
if ( event . reply ) event . reply ( e . message ) ;
}
} ,
"test-ipc-message" ( event , args ) {
const { name , params } = args ;
IpcManager . testIpcMessage ( name , params ) . then ( ( result ) => {
if ( event . reply ) event . reply ( null , result ) ;
} ) ;
} ,
2026-01-29 14:53:06 +08:00
"open-test-panel" ( ) {
Editor . Panel . open ( "mcp-bridge" ) ;
} ,
2026-02-02 14:34:34 +08:00
2026-01-29 14:53:06 +08:00
"toggle-server" ( event , port ) {
if ( serverConfig . active ) this . stopServer ( ) ;
else this . startServer ( port ) ;
} ,
"clear-logs" ( ) {
logBuffer = [ ] ;
addLog ( "info" , "Logs cleared" ) ;
} ,
// 修改场景中的节点(需要通过 scene-script)
"set-node-property" ( event , args ) {
addLog ( "mcp" , ` Creating node: ${ args . name } ( ${ args . type } ) ` ) ;
// 确保第一个参数 'mcp-bridge' 和 package.json 的 name 一致
Editor . Scene . callSceneScript ( "mcp-bridge" , "set-property" , args , ( err , result ) => {
if ( err ) {
Editor . error ( "Scene Script Error:" , err ) ;
}
if ( event && event . reply ) {
event . reply ( err , result ) ;
2026-01-29 13:47:38 +08:00
}
} ) ;
2026-01-29 14:53:06 +08:00
} ,
"create-node" ( event , args ) {
addLog ( "mcp" , ` Creating node: ${ args . name } ( ${ args . type } ) ` ) ;
Editor . Scene . callSceneScript ( "mcp-bridge" , "create-node" , args , ( err , result ) => {
if ( err ) addLog ( "error" , ` CreateNode Failed: ${ err } ` ) ;
else addLog ( "success" , ` Node Created: ${ result } ` ) ;
event . reply ( err , result ) ;
} ) ;
} ,
"get-server-state" ( event ) {
let profile = this . getProfile ( ) ;
event . reply ( null , {
config : serverConfig ,
logs : logBuffer ,
autoStart : profile . get ( "auto-start" ) , // 返回自动启动状态
} ) ;
} ,
2026-01-29 13:47:38 +08:00
2026-01-29 14:53:06 +08:00
"set-auto-start" ( event , value ) {
this . getProfile ( ) . set ( "auto-start" , value ) ;
this . getProfile ( ) . save ( ) ;
addLog ( "info" , ` Auto-start set to: ${ value } ` ) ;
} ,
2026-02-01 13:30:11 +08:00
2026-02-02 14:34:34 +08:00
"inspect-apis" ( ) {
addLog ( "info" , "[API Inspector] Starting DEEP inspection..." ) ;
2026-02-01 13:30:11 +08:00
2026-02-03 20:04:45 +08:00
// 获取函数参数的辅助函数
2026-02-02 14:34:34 +08:00
const getArgs = ( func ) => {
try {
const str = func . toString ( ) ;
const match = str . match ( /function\s.*?\(([^)]*)\)/ ) || str . match ( /.*?\(([^)]*)\)/ ) ;
if ( match ) {
return match [ 1 ] . split ( "," ) . map ( arg => arg . trim ( ) ) . filter ( a => a ) . join ( ", " ) ;
}
return ` ${ func . length } args ` ;
} catch ( e ) {
return "?" ;
}
} ;
2026-02-03 20:04:45 +08:00
// 检查对象的辅助函数
2026-02-02 14:34:34 +08:00
const inspectObj = ( name , obj ) => {
if ( ! obj ) return { name , exists : false } ;
const props = { } ;
const proto = Object . getPrototypeOf ( obj ) ;
// 组合自身属性和原型属性
const allKeys = new Set ( [ ... Object . getOwnPropertyNames ( obj ) , ... Object . getOwnPropertyNames ( proto || { } ) ] ) ;
allKeys . forEach ( key => {
2026-02-03 20:04:45 +08:00
if ( key . startsWith ( "_" ) ) return ; // 跳过私有属性
2026-02-02 14:34:34 +08:00
try {
const val = obj [ key ] ;
if ( typeof val === 'function' ) {
props [ key ] = ` func( ${ getArgs ( val ) } ) ` ;
} else {
props [ key ] = typeof val ;
}
} catch ( e ) { }
} ) ;
return { name , exists : true , props } ;
} ;
2026-02-03 20:04:45 +08:00
// 1. 检查标准对象
2026-02-02 14:34:34 +08:00
const standardObjects = {
"Editor.assetdb" : Editor . assetdb ,
"Editor.Selection" : Editor . Selection ,
"Editor.Ipc" : Editor . Ipc ,
"Editor.Panel" : Editor . Panel ,
"Editor.Scene" : Editor . Scene ,
"Editor.Utils" : Editor . Utils ,
"Editor.remote" : Editor . remote
} ;
const report = { } ;
Object . keys ( standardObjects ) . forEach ( key => {
report [ key ] = inspectObj ( key , standardObjects [ key ] ) ;
} ) ;
2026-02-01 13:30:11 +08:00
2026-02-03 20:04:45 +08:00
// 2. 检查特定论坛提到的 API
2026-02-02 14:34:34 +08:00
const forumChecklist = [
"Editor.assetdb.queryInfoByUuid" ,
"Editor.assetdb.assetInfoByUuid" ,
"Editor.assetdb.move" ,
"Editor.assetdb.createOrSave" ,
"Editor.assetdb.delete" ,
"Editor.assetdb.urlToUuid" ,
"Editor.assetdb.uuidToUrl" ,
"Editor.assetdb.fspathToUrl" ,
"Editor.assetdb.urlToFspath" ,
"Editor.remote.assetdb.uuidToUrl" ,
"Editor.Selection.select" ,
"Editor.Selection.clear" ,
"Editor.Selection.curSelection" ,
"Editor.Selection.curGlobalActivate"
] ;
const checklistResults = { } ;
forumChecklist . forEach ( path => {
const parts = path . split ( "." ) ;
2026-02-03 20:04:45 +08:00
let curr = global ; // 在主进程中, Editor 是全局的
2026-02-02 14:34:34 +08:00
let exists = true ;
for ( const part of parts ) {
if ( curr && curr [ part ] ) {
curr = curr [ part ] ;
} else {
exists = false ;
break ;
}
2026-02-01 13:30:11 +08:00
}
2026-02-02 14:34:34 +08:00
checklistResults [ path ] = exists ? ( typeof curr === 'function' ? ` Available( ${ getArgs ( curr ) } ) ` : "Available" ) : "Missing" ;
} ) ;
addLog ( "info" , ` [API Inspector] Standard Objects: \n ${ JSON . stringify ( report , null , 2 ) } ` ) ;
addLog ( "info" , ` [API Inspector] Forum Checklist: \n ${ JSON . stringify ( checklistResults , null , 2 ) } ` ) ;
2026-02-01 13:30:11 +08:00
2026-02-03 20:04:45 +08:00
// 3. 检查内置包 IPC 消息
2026-02-02 14:34:34 +08:00
const ipcReport = { } ;
const builtinPackages = [ "scene" , "builder" , "assets" ] ; // 核心内置包
const fs = require ( "fs" ) ;
builtinPackages . forEach ( pkgName => {
2026-02-01 13:30:11 +08:00
try {
2026-02-02 14:34:34 +08:00
const pkgPath = Editor . url ( ` packages:// ${ pkgName } /package.json ` ) ;
if ( pkgPath && fs . existsSync ( pkgPath ) ) {
const pkgData = JSON . parse ( fs . readFileSync ( pkgPath , "utf-8" ) ) ;
if ( pkgData . messages ) {
ipcReport [ pkgName ] = Object . keys ( pkgData . messages ) ;
} else {
ipcReport [ pkgName ] = "No messages defined" ;
}
} else {
ipcReport [ pkgName ] = "Package path not found" ;
2026-02-01 13:30:11 +08:00
}
2026-02-02 14:34:34 +08:00
} catch ( e ) {
ipcReport [ pkgName ] = ` Error: ${ e . message } ` ;
2026-02-01 13:30:11 +08:00
}
} ) ;
2026-02-02 14:34:34 +08:00
addLog ( "info" , ` [API Inspector] Built-in IPC Messages: \n ${ JSON . stringify ( ipcReport , null , 2 ) } ` ) ;
} ,
2026-02-01 13:30:11 +08:00
} ,
2026-02-02 14:34:34 +08:00
2026-02-01 13:30:11 +08:00
// 全局文件搜索
findInFile ( args , callback ) {
const { query , extensions , includeSubpackages } = args ;
const assetsPath = Editor . assetdb . urlToFspath ( "db://assets" ) ;
const validExtensions = extensions || [ ".js" , ".ts" , ".json" , ".fire" , ".prefab" , ".xml" , ".txt" , ".md" ] ;
const results = [ ] ;
const MAX _RESULTS = 500 ; // 限制返回结果数量,防止溢出
try {
// 递归遍历函数
const walk = ( dir ) => {
if ( results . length >= MAX _RESULTS ) return ;
const list = fs . readdirSync ( dir ) ;
list . forEach ( ( file ) => {
if ( results . length >= MAX _RESULTS ) return ;
// 忽略隐藏文件和 node_modules
if ( file . startsWith ( '.' ) || file === 'node_modules' || file === 'bin' || file === 'local' ) return ;
2026-02-10 09:14:50 +08:00
const filePath = pathModule . join ( dir , file ) ;
2026-02-01 13:30:11 +08:00
const stat = fs . statSync ( filePath ) ;
if ( stat && stat . isDirectory ( ) ) {
walk ( filePath ) ;
} else {
// 检查后缀
2026-02-10 09:14:50 +08:00
const ext = pathModule . extname ( file ) . toLowerCase ( ) ;
2026-02-01 13:30:11 +08:00
if ( validExtensions . includes ( ext ) ) {
try {
const content = fs . readFileSync ( filePath , 'utf8' ) ;
// 简单的行匹配
const lines = content . split ( '\n' ) ;
lines . forEach ( ( line , index ) => {
if ( results . length >= MAX _RESULTS ) return ;
if ( line . includes ( query ) ) {
// 转换为项目相对路径 (db://assets/...)
2026-02-10 09:14:50 +08:00
const relativePath = pathModule . relative ( assetsPath , filePath ) ;
2026-02-01 13:30:11 +08:00
// 统一使用 forward slash
2026-02-10 09:14:50 +08:00
const dbPath = "db://assets/" + relativePath . split ( pathModule . sep ) . join ( '/' ) ;
2026-02-01 13:30:11 +08:00
results . push ( {
filePath : dbPath ,
line : index + 1 ,
content : line . trim ( )
} ) ;
}
} ) ;
} catch ( e ) {
// 读取文件出错,跳过
}
}
}
} ) ;
} ;
walk ( assetsPath ) ;
callback ( null , results ) ;
} catch ( err ) {
callback ( ` Find in file failed: ${ err . message } ` ) ;
}
} ,
// 管理撤销/重做
manageUndo ( args , callback ) {
const { action , description } = args ;
try {
switch ( action ) {
case "undo" :
Editor . Ipc . sendToPanel ( "scene" , "scene:undo" ) ;
callback ( null , "Undo command executed" ) ;
break ;
case "redo" :
Editor . Ipc . sendToPanel ( "scene" , "scene:redo" ) ;
callback ( null , "Redo command executed" ) ;
break ;
case "begin_group" :
// scene:undo-record [id]
// 这里的 id 好像是可选的,或者用于区分不同的事务
Editor . Ipc . sendToPanel ( "scene" , "scene:undo-record" , description || "MCP Action" ) ;
callback ( null , ` Undo group started: ${ description || "MCP Action" } ` ) ;
break ;
case "end_group" :
Editor . Ipc . sendToPanel ( "scene" , "scene:undo-commit" ) ;
callback ( null , "Undo group committed" ) ;
break ;
case "cancel_group" :
Editor . Ipc . sendToPanel ( "scene" , "scene:undo-cancel" ) ;
callback ( null , "Undo group cancelled" ) ;
break ;
default :
callback ( ` Unknown undo action: ${ action } ` ) ;
}
} catch ( err ) {
callback ( ` Undo operation failed: ${ err . message } ` ) ;
}
} ,
2026-02-04 01:57:12 +08:00
// 获取文件 SHA-256
getSha ( args , callback ) {
const { path : url } = args ;
const fspath = Editor . assetdb . urlToFspath ( url ) ;
if ( ! fspath || ! fs . existsSync ( fspath ) ) {
return callback ( ` File not found: ${ url } ` ) ;
}
try {
const fileBuffer = fs . readFileSync ( fspath ) ;
const hashSum = crypto . createHash ( 'sha256' ) ;
hashSum . update ( fileBuffer ) ;
const sha = hashSum . digest ( 'hex' ) ;
callback ( null , { path : url , sha : sha } ) ;
} catch ( err ) {
callback ( ` Failed to calculate SHA: ${ err . message } ` ) ;
}
} ,
// 管理动画
manageAnimation ( args , callback ) {
// 转发给场景脚本处理
Editor . Scene . callSceneScript ( "mcp-bridge" , "manage-animation" , args , callback ) ;
} ,
2026-01-29 13:47:38 +08:00
} ;