docs: 完善 MCP 工具说明,注入 AI 安全守则并优化资产操作规范

This commit is contained in:
火焰库拉
2026-02-10 00:38:38 +08:00
parent bf6ec93b99
commit 23c6ea13f9
5 changed files with 216 additions and 90 deletions

View File

@@ -11,7 +11,8 @@
2. **技术栈**: 新脚本必须使用 **TypeScript** (`.ts`)。禁止创建新的 `.js` 文件 (除非是构建脚本或测试配置)。
3. **文档**: 所有修改或创建的脚本必须包含详细的 JSDoc 格式注释。
4. **架构**: 严禁引入新的架构模式或重型外部库。必须复用现有的 Cocos Creator 管理器和工具类。
5. **隔离原则**: 保持 `main.js` (主进程) 与 `scene-script.js` (渲染进程) 的严格职责分离。即使看似方便,也不要在 `main.js` 中直接操作场景节点对象
5. **隔离原则**: 保持 `main.js` (主进程) 与 `scene-script.js` (渲染进程) 的严格职责分离。即便是在主进程中,也应通过 IPC 与场景脚本交互
6. **主体校验规则 (Subject Validation Rule)**: 在对节点、组件或属性进行任何“写”操作之前AI 必须先验证主体的存在性与类型正确性。严禁基于假设进行操作。
## 1. 项目初始化

View File

