news 2026/5/9 14:23:32

91 个 MCP 工具中最关键的一个:Funplay Unity MCP 的 execute_code 设计与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
91 个 MCP 工具中最关键的一个:Funplay Unity MCP 的 execute_code 设计与实现

Funplay Unity MCP 是一个运行在 Unity Editor 内部的 HTTP MCP server,外部 AI 客户端(Claude Code、Cursor、Codex、VS Code 等)通过它驱动编辑器与 PlayMode。截至 v0.3.0,仓库一共注册了 91 个工具,按[ToolProvider]标注分布在 20 个模块下。

在所有这些工具中,使用频率最高、对客户端工作流影响最深的是execute_code——一个允许 AI 客户端提交 C# 代码片段、由 Editor 在内存中编译并立即执行的工具。本文记录它的设计动因、实现要点以及在工具集合中扮演的角色。

1. 系统位置

整体的请求链路如下:

Unity Editor 主线程工具方法Editor/Tools/BuiltinsMCPExecutionBridgeMCPRequestHandlerHttpMCPTransport外部 AI 客户端Unity Editor 主线程工具方法Editor/Tools/BuiltinsMCPExecutionBridgeMCPRequestHandlerHttpMCPTransport外部 AI 客户端HTTP JSON-RPC 2.0tools/call解析参数调度到主线程执行结构化 JSON 响应

execute_codeTools/Builtins/ScriptExecutionFunctions.cs注册的众多工具方法之一,但它的输入不是预定义参数,而是任意 C# 源代码。

2. 为什么需要一个"通用工具"

工具系统最自然的演进路径是"专用工具优先"——为每个高频操作单独定义一个 RPC:create_primitiveset_transformadd_componentset_material_shader。这条路线的优点是参数明确、单元测试容易、文档化彻底。

但一旦面对组合性需求,专用工具的代价会迅速放大。考虑这样一个任务:

找到场景中所有名字以Enemy_开头的 GameObject,将它们的BoxCollider改为 trigger,给每个对象添加一个名为HitBox的子物体,最后把整批对象保存为一个 prefab。

这一句需求拆解为专用工具调用至少需要 8 次 round-trip,且每次调用都要在 AI 客户端与 Editor 之间传递 instanceId 等中间状态。任意一步失败都需要回滚或重新拼装。

专用工具路线

find_game_objects

get_component

set_property

create_game_object

set_parent

create_prefab_asset

...

execute_code 路线

一次提交完整逻辑

一次结构化返回

更进一步,有些操作根本无法预先定义工具——例如调用项目内自定义ScriptableObject的某个静态方法,或访问第三方插件暴露的 Editor API。任何专用工具集合都不可能覆盖这些场景。

execute_code的设计目标,就是为这一类"在工具表中找不到对应项"的需求提供统一出口。

3. 执行模型

execute_code不是把代码写到.cs文件再触发AssetDatabase.Refresh(),而是采用 CodeDom 在内存中编译后通过反射调用:

有 IFunplayCommand 实现

仅有 static Run 方法

接收 C# 源代码

包装:添加 using + namespace

AssetDatabase.Refresh
等待 isCompiling 与 isUpdating 结束

CodeDom 编译为内存 Assembly

发现入口类型

实例化 + 注入 ExecutionContext

反射调用 Run

收集 Created/Modified/Destroyed instanceId

结构化 JSON 返回

此模型相对文件落盘方案有三点关键差异:

  • 不触发 domain reload:编译产物是临时 in-memory assembly,不会引发脚本域重启,编辑器不会出现 1–3 秒的卡顿。
  • 失败隔离:代码错误仅影响当次调用,不会让整个项目陷入编译失败状态。其他工具继续可用。
  • 执行前主动同步EditorReadyHelper.RefreshAndWaitForReady()在编译前自动 refresh asset 并等待 Unity 完成增量编译,调用方无需额外request_recompile

4. IFunplayCommand:统一的执行入口

为了让 AI 生成的代码能稳定参与 Undo 堆栈与结构化返回,定义了IFunplayCommand接口:

usingUnityEngine;usingUnityEditor;usingFunplay.Editor.Tools.Scripting;publicclassCommandScript:IFunplayCommand{publicvoidExecute(ExecutionContextctx){vargo=GameObject.CreatePrimitive(PrimitiveType.Cube);ctx.RegisterObjectCreation(go);ctx.Log("Created {0} (id={1})",go.name,go.GetInstanceID());ctx.ReturnValue=new{instanceId=go.GetInstanceID()};}}

ExecutionContext提供三类基础能力:

API作用
RegisterObjectCreation(go)等价Undo.RegisterCreatedObjectUndo,确保用户可 Ctrl+Z 撤销
RegisterObjectModification(obj)等价Undo.RecordObject,在修改前调用
Log(format, args)/LogWarning/LogError写入工具响应中的messages字段,不污染 Unity Console

为了向后兼容旧脚本,框架保留了对public static string Run()入口的支持——若编译产物中未找到IFunplayCommand实现,则反射调用Run方法。新代码建议一律使用IFunplayCommand

5. 实现细节:可见性与反射

CodeDom 编译生成的临时 Assembly 与 Funplay 包本身不在同一个程序集。Unity 包的常规做法是将内部类型标记为internal,避免外部用户依赖私有 API。这意味着如果将所有 helper 都设为internal,AI 提交的代码将无法引用ObjectsHelperTypeResolver等查询/序列化工具。

实际表现为运行期编译错误:

'Funplay.Editor.Tools.Helpers.ObjectsHelper' is inaccessible due to its protection level

解决方案是建立明确的暴露边界:

类型可见性理由
IFunplayCommand/ExecutionContextpublic脚本入口契约
ObjectsHelper/TypeResolverpublic高频查询能力
ComponentSerializer/GameObjectSerializerpublic结构化读写
Response/EditorReadyHelperinternal框架内部,脚本无需直接调用

这样既保证了 AI 脚本的可表达能力,又控制了向外暴露的 API 表面。

6. 何时优先使用专用工具

execute_code并非取代专用工具,而是作为其互补存在。下列场景仍应优先使用专用工具:

  • 状态读取get_selectionget_prefab_stageget_tagsget_layersget_build_settings直接返回结构化 JSON,比脚本中手动序列化更稳。
  • 菜单项执行execute_menu_item接受单字符串参数即可触发任意菜单项,比包裹IFunplayCommand调用EditorApplication.ExecuteMenuItem更紧凑。
  • 组件字段写入set_component_property/set_component_properties基于SerializedPropertyAPI,能正确处理[SerializeField] private、Object 引用赋值、prefab 上下文,远比脚本中手写反射稳定。

判断标准为:当前任务能否由单次工具调用完成?若可以,使用专用工具;若需要在调用之间维护状态或拼接逻辑,使用execute_code

7. 完整示例:单次调用闭环

回到第 2 节中的需求,使用execute_code的实现如下:

usingSystem.Linq;usingUnityEngine;usingUnityEditor;usingFunplay.Editor.Tools.Scripting;publicclassCommandScript:IFunplayCommand{publicvoidExecute(ExecutionContextctx){varenemies=Object.FindObjectsByType<GameObject>(FindObjectsSortMode.None).Where(g=>g.name.StartsWith("Enemy_")).ToList();foreach(vareinenemies){varcol=e.GetComponent<BoxCollider>();if(col!=null){ctx.RegisterObjectModification(col);col.isTrigger=true;}varhit=newGameObject("HitBox");hit.transform.SetParent(e.transform,false);ctx.RegisterObjectCreation(hit);}varpath="Assets/Generated/Enemies.prefab";System.IO.Directory.CreateDirectory("Assets/Generated");varroot=newGameObject("Enemies");ctx.RegisterObjectCreation(root);foreach(vareinenemies)e.transform.SetParent(root.transform,true);PrefabUtility.SaveAsPrefabAsset(root,path);ctx.Log("Processed {0} enemies → {1}",enemies.Count,path);ctx.ReturnValue=new{count=enemies.Count,prefab=path};}}

整个任务在一次 RPC 中完成:一个 Undo group、一次结构化返回({ count, prefab })、一次ctx.Log记录。客户端无需在多次工具调用之间传递 instanceId,也不会因中间任一步失败而留下半成品。

