unity-mcp scripting_ext 工具组指南:在 Unity 编辑器中执行任意 C# 代码与批量管理 ScriptableObject
【免费下载链接】unity-mcpUnity MCP acts as a bridge between AI assistants and your Unity Editor. Give your LLM tools to manage assets, control scenes, edit scripts, and automate tasks within Unity.项目地址: https://gitcode.com/GitHub_Trending/un/unity-mcp
导读
scripting_ext是 unity-mcp(MCP for Unity)提供的"脚本扩展"工具组,包含两个把 AI 助手能力直接注入 Unity 编辑器的核心工具:execute_code与manage_scriptable_object。前者允许 LLM 在编辑器进程内即时编译并运行任意 C# 方法体(无需创建任何脚本文件),后者则通过 Unity 原生的SerializedObject/SerializedProperty属性路径批量创建、修改 ScriptableObject 资源。读完本文,你将掌握这两个工具的完整参数语义、四种 action 的使用方式、编译器后端(auto/roslyn/codedom)的选择逻辑、patch 结构与对象引用/子资源(Sprite)的赋值技巧,以及它们在服务端、Unity 端与测试中的底层实现原理。
工具组概览:scripting_ext定位
scripting_ext的索引文档 index.md 将本工具组定义为一句话:ScriptableObject management(以及代码执行)。它在工具目录体系中属于"脚本扩展"分组,与core、asset_gen、probuilder、profiling、testing、ui、vfx等分组并列(见 tools/index.md 与 scripting_ext/category.json)。
两个工具在manifest.json中均以scripting_ext分组注册(execute_code见 manifest.json,manage_scriptable_object见 manifest.json)。从源码结构看,它们的 Python 端入口分别位于 Server/src/services/tools/execute_code.py 与 Server/src/services/tools/manage_scriptable_object.py,Unity 端处理器分别为 MCPForUnity/Editor/Tools/ExecuteCode.cs 与 MCPForUnity/Editor/Tools/ManageScriptableObject.cs。两个工具都被标记了destructiveHint=True(破坏性提示),意味着 MCP 客户端会要求用户显式确认后才允许调用。
execute_code:在 Unity 编辑器内即时编译并执行 C# 代码
功能定位与执行模型
execute_code的核心价值在于:代码以方法体(method body)形式运行,可直接访问UnityEngine与UnityEditor命名空间,通过return把数据回传给 LLM。编译在内存中完成,不创建任何 .cs 脚本文件,也不会污染项目 Assets 目录。
Unity 端的实现封装在 ExecuteCode.cs 中,其执行模型为:
- 用户代码被包装进一个固定模板(
WrapUserCode),生成类似下面的动态类:
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine; using UnityEditor; public static class MCPDynamicCode { public static object Execute() { // <-- 你的代码插入在这里 return ...; } }动态程序集以"每次编译一个全新的内存程序集"的方式产生。由于 Mono 无法卸载已加载的程序集,重复编译相同代码会导致内存泄漏,因此实现引入了一个以"包装后源码 + 编译器"为 key 的编译缓存(
_compiledCache,上限 64 条,见 ExecuteCode.cs);同时InitializeOnLoadMethod会在域重载(domain reload)时清空全部缓存与 Roslyn 反射缓存。代码长度上限为 50,000 字符(
MaxCodeLength);历史记录最多保留 50 条(MaxHistoryEntries),历史条目中的代码预览截断为 500 字符、结果预览截断为 200 字符。
参数总览
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
action | Literal['execute', 'get_history', 'replay', 'clear_history'] | 是 | 要执行的动作 |
code | str \| None | — | 待执行的 C# 代码(仅execute使用)。必须是合法方法体,可访问 UnityEngine/UnityEditor,用return返回数据 |
safety_checks | bool | — | 是否启用危险模式拦截(File.Delete、Process.Start、死循环等)。注意:不是完整沙箱,高级绕过仍可能。默认true |
index | int \| None | — | 要重放的历史条目索引(仅replay使用) |
limit | int | — | get_history返回的历史条数(1–50)。默认 10 |
compiler | Literal['auto', 'roslyn', 'codedom'] | — | 编译器后端。auto在安装了 Microsoft.CodeAnalysis 时用 Roslyn,否则回退 CodeDom;roslyn强制 Roslyn(C# 12+);codedom强制旧版 CSharpCodeProvider(仅 C# 6)。默认auto |
服务端 execute_code.py 会校验参数的组合关系:execute动作必须携带code(缺失时直接返回Parameter 'code' is required for 'execute' action.),replay动作必须携带index,而get_history的limit会被钳制在 1–50 之间(max(1, min(limit, 50))),随后把参数通过send_with_unity_instance以命令名execute_code转发给 Unity 端。
四种 action 的用法
execute(执行代码)
最基本的用法,让 LLM 读取当前场景信息后回传数据。例如查询场景中 GameObject 数量:
action: execute code: return Object.FindObjectsOfType<GameObject>().Length;执行结果会以{ "success": true, "message": "...", "data": { "result": ..., "compiler": "roslyn" } }形式返回。返回值序列化遵循 SerializeResult:原始类型(isPrimitive)、string、decimal直接返回;其余对象尝试用JToken.FromObject转 JSON,失败则回退为ToString()。
get_history(查看历史)
列出最近的执行记录,每条包含index、codePreview(500 字符截断)、success、resultPreview(200 字符截断)、elapsedMs(耗时,毫秒)、timestamp、safetyChecksEnabled、compiler。limit控制返回条数,历史为空时返回No execution history.。
replay(重放历史)
按index取回某条历史中的原始code,并以该条记录当时的safety_checks与compiler设置重新走一遍 execute 流程(见 HandleReplay)。这在"上一条代码执行成功但后续修改搞坏了状态"时特别有用,可以快速回滚到可用版本。注意:索引越界或历史为空时会返回错误,合法范围是0 ~ 历史条数-1。
clear_history(清空历史)
清空全部历史记录并返回被清除的条数。
safety_checks拦截的黑名单模式
当safety_checks=true(默认)时,CheckBlockedPatterns 会做大小写不敏感的字符串包含匹配,命中即拒绝执行。完整黑名单见 ExecuteCode.cs:
System.IO.File.Delete System.IO.Directory.Delete FileUtil.DeleteFileOrDirectory AssetDatabase.DeleteAsset AssetDatabase.MoveAssetToTrash EditorApplication.Exit Process.Start Process.Kill while(true) / while (true) for(;;) / for (;;)命中时的错误信息形如Blocked pattern detected: 'AssetDatabase.DeleteAsset',并提示"如属有意为之,可用 safety_checks=false 关闭"。请务必理解文档与源码中的双重强调:这是一道"防呆"而不是"防攻击"的关卡——字符串匹配很容易被改写绕过(如File.Dele+te(...)),绝不能在不可信输入场景下把它当作安全边界。两个工具在ToolAnnotations上都标记了destructiveHint,也是同样的提醒。
三种编译器后端:auto / roslyn / codedom
| 后端 | 语言能力 | 依赖 | 说明 |
|---|---|---|---|
auto(默认) | Roslyn 可用则 C# 12+,否则 C# 6 | Microsoft.CodeAnalysis(可选) | 自动探测并择优 |
roslyn | C# 12+ | 必须安装 Microsoft.CodeAnalysis,否则报错并提示改用 codedom | 推荐用于现代语法与更清晰的诊断信息 |
codedom | 仅 C# 6 | 随 .NET 内置的CSharpCodeProvider | 兜底方案,处理旧语法 |
Roslyn 后端通过纯反射调用(RoslynCompiler),对Microsoft.CodeAnalysis没有任何编译期依赖——包未安装时IsAvailable返回 false,auto模式自动回退到 CodeDom,roslyn模式则返回Roslyn (Microsoft.CodeAnalysis) is not available.。CSharpParseOptions的语言版本被设为Latest,因此支持最新的 C# 语法特性。
CodeDom 路径有两个值得一提的工程细节(源码注释均有交代):
- 响应文件(.rsp)规避命令行超长:
CSharpCodeProvider会把每个ReferencedAssemblies转成csc命令行的字面/r:"..."参数。当项目有 100+ 个 asmdef 时,引用路径会击穿 Windows 的 32 KBCreateProcess参数长度上限(报 "The filename or extension is too long")。实现改为把所有/r:引用写入临时 .rsp 响应文件,命令行只传一个短参数@"..."。 - 重复程序集去重:CSharpCodeProvider 无法解析类型转发(type-forwarding),当
netstandard.dll与mscorlib/System.Runtime/System.Collections同时加载时,List<T>等类型会出现在多个程序集中导致 "type defined multiple times" 错误。因此 FilterAssemblyPathsForCodeDom 会在存在 netstandard 时剔除这组重复程序集,并按"被引用次数优先、版本次之"的策略对同名程序集去重。
此外,CodeDom 编译先输出到临时 DLL 再Assembly.Load,而非直接内存编译——这是因为 Mono 的 CodeDom 会把 mcs 输出到 stdout 的 BOM 行误判为一条伪造错误,导致明明编译成功却拿不到程序集(详见 CodeDomCompile)。
编译与运行错误处理
- 编译错误:错误行号会通过
WrapperLineOffset(值为 10)减去包装模板占用的行数,映射回用户代码的真实行号,返回形如Line 3: ...的诊断。 - 运行错误:反射调用抛出的
TargetInvocationException会被解包,返回Runtime error: <message>,并附带exceptionType、stackTrace与compiler字段,便于 LLM 定位问题。
服务端行为与测试佐证
服务端把 Unity 的原始响应归一化为{success, message, data}三字段结构。单元测试 Server/tests/test_execute_code.py 覆盖了:code正确透传(test_execute_forwards_code_to_unity)、safety_checks默认true与显式false的传递、返回值透传(data.result == 42)、code缺失时报错,以及get_history的limit透传等场景。
典型实战场景
- 快速验证 API:不确定某个
UnityEditorAPI 的写法?直接执行一段代码验证,无需新建脚本、等待编译。 - 批量只读查询:统计场景/资源数据后
return给 LLM 决策。 - 临时修复:对运行态数据做即时修补(注意对资源的持久化修改推荐走
manage_scriptable_object这类专用工具,语义更安全)。
manage_scriptable_object:基于 SerializedProperty 路径的 ScriptableObject 资产管理
功能定位与设计理念
manage_scriptable_object把 ScriptableObject 的"创建 + 修改"合并到一个工具中,且修改走的是 Unity 原生SerializedObject/SerializedProperty路径,而非反射(见 ManageScriptableObject.cs 的注释)。这意味着:支持 Undo、属性变更可被 Inspector 正确识别、不依赖字段名的大小写/命名约定,天然兼容序列化系统的各种规则。
Unity 端在收到命令时首先检查EditorStateCache.GetActualIsCompiling() || EditorApplication.isUpdating,处于编译/刷新状态会返回compiling_or_reloading错误并带hint = "retry",客户端可据此稍后重试(见 ManageScriptableObject.cs)。
参数总览
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
action | Literal['create', 'modify'] | 是 | create 或 modify |
type_name | str \| None | — | ScriptableObject 类型的完整命名空间限定名(create 用) |
folder_path | str \| None | — | Assets/... 下的目标文件夹(create 用) |
asset_name | str \| None | — | 资源文件名(不含扩展名,create 用) |
overwrite | bool \| str \| None | — | 为 true 时覆盖同路径已存在资源(create 用) |
target | dict \| str \| None | — | 目标资源引用{guid \| path}(modify 用) |
patches | list \| str \| None | — | 补丁列表(或 JSON 字符串)。对象引用用{"ref": {"guid": "..."}}或{"value": {"guid": "..."}};Sprite 子资源需在 ref/value 对象中携带spriteName;单精灵贴图可仅凭 guid/path 自动解析 |
dry_run | bool \| str \| None | — | 为 true 时只校验不落地(仅 modify) |
create 动作:创建 ScriptableObject 资源
create需要type_name、folder_path、asset_name三个参数(缺任一都会报invalid_params),并做以下校验(见 HandleCreate):
asset_name不得包含路径分隔符(/或\);folder_path会被规范化并确保文件夹存在(不存在则自动创建);type_name会被解析为实际类型,且必须可赋值给ScriptableObject,否则返回type_not_found。
create同样可以携带patches,在创建实例后立即应用属性补丁再保存资源,实现"一步建好并填好内容"。此外 action 还兼容了createso/modifyso的别名(经NormalizeAction归一化,见 ManageScriptableObject.cs)。
modify 动作:按属性路径打补丁
modify通过target({guid}或{path})定位资源,patches是核心参数。服务端 manage_scriptable_object.py 会先用parse_json_payload容忍 LLM 把复杂对象序列化成 JSON 字符串的情况(target必须是对象、patches必须是数组,否则直接报错),再以typeName/folderPath/assetName/overwrite/target/patches/dryRun的驼峰键转发给 Unity。
patch 结构(每条补丁对象):
| 字段 | 说明 |
|---|---|
propertyPath(也兼容property_path/path) | 目标属性路径,支持displayName、nested.field等点分路径 |
op | 操作类型,set(默认)或array_resize |
value | 要写入的值;对象引用属性也接受ref键(ref优先于value,向后兼容) |
例如把displayName设置为"Hello"的补丁:
[ { "propertyPath": "displayName", "op": "set", "value": "Hello" } ]友好路径自动归一化
写数组属性时,Unity 内部路径格式是myList.Array.data[0],手工书写繁琐且易错。工具内置了NormalizePropertyPath(见 ManageScriptableObject.cs):myList[5]、nested.list[0].field这类中括号写法会自动转换为myList.Array.data[5],而已处于.Array.data[...]格式的路径则原样保留。
数组操作:set与array_resize
- 批量 set:当属性是数组/列表且
value为 JSON 数组时,TrySetValueRecursive 会先把arraySize调整为数组长度,再逐个元素递归赋值,支持部分成功(返回Set 2/3 elements. ...)。 - array_resize:单独调整数组长度,常用于"先扩容再逐元素赋值"的多步流程,例如:
[ { "propertyPath": "materials.Array.size", "op": "array_resize", "value": 2 } ]array_resize要求value为非负整数;应用后立即ApplyModifiedProperties()并Update(),保证后续补丁能基于新数组解析元素路径(见 ApplyPatches)。
对象引用:ref/value+ guid
当目标属性是ObjectReference类型时,赋值必须携带引用来源。推荐的写法是(详见 ApplySet):
[ { "propertyPath": "someMaterial", "op": "set", "value": { "guid": "3f2a...c9d1" } } ]或使用旧的ref键(优先于value):{ "propertyPath": "...", "op": "set", "ref": { "guid": "3f2a...c9d1" } }。ref/value也会被解析为纯字符串 GUID 的形态。引用解析实现在 ComponentOps.SetObjectReference 中,它需要同时处理 guid、路径、fileID 以及 spriteName 子资源等形态(见 ComponentOps.cs 附近注释)。设置成功会返回Set reference to '<name>'.,传 null 则清除引用。
Sprite 子资源:spriteName 与自动解析
Sprite 通常作为贴图(Texture)导入资源的**子资源(sub-asset)**存在,仅凭 guid/path 无法唯一确定引用哪个 Sprite。为此工具约定:
- 单精灵贴图:仅凭
guid/path即可自动解析; - 精灵图集(atlas)或多精灵贴图:必须在
ref/value对象里携带spriteName:
[ { "propertyPath": "icon", "op": "set", "value": { "guid": "5b1a...e0ff", "spriteName": "coin" } } ]实现会在目标资源中按名称查找匹配的Sprite子资源,找不到时报Sprite '<name>' not found in atlas '<path>'.,类型不匹配时报对应兼容性错误(见 ComponentOps.cs)。
Generic 复杂对象映射
对Generic类型的结构体/类字段(如自定义可序列化类),可直接传 JSON 对象做字段级递归映射(见 TrySetValueRecursive),例如{ "propertyPath": "settings", "value": { "volume": 0.8, "loop": true } }。递归深度上限为 20,防止循环引用导致的栈溢出。
dry_run:安全演练模式
dry_run=true(仅 modify)会进入 ValidatePatches 分支:校验每条 patch 的属性路径是否存在、值类型是否兼容、array_resize的 value 是否为非负整数,但不做任何写入。对AnimationCurve与Quaternion还会做格式校验(通过 VectorParsing.ValidateAnimationCurveFormat / ValidateQuaternionFormat)。每个 patch 返回{index, propertyPath, op, ok, message}形式的校验结果。这是 LLM 在不确定属性路径是否正确时最稳妥的探索手段——先 dry_run 验证,再正式执行。
变更落盘流程
成功应用的补丁会按序执行:所有 patch 处理完后统一so.ApplyModifiedProperties()、EditorUtility.SetDirty(target)、AssetDatabase.SaveAssets()(见 ApplyPatches),确保资源变更立即持久化到磁盘。Unity 处于编译/刷新状态时返回compiling_or_reloading并提示retry。
测试佐证
集成测试 Server/tests/integration/test_manage_scriptable_object_tool.py 覆盖了:create 参数的透传、modify 时 patches 的转发(含displayNameset 与materials.Array.sizearray_resize 两种形态)、dry_run参数的转发,以及 dry_run 以 JSON 字符串形式传入时的强制转换(test_manage_scriptable_object_dry_run_string_coercion)等场景,可作为"服务端如何把 LLM 的字符串化参数还原为结构化数据"的参考。
实战组合:一条完整的脚本化工作流
将两个工具串联,可以在一次多轮对话中完成"创建配置资源 → 批量填值 → 校验 → 落地"的完整闭环:
- create:
action=create、type_name=MyGameConfig、folder_path=Assets/Configs、asset_name=Level01。 - dry_run 预演:
action=modify、target={"guid":"..."}、dry_run=true,先确认playerSettings.speed、levels.Array.size等路径无误。 - modify 落地:携带完整 patches(普通字段 set、数组 array_resize 后再逐元素赋值、对象引用
{"ref":{"guid":...}}、精灵图集引用加spriteName)正式执行。 - 如需临时逻辑修补,再配合
execute_code执行一段只读/验证代码,利用return获取反馈。
安全边界与最佳实践小结
execute_code不是沙箱:文档(execute_code.md)与源码注释都明确警告safety_checks只是"拦截已知危险模式",高级绕过是可能的。仅在可信环境下对受信任的 LLM 开启,并善用destructiveHint确认机制。- 优先专用工具:涉及资源持久化、序列化属性修改时优先
manage_scriptable_object(原生 SerializedObject 路径 + Undo + 落盘),把execute_code留给探索性与一次性验证任务。 - 善用 dry_run 与 get_history:不确定路径用
dry_run探路;执行出错用get_history+replay找回可用版本。 - 理解编译器差异:需要 C# 12+ 现代语法时确保安装了 Microsoft.CodeAnalysis(Roslyn),安装引导可参考 website/docs/guides/roslyn.md 中关于
USE_ROSLYN定义符号与Assets/Plugins/Roslyn/的说明;仓库内另有独立的运行时编译演示组件 CustomTools/RoslynRuntimeCompilation/RoslynRuntimeCompiler.cs 可作参考。
scripting_ext这两个工具共同构成了 unity-mcp 中"让 AI 直接操作编辑器运行时与序列化数据"的底层通道:一个面向瞬时执行,一个面向资源持久化,配合core组的场景/资源管理工具,可以覆盖绝大多数自动化游戏开发工作流。
【免费下载链接】unity-mcpUnity MCP acts as a bridge between AI assistants and your Unity Editor. Give your LLM tools to manage assets, control scenes, edit scripts, and automate tasks within Unity.项目地址: https://gitcode.com/GitHub_Trending/un/unity-mcp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考