@@ -113,7 +113,8 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
- `id`: 节点 UUID
- `x`, `y`: 坐标
- `scaleX`, `scaleY`: 缩放值
- `color`: HEX 颜色代码(如 #FF0000(支持撤销操作)
- `color`: HEX 颜色代码(如 #FF0000
- **重要提示**: 执行前必须调用 `get_scene_hierarchy` 确保 `id` 有效,防止操作不存在的节点。
### 6. open_scene
@@ -149,7 +150,11 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
- `action`: 操作类型(`add`, `remove`, `get`, `update`
- `componentType`: 组件类型,如 `cc.Sprite`(用于 `add`/`update` 操作)
- `componentId`: 组件 ID用于 `remove`/`update` 操作)
- `properties`: 组件属性(用于 `add`/`update` 操作)。**智能特性**:如果属性期望组件类型但传入节点 UUID插件会自动查找匹配组件。
- `properties`: 组件属性(用于 `add`/`update` 操作)。
- **智能特性**
1. 如果属性期望组件类型但传入节点 UUID插件会自动查找匹配组件。
2. 对于资源类属性(如 `cc.Prefab`, `sp.SkeletonData`),传递资源的 UUID插件会自动处理异步加载与序列化确保不出现 Type Error。
- **操作规则 (Subject Validation Rule)**:赋值或更新前必须确保目标属性在组件上真实存在。
### 9. manage_script
@@ -286,7 +291,7 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
### 24. manage_vfx
- **描述**: 特效(粒子)管理
- **描述**: 特效(粒子)管理。重要提示:操作前必须确认父节点或目标节点的有效性。
- **参数**:
- `action`: 操作类型 (`create`, `update`, `get_info`)
- `nodeId`: 节点 UUID (用于 `update`, `get_info`)
@@ -368,6 +373,17 @@ Args: [你的项目所在盘符]:/[项目路径]/packages/mcp-bridge/mcp-proxy.j
- 插件会自动标记场景为"已修改",请注意保存场景
- 不同版本的 Cocos Creator 可能会有 API 差异,请根据实际情况调整
## AI 操作安全守则 (Subject Validation Rule)
为了保证自动化操作的稳定性AI 在使用本插件工具时必须遵循以下守则:
1. **确定性优先**:任何对节点、组件、属性的操作,都必须建立在“主体已确认存在”的基础上。
2. **校验流程**
* **节点校验**:操作前必须使用 `get_scene_hierarchy` 确认节点。
* **组件校验**:操作组件前必须使用 `get`(通过 `manage_components`)确认组件存在。
* **属性校验**:更新属性前必须确认属性名准确无误。
3. **禁止假设**:禁止盲目尝试对不存在的对象或属性进行修改。
## 贡献

78
main.js
View File

@@ -91,15 +91,16 @@ const getNewSceneTemplate = () => {
* @returns {Array<Object>} 工具定义数组
*/
const getToolsList = () => {
const globalPrecautions = "【AI 安全守则】: 1. 执行任何写操作前必须先通过 get_scene_hierarchy 或 manage_components(get) 验证主体存在。 2. 严禁基于假设盲目猜测属性名。 3. 资源属性(如 cc.Prefab必须通过 UUID 进行赋值。";
return [
{
name: "get_selected_node",
description: "获取当前编辑器中选中的节点 ID。建议获取后立即调用 get_scene_hierarchy 确认该节点是否仍存在于当前场景中。",
description: `${globalPrecautions} 获取当前编辑器中选中的节点 ID。建议获取后立即调用 get_scene_hierarchy 确认该节点是否仍存在于当前场景中。`,
inputSchema: { type: "object", properties: {} },
},
{
name: "set_node_name",
description: "修改指定节点的名称",
description: `${globalPrecautions} 修改指定节点的名称`,
inputSchema: {
type: "object",
properties: {
@@ -111,17 +112,17 @@ const getToolsList = () => {
},
{
name: "save_scene",
description: "保存当前场景的修改",
description: `${globalPrecautions} 保存当前场景的修改`,
inputSchema: { type: "object", properties: {} },
},
{
name: "get_scene_hierarchy",
description: "获取当前场景的完整节点树结构(包括 UUID、名称和层级关系",
description: `${globalPrecautions} 获取当前场景的完整节点树结构(包括 UUID、名称和层级关系`,
inputSchema: { type: "object", properties: {} },
},
{
name: "update_node_transform",
description: "修改节点的坐标、缩放或颜色。执行前必须调用 get_scene_hierarchy 确保 node ID 有效。",
description: `${globalPrecautions} 修改节点的坐标、缩放或颜色。执行前必须调用 get_scene_hierarchy 确保 node ID 有效。`,
inputSchema: {
type: "object",
properties: {
@@ -137,7 +138,7 @@ const getToolsList = () => {
},
{
name: "create_scene",
description: "在 assets 目录下创建一个新的场景文件。创建并通过 open_scene 打开后,请务必初始化基础节点(如 Canvas 和 Camera",
description: `${globalPrecautions} 在 assets 目录下创建一个新的场景文件。创建并通过 open_scene 打开后,请务必初始化基础节点(如 Canvas 和 Camera`,
inputSchema: {
type: "object",
properties: {
@@ -148,7 +149,7 @@ const getToolsList = () => {
},
{
name: "create_prefab",
description: "将场景中的某个节点保存为预制体资源",
description: `${globalPrecautions} 将场景中的某个节点保存为预制体资源`,
inputSchema: {
type: "object",
properties: {
@@ -160,7 +161,7 @@ const getToolsList = () => {
},
{
name: "open_scene",
description: "打开场景文件。注意这是一个异步且耗时的操作打开后请等待几秒。重要如果是新创建或空的场景请务必先创建并初始化基础节点Canvas/Camera",
description: `${globalPrecautions} 打开场景文件。注意这是一个异步且耗时的操作打开后请等待几秒。重要如果是新创建或空的场景请务必先创建并初始化基础节点Canvas/Camera`,
inputSchema: {
type: "object",
properties: {
@@ -174,7 +175,7 @@ const getToolsList = () => {
},
{
name: "create_node",
description: "在当前场景中创建一个新节点。重要提示1. 如果指定 parentId必须先调用 get_scene_hierarchy 确保该父节点真实存在。2. 类型说明:'sprite' (100x100 尺寸 + 默认贴图), 'button' (150x50 尺寸 + 深色底图 + Button组件), 'label' (120x40 尺寸 + Label组件), 'empty' (纯空节点)。",
description: `${globalPrecautions} 在当前场景中创建一个新节点。重要提示1. 如果指定 parentId必须先通过 get_scene_hierarchy 确保该父节点真实存在且未被删除。2. 类型说明:'sprite' (100x100 尺寸 + 默认贴图), 'button' (150x50 尺寸 + 深色底图 + Button组件), 'label' (120x40 尺寸 + Label组件), 'empty' (纯空节点)。`,
inputSchema: {
type: "object",
properties: {
@@ -194,7 +195,7 @@ const getToolsList = () => {
},
{
name: "manage_components",
description: "管理节点组件。重要提示1. 操作前必须调用 get_scene_hierarchy 确认 nodeId 对应的节点仍然存在。2. 添加前先用 'get' 检查是否已存在。3. 添加 cc.Sprite 后必须设置 spriteFrame 属性否则节点不显示。4. 创建按钮时,请确保目标节点有足够的 width 和 height 作为点击区域。",
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。`,
inputSchema: {
type: "object",
properties: {
@@ -209,7 +210,7 @@ const getToolsList = () => {
},
{
name: "manage_script",
description: "管理脚本文件。注意:创建或修改脚本后,编辑器需要时间进行编译(通常几秒钟)。新脚本在编译完成前无法作为组件添加到节点。建议在 create 后调用 refresh_editor或等待一段时间后再使用 manage_components。",
description: `${globalPrecautions} 管理脚本文件。注意:创建或修改脚本后,编辑器需要时间进行编译(通常几秒钟)。新脚本在编译完成前无法作为组件添加到节点。建议在 create 后调用 refresh_editor或等待一段时间后再使用 manage_components。`,
inputSchema: {
type: "object",
properties: {
@@ -223,7 +224,7 @@ const getToolsList = () => {
},
{
name: "batch_execute",
description: "批处理执行多个操作",
description: `${globalPrecautions} 批处理执行多个操作`,
inputSchema: {
type: "object",
properties: {
@@ -245,7 +246,7 @@ const getToolsList = () => {
},
{
name: "manage_asset",
description: "管理资源",
description: `${globalPrecautions} 管理资源`,
inputSchema: {
type: "object",
properties: {
@@ -259,7 +260,7 @@ const getToolsList = () => {
},
{
name: "scene_management",
description: "场景管理",
description: `${globalPrecautions} 场景管理`,
inputSchema: {
type: "object",
properties: {
@@ -277,7 +278,7 @@ const getToolsList = () => {
},
{
name: "prefab_management",
description: "预制体管理",
description: `${globalPrecautions} 预制体管理`,
inputSchema: {
type: "object",
properties: {
@@ -295,7 +296,7 @@ const getToolsList = () => {
},
{
name: "manage_editor",
description: "管理编辑器",
description: `${globalPrecautions} 管理编辑器`,
inputSchema: {
type: "object",
properties: {
@@ -316,7 +317,7 @@ const getToolsList = () => {
},
{
name: "find_gameobjects",
description: "查找游戏对象",
description: `${globalPrecautions} 查找游戏对象`,
inputSchema: {
type: "object",
properties: {
@@ -328,7 +329,7 @@ const getToolsList = () => {
},
{
name: "manage_material",
description: "管理材质",
description: `${globalPrecautions} 管理材质`,
inputSchema: {
type: "object",
properties: {
@@ -341,7 +342,7 @@ const getToolsList = () => {
},
{
name: "manage_texture",
description: "管理纹理",
description: `${globalPrecautions} 管理纹理`,
inputSchema: {
type: "object",
properties: {
@@ -354,7 +355,7 @@ const getToolsList = () => {
},
{
name: "execute_menu_item",
description: "执行菜单项",
description: `${globalPrecautions} 执行菜单项`,
inputSchema: {
type: "object",
properties: {
@@ -365,7 +366,7 @@ const getToolsList = () => {
},
{
name: "apply_text_edits",
description: "应用文本编辑",
description: `${globalPrecautions} 应用文本编辑`,
inputSchema: {
type: "object",
properties: {
@@ -377,7 +378,7 @@ const getToolsList = () => {
},
{
name: "read_console",
description: "读取控制台",
description: `${globalPrecautions} 读取控制台`,
inputSchema: {
type: "object",
properties: {
@@ -388,7 +389,7 @@ const getToolsList = () => {
},
{
name: "validate_script",
description: "验证脚本",
description: `${globalPrecautions} 验证脚本`,
inputSchema: {
type: "object",
properties: {
@@ -399,7 +400,7 @@ const getToolsList = () => {
},
{
name: "find_in_file",
description: "在项目中全局搜索文本内容",
description: `${globalPrecautions} 在项目中全局搜索文本内容`,
inputSchema: {
type: "object",
properties: {
@@ -417,7 +418,7 @@ const getToolsList = () => {
},
{
name: "manage_undo",
description: "管理编辑器的撤销和重做历史",
description: `${globalPrecautions} 管理编辑器的撤销和重做历史`,
inputSchema: {
type: "object",
properties: {
@@ -433,7 +434,7 @@ const getToolsList = () => {
},
{
name: "manage_vfx",
description: "管理全场景特效 (粒子系统)",
description: `${globalPrecautions} 管理全场景特效 (粒子系统)。重要提示:在创建或更新前,必须通过 get_scene_hierarchy 或 manage_components 确认父节点或目标节点的有效性。严禁对不存在的对象进行操作。`,
inputSchema: {
type: "object",
properties: {
@@ -469,7 +470,7 @@ const getToolsList = () => {
},
{
name: "get_sha",
description: "获取指定文件的 SHA-256 哈希值",
description: `${globalPrecautions} 获取指定文件的 SHA-256 哈希值`,
inputSchema: {
type: "object",
properties: {
@@ -480,7 +481,7 @@ const getToolsList = () => {
},
{
name: "manage_animation",
description: "管理节点的动画组件",
description: `${globalPrecautions} 管理节点的动画组件。重要提示:在执行 play/pause 等操作前,必须先确认节点及其 Animation 组件存在。严禁操作空引用。`,
inputSchema: {
type: "object",
properties: {
@@ -1570,9 +1571,9 @@ export default class NewScript extends cc.Component {
'File/Save': 'scene:stash-and-save', // 别名
'Edit/Undo': 'scene:undo',
'Edit/Redo': 'scene:redo',
'Edit/Delete': 'scene:delete-selected',
'Delete': 'scene:delete-selected',
'delete': 'scene:delete-selected',
'Edit/Delete': 'scene:delete-nodes',
'Delete': 'scene:delete-nodes',
'delete': 'scene:delete-nodes',
'Node/Create Empty Node': 'scene:create-node-by-classid', // 简化的映射,通常需要参数
'Project/Build': 'app:build-project',
};
@@ -1592,8 +1593,19 @@ export default class NewScript extends cc.Component {
if (menuMap[menuPath]) {
const ipcMsg = menuMap[menuPath];
try {
Editor.Ipc.sendToMain(ipcMsg);
callback(null, `Menu action triggered: ${menuPath} -> ${ipcMsg}`);
// 获取当前选中的节点进行删除(如果该消息是删除操作)
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}`);
}
} catch (err) {
callback(`Failed to execute IPC ${ipcMsg}: ${err.message}`);
}

View File

@@ -298,71 +298,98 @@ module.exports = {
if (component[key] !== undefined) {
let finalValue = value;
// 【关键修复】智能组件引用赋值
// 如果属性期望一个组件 (cc.Component子类) 但传入的是节点/UUID尝试自动获取组件
// 【核心逻辑】智能类型识别与赋值
try {
// 检查传入值是否是字符串 (可能是 UUID) 或 Node 对象
let targetNode = null;
if (typeof value === 'string') {
targetNode = findNode(value);
if (targetNode) {
Editor.log(`[scene-script] Resolved node: ${value} -> ${targetNode.name}`);
} else if (value.length > 20) {
Editor.warn(`[scene-script] Failed to resolve node: ${value}`);
}
} else if (value instanceof cc.Node) {
targetNode = value;
const attrs = (cc.Class.Attr.getClassAttrs && cc.Class.Attr.getClassAttrs(compClass)) || {};
let propertyType = attrs[key] ? attrs[key].type : null;
if (!propertyType && attrs[key + '$_$ctor']) {
propertyType = attrs[key + '$_$ctor'];
}
if (targetNode) {
// 尝试获取属性定义类型
let typeName = null;
let isAsset = propertyType && (propertyType.prototype instanceof cc.Asset || propertyType === cc.Asset || propertyType === cc.Prefab || propertyType === cc.SpriteFrame);
// 优先尝试 getClassAttrs (Cocos 2.x editor environment)
if (cc.Class.Attr.getClassAttrs) {
const attrs = cc.Class.Attr.getClassAttrs(compClass);
// attrs 是整个属性字典 { name: { type: ... } }
if (attrs) {
if (attrs[key] && attrs[key].type) {
typeName = attrs[key].type;
} else if (attrs[key + '$_$ctor']) {
// 编辑器环境下,自定义组件类型可能存储在 $_$ctor 后缀中
typeName = attrs[key + '$_$ctor'];
// 启发式:如果属性名包含 prefab/sprite/texture 等,且值为 UUID 且不是节点
if (!isAsset && typeof value === 'string' && value.length > 20) {
const lowerKey = key.toLowerCase();
const assetKeywords = ['prefab', 'sprite', 'texture', 'material', 'skeleton', 'spine', 'atlas', 'font', 'audio', 'data'];
if (assetKeywords.some(k => lowerKey.includes(k))) {
if (!findNode(value)) {
isAsset = true;
}
}
}
if (isAsset) {
// 1. 处理资源引用 (cc.Prefab, cc.SpriteFrame 等)
if (typeof value === 'string' && value.length > 20) {
// 在场景进程中异步加载资源,这能确保 serialization 时是正确的老格式对象
cc.AssetLibrary.loadAsset(value, (err, asset) => {
if (!err && asset) {
component[key] = asset;
Editor.log(`[scene-script] Successfully loaded and assigned asset for ${key}: ${asset.name}`);
// 通知编辑器 UI 更新
const compIndex = node._components.indexOf(component);
if (compIndex !== -1) {
Editor.Ipc.sendToPanel('scene', 'scene:set-property', {
id: node.uuid,
path: `_components.${compIndex}.${key}`,
type: 'Object',
value: { uuid: value },
isSubProp: false
});
}
Editor.Ipc.sendToMain("scene:dirty");
} else {
Editor.warn(`[scene-script] Failed to load asset ${value} for ${key}: ${err}`);
}
});
return; // 跳过后续的直接赋值
}
} else if (propertyType && (propertyType.prototype instanceof cc.Component || propertyType === cc.Component || propertyType === cc.Node)) {
// 2. 处理节点或组件引用
const targetNode = findNode(value);
if (targetNode) {
if (propertyType === cc.Node) {
finalValue = targetNode;
} else {
const targetComp = targetNode.getComponent(propertyType);
if (targetComp) {
finalValue = targetComp;
} else {
Editor.warn(`[scene-script] Component ${propertyType.name} not found on node ${targetNode.name}`);
}
}
Editor.log(`[scene-script] Applied Reference for ${key}: ${targetNode.name}`);
} else if (value && value.length > 20) {
// 如果明确是组件/节点类型但找不到,才报错
Editor.warn(`[scene-script] Failed to resolve target node/comp for ${key}: ${value}`);
}
// 兼容性尝试 getClassAttributes
else if (cc.Class.Attr.getClassAttributes) {
const attrs = cc.Class.Attr.getClassAttributes(compClass, key);
if (attrs && attrs.type) {
typeName = attrs.type;
}
}
if (typeName && (typeName.prototype instanceof cc.Component || typeName === cc.Component)) {
// 这是一个组件属性
const targetComp = targetNode.getComponent(typeName);
if (targetComp) {
finalValue = targetComp;
Editor.log(`[scene-script] Auto-resolved component ${typeName.name} from node ${targetNode.name}`);
} else {
// 3. 通用启发式 (找不到类型时的 fallback)
if (typeof value === 'string' && value.length > 20) {
const targetNode = findNode(value);
if (targetNode) {
finalValue = targetNode;
Editor.log(`[scene-script] Heuristic resolved Node for ${key}: ${targetNode.name}`);
} else {
Editor.warn(`[scene-script] Component ${typeName.name} not found on node ${targetNode.name}`);
}
} else if (!typeName) {
// 无法确切知道类型,尝试常见的组件类型推断 (heuristic)
const lowerKey = key.toLowerCase();
if (lowerKey.includes('label')) {
const l = targetNode.getComponent(cc.Label);
if (l) finalValue = l;
} else if (lowerKey.includes('sprite')) {
const s = targetNode.getComponent(cc.Sprite);
if (s) finalValue = s;
// 找不到节点且是 UUID -> 视为资源
const compIndex = node._components.indexOf(component);
if (compIndex !== -1) {
Editor.Ipc.sendToPanel('scene', 'scene:set-property', {
id: node.uuid,
path: `_components.${compIndex}.${key}`,
type: 'Object',
value: { uuid: value },
isSubProp: false
});
Editor.log(`[scene-script] Heuristic resolved Asset via IPC for ${key}: ${value}`);
}
return;
}
}
}
} catch (e) {
// 忽略类型检查错误
Editor.warn(`[scene-script] Property resolution failed for ${key}: ${e.message}`);
}
component[key] = finalValue;

70
注意事项.md Normal file
View File

@@ -0,0 +1,70 @@
# Cocos Creator 插件开发注意事项
## 1. 资源与预制体管理
### 1.1 禁止手动干预 `.meta` 文件
* **问题**:手动使用 `manage_asset` 创建或修改以 `.meta` 结尾的文件会导致编辑器报错(如 `Invalid assetpath, must not use .meta as suffix`)。
* **原因**Cocos Creator 资源数据库会自动识别新资源并生成对应的 `.meta` 文件。外部强行介入会破坏资源索引一致性。
* **最佳实践**:始终使用高级资源工具(如 `prefab_management`)来处理资源,让编辑器自行管理 `.meta` 文件。
### 1.2 预制体的正确创建流程
* **推荐工具**:使用 `prefab_management``create` 操作。
* **核心逻辑**该工具会同步处理节点的序列化、db 路径映射以及资源刷新Refresh确保预制体及其配套的 `.meta` 文件原子化生成。
---
## 2. 脚本属性Inspector关联
### 2.1 路径与 UUID 的区别
* **问题**:在 Inspector 面板中,脚本属性(如 `cc.Prefab``cc.Node`)若通过路径关联,经常会出现 "Type Error"。
* **原因**:编辑器 Inspector 期望获得的是引擎内部的对象引用。虽然 `db://` 路径可以指向文件,但在属性赋值层面,编辑器无法自动将字符串路径转换为对应的类实例。
* **最佳实践**
1. 通过 `manage_asset -> get_info` 获取资源的 **UUID**
2. 在调用 `manage_components -> update` 时,**优先使用 UUID** 进行赋值。
3. UUID 是底层唯一标识,能确保编辑器精准识别资源类型并正确完成引用链接。
---
## 3. 场景同步与编辑器状态
### 3.1 刷新与编译
* **注意事项**:创建新脚本后,必须调用 `manage_editor -> refresh_editor` 或等待几秒钟以触发编译。
* **风险**:在脚本编译完成前尝试将其作为组件添加到节点,会导致 `添加组件失败: Cannot read property 'constructor' of null` 或找不到脚本组件的问题。
### 3.2 节点删除 (IPC 协议)
* **正确协议**:应使用 `Editor.Ipc.sendToMain('scene:delete-nodes', uuid_or_array)`
* **注意**:不要误用 `scene:delete-selected`,因为它在某些版本的编辑器底层不接受参数或行为不一致。
* **技巧**:在 `mcp-bridge` 中,调用 `execute_menu_item("delete-node:NODE_UUID")` 会走场景脚本的直连删除,而 `execute_menu_item("Delete")` 则会走主进程的 `scene:delete-nodes` 并自动带上选中的节点。
---
## 4. MCP Bridge 源码补丁说明
### 4.1 属性解析增强 (Asset 序列化修复)
* **改进点**:在 `scene-script.js``applyProperties` 中通过 `cc.AssetLibrary.loadAsset` 解决了资源属性在 Inspector 报错 "Type Error" 的问题。
* **原理**
* **问题根源**:在场景进程中直接将 UUID 字符串赋给资源属性(如 `comp.prefab = "uuid"`),会导致 `.fire` 文件将其保存为纯字符串而非对象格式。编辑器 Inspector 期望的是资源引用结构 `{ "__uuid__": "..." }`,类型不匹配产生 Type Error。
* **修复逻辑**
1. **真实加载**:使用 `cc.AssetLibrary.loadAsset(uuid, callback)` 在场景进程中异步加载真实的资源实例。
2. **实例赋值**:在回调中将加载到的 `asset` 对象赋予组件(`component[key] = asset`),这确保了场景保存时能生成正确的序列化对象。
3. **UI 同步**:同步发送 IPC `scene:set-property`,使用 `{ uuid: value }` 格式通知编辑器面板更新 Inspector UI。
* **Node/Component**: 对于节点或组件引用,通过 `findNode` 查找实例并直接赋值实例对象,而非 UUID 字符串。
---
## 5. AI 操作安全守则 (Subject Validation Rule)
### 5.1 确定性优先
* **核心法则**:任何对节点、组件、属性的操作,都必须建立在 **“主体已确认存在”** 的基础上。
* **具体流程**
1. **节点校验**:在操作前必须调用 `get_scene_hierarchy` 确认 `nodeId`
2. **组件校验**:在 `update``remove` 前必须调用 `manage_components(action: 'get')` 确认目标组件存在。
3. **属性校验**:严禁猜测属性名。在 `update` 前,应通过读取脚本定义或 `get` 返回的现有属性列表来确定准确的属性名称。
* **禁止行为**:禁止基于假设进行盲目赋值或删除。如果发现对象不存在,应立即报错或尝试重建,而非继续尝试修改。
---
## 6. 常见资源关键字
* **资产识别启发式**:当通过 `manage_components` 赋值时,如果属性名包含以下关键字,插件会尝试将其作为 UUID 资源处理:
`prefab`, `sprite`, `texture`, `material`, `skeleton`, `spine`, `atlas`, `font`, `audio`, `data`
* **建议**:如果资源未正确加载,请检查属性名是否包含以上关键字,或手动确认该 UUID 不属于任何节点。