8. 在工具集中的定位

v0.3.0 的core默认 profile 共暴露 29 个工具,其中 20 个为只读 / 状态读取(get_*list_*),9 个为关键写操作。execute_code是这 9 个写操作中唯一支持任意逻辑的入口。其余写工具(set_component_propertyadd_componentexecute_menu_item等)覆盖单步高频操作,组合性需求由execute_code承接。

这种结构在工具数量与表达能力之间取得了平衡:客户端默认可见的工具数量保持精简(29 个),但实际可表达的操作空间仍然覆盖整个 Unity Editor API。

9. 总结

为 AI 客户端设计工具与为人类设计 SDK 是两种不同的范式。人类开发者偏好职责清晰、参数明确的 API,而 AI 客户端在面对未预期的组合需求时,需要一个可以现场拼接逻辑的"逃生出口"。

execute_code正是为此而设计:

  • 内存编译,不写文件,不触发 domain reload
  • IFunplayCommand统一入口,自动接入 Undo 与结构化返回
  • 与专用工具互补,由调用方依据任务粒度自行选择

仓库地址:FunplayAI/funplay-unity-mcp,MIT 协议。如果你正在为 Unity 项目接入 AI 工作流,欢迎提交 issue 或讨论。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/9 14:22:38

AI驱动野生动物保护:计算机视觉与机器学习实战指南

1. 项目概述&#xff1a;当AI成为森林的“眼睛”几年前&#xff0c;我在一个自然保护区的项目现场&#xff0c;和巡护员们一起翻看红外相机拍下的海量照片。他们需要从成千上万张照片里&#xff0c;找出可能出现的珍稀物种&#xff0c;或者识别出可疑的人类活动痕迹。那是一个极…

作者头像 李华
网站建设 2026/5/9 14:19:36

CANN驱动PCIe错误查询API

dcmi_get_device_pcie_error_cnt 【免费下载链接】driver 本项目是CANN提供的驱动模块&#xff0c;实现基础驱动和资源管理及调度等功能&#xff0c;使能昇腾芯片。 项目地址: https://gitcode.com/cann/driver 函数原型 int dcmi_get_device_pcie_error_cnt(int card_…

作者头像 李华
网站建设 2026/5/9 14:17:32

CANN运行时简单模型实例示例

0_simple_model 【免费下载链接】runtime 本项目提供CANN运行时组件和维测功能组件。 项目地址: https://gitcode.com/cann/runtime 描述 本样例展示了如何捕获Stream中的任务并创建一个模型实例&#xff0c;然后执行该模型实例得到结果。 产品支持情况 本样例在以下…

作者头像 李华
网站建设 2026/5/9 14:14:31

CANN竞赛Cumsum算子测试报告

【免费下载链接】cann-competitions 本仓库用于 CANN 开源社区各类竞赛、开源课题、社区任务等课题发布、开发者作品提交和展示。 项目地址: https://gitcode.com/cann/cann-competitions 元信息&#xff08;请如实填写&#xff0c;此区块将由组委会脚本自动解析&#xf…

作者头像 李华
网站建设 2026/5/9 14:13:30

CANN FlashAttention反向梯度计算V4

aclnnFlashAttentionScoreGradV4 【免费下载链接】ops-transformer 本项目是CANN提供的transformer类大模型算子库&#xff0c;实现网络在NPU上加速计算。 项目地址: https://gitcode.com/cann/ops-transformer 产品支持情况 产品是否支持Ascend 950PR/Ascend 950DT√A…

作者头像 李华
网站建设 2026/5/9 14:11:50

FPGA-MPSoC边缘AI加速实战:从模型量化到硬件部署全解析

1. 项目概述&#xff1a;为什么要在边缘用FPGA-MPSoC做AI加速&#xff1f;这几年&#xff0c;但凡跟AI沾边的项目&#xff0c;无论是自动驾驶里识别一个突然窜出来的行人&#xff0c;还是工厂质检摄像头判断一个零件的瑕疵&#xff0c;大家挂在嘴边的都是“实时性”和“低功耗”…

作者头像 李华