给 AI 客户端设计工具的 5 个反直觉原则:来自 91 个 MCP 工具的经验
2026/5/12 21:06:13 网站建设 项目流程

为人类设计的 API 与为 AI agent 设计的工具,是两套不同的设计范式。前者优化的是"易读、易记、有 IDE 提示";后者优化的是"易选、易容错、给出来的信息够 LLM 继续推理"。这两套原则在很多地方甚至相反。

下面 5 条原则来自做 Funplay Unity MCP 半年的具体踩坑——一个跑在 Unity Editor 内部、把编辑器与 PlayMode 暴露为 91 个 MCP 工具的 server。每条原则在设计之初都被某种"工程师本能"反对过,最终在 AI 客户端真实使用反馈里才确立。

原则 1:工具数量不是越多越好

工程师的本能是"职责单一、覆盖完整"——每个高频操作单独定义一个工具,文档化彻底、单元测试好写。但放到 LLM 视角下,工具数量上升对选对率有显著负面影响。

工具数量

LLM 选错率

LLM 选漏率

token 开销

新用户上手门槛

实测在 90+ 工具规模下,主流 LLM 在tools/list阶段就需要消耗大量 token 加载工具元数据,且 tool selection 容易把get_component选成list_components、把set_property选成set_properties

做法:把工具集分成两层。Funplay Unity MCP 默认暴露coreprofile 29 个高频工具,fullprofile 91 个全集只在需要时切换。Core 集合的工具数控制在"一个 LLM 上下文窗口能完整审视"的量级(30 以内是经验值)。

需要注意:这条原则不等于"少做工具"。完整的 91 个工具该写还得写——只是 LLM 默认看到的应该是经过精选的子集。

原则 2:永远留一个万能逃生口

如果做到了原则 1 的精简,立即面临的问题是:精选子集必然漏掉某些低频但合理的场景。例如"调用项目里某个自定义 ScriptableObject 的静态方法"这种需求,无法预先定义专用工具。

工程师本能的解决方案是"再加几个通用辅助工具",但这条路无限延伸。更好的做法是显式承认有漏洞,并提供一个能在工具集之外执行任意逻辑的"逃生口"

Funplay Unity MCP 里这个逃生口是execute_code——AI 客户端提交 C# 代码片段,Editor 内存编译执行:

publicclassCommandScript:IFunplayCommand{publicvoidExecute(ExecutionContextctx){// 任意 Unity Editor API 调用ctx.ReturnValue=...;}}

逃生口的存在使得"工具集精简"成为可承受的设计选择——客户端遇到工具集外的需求时不会卡死,而是降级到execute_code拼装逻辑。

适用范围:任何提供宿主环境完整访问权的 MCP server(Unity Editor、浏览器、IDE、数据库 console)都应考虑提供逃生口。逃生口的成本是"AI 写错代码可能导致副作用",收益是"工具集可以保持精简,且能覆盖未预期的场景"。

原则 3:结构化返回胜过字符串返回

最初的 MCP 工具实现里,很多返回都是裸字符串:“已创建对象 Cube”、“操作成功”。从工程师视角看这干净简洁,从 AI 客户端视角看这是灾难——下一个调用想引用刚创建的对象,得自己从字符串里 parse 出对象名,且没有任何 ID。

字符串返回 已创建对象 Cube

AI 想引用

只能按名称 find_by_name

重名时拿到错对象

操作失败 或副作用

结构化返回 success/data/instanceId

AI 想引用

直接 by_id

精准且幂等

做法:所有工具返回统一为以下 JSON 模式:

{"success":true,"message":"Created cube","data":{"instanceId":13520,"name":"Cube","path":"/Cube"}}

失败时换为:

{"success":false,"code":"OBJECT_NOT_FOUND","error":"GameObject 'Player' not found in active scene","data":{"searchedNames":["Player"]}}

code字段是机器可读的错误类型常量,AI 客户端基于code分支判断处理逻辑,比 free-formerror字符串可靠得多。

原则 4:用稳定 ID 形成调用链

紧接着原则 3。返回了instanceId之后,所有"按对象操作"的工具都应该接受find_method=by_id参数:

{"tool":"get_component_properties","arguments":{"find_method":"by_id","instance_id":13520,"component_type":"BoxCollider"}}

这种"返回 ID → 后续 by_id 调用"的设计让 AI 不需要在多个工具调用之间反复解析名字。一次调用产生 ID,后续 N 次调用直接复用——既快,又避免了重名/路径变动带来的歧义。

set_component_propertyadd_componentcreate_primitiveAI 客户端set_component_propertyadd_componentcreate_primitiveAI 客户端create Cube{ instanceId: 13520 }by_id=13520, type=Rigidbody{ instanceId: 13521 }by_id=13521, prop=mass, value=5{ success: true }

每一步都用前一步返回的instanceId,没有任何字符串解析。Funplay Unity MCP 的所有写入类工具都接受find_method=by_id,是这个原则的统一落地。

Anti-pattern:返回 ID 但不让其他工具消费 ID。例如某些 server 创建对象后只返回名字,需要再list_objects拿 ID——这种设计强迫 AI 多走一轮,且把"ID 是否稳定"的问题留给客户端。

原则 5:read-only 与 write 操作显式划分

成熟的 MCP server 会区分两类工具:

  • read-only— 只读,不改变宿主状态(get_scene_infolist_components
  • write— 修改宿主状态(set_component_propertyexecute_code

工程师本能可能觉得"调用方自己看名字判断就好"——但 AI 客户端的"看名字判断"不可靠。同时,宿主侧的安全/审计/权限策略需要在工具元数据层就能区分类型,而不是逐工具特判。

Funplay Unity MCP 用两个 attribute 显式标注:

[ToolProvider("Scene")]internalstaticclassSceneFunctions{[ReadOnlyTool][Description("Get info about the active scene")]publicstaticstringGetSceneInfo(){/* ... */}[SceneEditingTool][Description("Create a primitive GameObject in the active scene")]publicstaticstringCreatePrimitive(/* ... */){/* ... */}}

[ReadOnlyTool][SceneEditingTool]这两个 attribute 不参与运行时逻辑,纯粹是元数据。但它们让以下三件事变得可行:

用途实现
Profile 过滤coreprofile 可以只暴露 read-only 工具给某些 AI 客户端
审计日志write 工具的调用自动记入 Recent Activity,read 不记
工具描述自动在生成的 description 里加[Read-only]/[Modifies scene]标签

把"操作的副作用面"建模成工具的一等元数据,比依赖工具名约定可靠得多。

MCP 协议层面的补充:MCP 标准里的annotations.readOnlyHint/destructiveHint字段提供了协议级表达。如果你的 server 暴露给多个 AI 客户端使用,应该填上这些 annotation,让客户端 UI 能据此显示警告。

原则之外:一条经验性观察

5 条原则的共通底层是一句话:为 AI 设计工具时,所有"看起来给 AI 增加负担"的设计都值得重新审视

  • 工具描述 verbose 不是坏事——AI 不会"嫌长",它要的是消歧
  • 返回结构带冗余字段不是坏事——AI 会忽略它不需要的字段
  • 命名长一点(find_game_objects_by_component_type)不是坏事——AI 不会嫌打字累

这些"啰嗦"的设计选择在人类视角下显得 over-engineered,但对 LLM 工具选择 / 参数填写 / 后续推理都有显著正向。Funplay Unity MCP 的 91 个工具几乎全部走这条路,名字偏长、描述偏多、返回字段偏全——而非偏简洁。

写在最后

这些原则没有任何一条是预先想出来的,全都来自 AI 客户端的实际反馈:

  • 原则 1 来自客户端在tools/list阶段消耗过多 token 的报告
  • 原则 2 来自频繁出现"AI 想做某事但找不到对应工具"的会话
  • 原则 3 来自客户端在多步操作中"上一步返回的对象名找不到了"的现象
  • 原则 4 来自 AI 客户端反复list_objects + match by name浪费 round-trip
  • 原则 5 来自需要给某些场景做 dry-run / approval gate 时无法判别工具副作用

把"AI 怎么用"当成第一公民去设计工具,剩下的就是把这五条作为 checklist 不断回头审视。

Funplay Unity MCP 的实现在 FunplayAI/funplay-unity-mcp,MIT 协议。所有 91 个工具的 attribute 注册、结构化返回、instanceId 链路、core / full profile 划分都可以直接参考。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询