Tool Calling 和 MCP 到底是什么关系:从 EngineRunner 看 Agent 的工具边界
从游戏变更验证 Agent 的 EngineRunner 与确定性工具出发,解释 Tool Calling 解决什么问题、MCP 又标准化了什么,以及内部函数什么时候值得进一步封装成 MCP Tool。
上一篇写 Agent Loop 时,我把一个 Agent 简化成了:
State
↓
Decision
↓
Action
↓
Tool / Environment
↓
Observation
↓
新的 State
这里马上会出现一个很自然的问题:
Tool 到底是什么?
如果模型输出:
repair_action = REDUCE_BULLETS_PER_WAVE
然后程序调用:
apply_repair(...)
这算 Tool Calling 吗?
如果 Workflow 调用:
runner.automated_run(...)
启动 Unity 或 UE5,这又算不算 Tool?
那 MCP 又是什么?
是不是:
Agent 想调用工具,就一定要用 MCP?
Game Change Verification Agent 当前没有实现 MCP Server,也没有通过 MCP 调用 Unity / UE5。
项目内部使用的是普通 Python 接口和受控 Workflow:
Workflow
↓
EngineRunner
↓
UnityEngineRunner / UnrealEngineRunner
但恰恰因为先实现了这样一套内部 Tool Abstraction,我才更容易理解:
Tool Calling 和 MCP 其实解决的是两个不同层次的问题。
Tool Calling 更接近:
模型怎样表达“我希望系统执行某项能力”。
MCP 更接近:
一个 AI 应用怎样以标准方式发现、描述和调用外部提供的工具与上下文。
这篇文章从项目里的 EngineRunner 和 Repair Tool 出发,把这两个概念拆开。
项目地址:theHerta27/game-change-verification-agent
先从最普通的函数调用开始
项目中有一个确定性修复函数:
repaired, repair_evidence = apply_repair(
candidate,
action,
evaluation,
)
例如 Quality Review Agent 判断:
peak_alive_bullets 超过目标
然后输出:
REDUCE_BULLETS_PER_WAVE
Workflow 验证这个 Action 合法以后,再调用:
apply_repair(...)
从 Python 语言本身看,这只是一次普通函数调用:
Python Code
↓
Python Function
模型根本没有直接执行函数。
真正发生的是:
LLM
↓
输出结构化 Action
↓
Workflow 解析
↓
Policy Gate
↓
Python 调用函数
所以严格来说:
模型提出了工具意图,真正调用工具的是应用程序。
这也是我觉得理解 Tool Calling 时很重要的一点。
LLM 本身并不会突然拥有:
CPU
文件系统
Unity
数据库
浏览器
这些能力。
它只能产生 Token。
真正的外部动作永远需要宿主程序执行。
Tool Calling 本质上是在建立一个“动作协议”
假设一个模型只会返回自然语言:
我认为应该降低第二阶段的单波子弹数量。
人当然能看懂。
但程序接下来很难稳定判断:
到底调用哪个函数?
参数是什么?
是不是在请求执行?
所以更适合程序消费的形式是:
{
"repair_action": "REDUCE_BULLETS_PER_WAVE"
}
或者更典型的 Tool Calling 形式:
{
"name": "reduce_bullets_per_wave",
"arguments": {
"phase_id": "phase_2"
}
}
它把:
自然语言意图
转成了:
工具名称
+
结构化参数
于是应用可以做:
模型输出
↓
解析 tool name
↓
验证 arguments
↓
查找真正实现
↓
执行
↓
把结果返回给模型
所以 Tool Calling 的核心并不是:
模型真的执行了函数。
而是:
模型和程序约定了一种结构化方式,用来表达下一步希望调用哪项能力。
项目的 Quality Review Agent 为什么还不算“自由 Tool Calling”
Game Change Agent 当前的 Quality Review 输出是:
class QualityReviewOutput(StrictAgentModel):
decision: Literal[
"accept",
"repair",
"human_review",
]
repair_action: RepairAction | None
reason: str
evidence_refs: list[str]
repair_action 又被限制为:
REDUCE_BULLETS_PER_WAVE
INCREASE_WAVE_INTERVAL
REDUCE_BULLET_SPEED
REDUCE_BULLET_LIFETIME
REDUCE_PATTERN_LAYERS
PRESERVE_VISUAL_STYLE
REQUEST_HUMAN
STOP
所以模型并没有拿到一个通用:
tools = [...]
然后自由选择:
run_unity
edit_file
run_shell
delete_file
...
它只能在非常小的 Action Space 中选择一个意图。
后面还有:
Agent Output
↓
Policy Gate
↓
apply_repair()
因此它更接近:
受限 Action Selection。
不过从 Agent 系统设计上看,它已经具有 Tool Calling 的核心结构:
模型决定动作
↓
程序验证动作
↓
确定性代码执行
↓
执行结果重新进入 Workflow
区别主要在于:
工具集合是不是以一个通用 Tool Schema 暴露给模型。
Tool Calling 不要求 MCP
这一点特别容易混淆。
假设我定义:
TOOLS = {
"run_engine": run_engine,
"apply_repair": apply_repair,
}
然后告诉模型:
你可以使用:
run_engine(config)
apply_repair(action)
模型返回:
{
"name": "run_engine",
"arguments": {
"variant": "candidate"
}
}
程序执行:
TOOLS[name](**arguments)
这已经可以形成完整 Tool Calling。
整个过程完全可以发生在:
一个 Python 进程
里面。
不需要:
MCP Server
MCP Client
网络协议
所以:
Tool Calling 是 Agent 能力的一部分,MCP 不是实现 Tool Calling 的必要条件。
这也是 Game Change Agent 当前的状态:
Agent Workflow
+
内部 Tool Abstraction
已经可以工作。
那 MCP 到底多解决了什么
问题出现在工具越来越多,而且使用它们的不再只有一个项目时。
假设现在:
Game Change Agent
需要调用 Unity。
于是项目内部写:
UnityEngineRunner
以后又有:
另一个测试 Agent
也想运行 Unity。
可能再写一套:
UnityTool
接着:
IDE Agent
也需要游戏引擎能力。
再后来:
Claude Code
VS Code
另一个内部 AI 平台
都想调用:
运行 Candidate
读取 Telemetry
获取截图
如果每一个 AI 应用都和 Unity Runner 单独集成:
Agent A ─────┐
Agent B ─────┼→ 各自写一套 Unity Adapter
IDE ─────┤
Agent C ─────┘
集成关系会越来越多。
这时真正想解决的问题变成:
能不能让“提供工具的一方”用一个标准接口描述自己的能力,而不同 AI 应用都按同一种协议调用?
这就是 MCP 更接近的问题。
可以把关系理解成:
┌→ Agent A
Unity Tool ├→ Agent B
MCP Server ←───┼→ IDE
└→ 其他 AI Host
Tool 不再只是一段某个项目内部知道怎么调用的 Python 函数。
而变成:
一个可以被不同 MCP Client 发现和调用的标准化能力。
MCP 并不是“AI 版 HTTP”
MCP 底层当然涉及通信。
但如果只把它理解成:
一种接口协议
还是有点过于宽泛。
普通 HTTP API 可能定义:
POST /run-unity
GET /telemetry/123
客户端需要提前知道:
接口路径
HTTP Method
参数
响应结构
认证方式
而 MCP 面向的是 AI Tool / Context 集成。
例如 Server 可以向 Client 暴露:
有哪些 Tools
每个 Tool 叫什么
描述是什么
输入 Schema 是什么
输出结构是什么
于是 AI 应用可以先发现能力,再把适合的 Tool Definition 提供给模型。
可以粗略理解成:
普通 API:
“你提前知道怎么调用我。”
MCP:
“我可以用标准格式告诉 AI 应用我有哪些能力,以及怎样调用。”
当然,真正的 MCP 规范比这句话复杂得多。
但对理解 Agent Tool 最重要的区别已经在这里。
如果把 EngineRunner 做成 MCP Tool,会长什么样
Game Change Agent 当前的统一接口是:
class EngineRunner(ABC):
@abstractmethod
def capabilities(self):
...
@abstractmethod
def validate_environment(self):
...
@abstractmethod
def build(self):
...
@abstractmethod
def automated_run(
self,
*,
contract,
run_dir,
seed,
run_id,
variant,
capture_times=(),
):
...
@abstractmethod
def manual_play(...):
...
@abstractmethod
def discover_artifacts(...):
...
如果未来真的需要 MCP,可以考虑只把其中某些安全能力暴露成 Tool。
例如:
run_bullet_hell_verification
输入:
{
"workflow_id": "bullet_xxx",
"variant": "candidate"
}
输出:
{
"engine": "unreal",
"status": "completed",
"telemetry": {
"peak_alive_bullets": 198,
"player_hits": 2,
"low_percentile_fps": 58.65
}
}
或者:
get_engine_capabilities
返回:
{
"engine": "unreal",
"patterns": [
"ring",
"aimed_fan",
"spiral",
"petal"
],
"automated_run": true,
"manual_play": true
}
这时候外部 Agent 不需要:
import workflow.engines.unreal
也不需要知道:
UE5.exe 在哪个路径
命令行参数怎么拼
Telemetry 文件在哪里
只需要知道:
这个 MCP Server 提供一个可以运行受控验证的 Tool。
这就是协议边界带来的解耦。
但绝对不能直接暴露 subprocess.run
这里也是 Tool 设计最重要的安全边界之一。
Unity Runner 内部最终确实执行:
subprocess.run(command, ...)
UE5 Runner 也是。
最危险的设计可能是直接给模型一个:
execute_command(command: string)
然后模型传:
任意 EXE
任意参数
任意路径
这种 Tool 的问题不是:
Tool Calling 技术有没有实现
而是:
Tool 能力边界几乎没有限制。
Game Change Agent 当前反而刻意不这么做。
UE5 Runner 只使用预注册:
BulletHellUE.exe
代码中的 executable 路径由 Runner 自己固定:
self.executable = (
repository_root
/ "game-unreal"
/ "BulletHellUE"
/ "Builds"
/ "Windows"
/ "BulletHellUE.exe"
)
外部调用者不能传:
executable_path
让 Runner 启动任意程序。
所以 Tool 接口不是:
run_process(path, args)
而是:
automated_run(contract, seed, variant)
差别非常大。
好的 Tool 应该暴露“能力”,不是暴露“底层权限”
如果我给 Agent:
shell(command)
那么:
读取配置
启动游戏
删除文件
安装软件
执行脚本
理论上全都可以通过一个 Tool 完成。
它看起来非常通用。
但授权边界几乎无法描述。
反过来:
run_candidate_verification(...)
能力很窄。
它只能做:
运行一个已经存在并通过验证的 Candidate。
这让我觉得 Agent Tool 设计里有一个很实用的原则:
尽量暴露业务能力,而不是暴露底层原语。
类似:
不优先给:
execute_sql(sql)
而给:
get_booking(id)
create_booking(...)
不优先给:
run_shell(command)
而给:
run_verified_candidate(...)
模型得到的权限更容易理解,也更容易校验。
EngineRunner 其实已经承担了一部分“工具契约”的职责
为什么我觉得 EngineRunner 很适合拿来理解 MCP?
因为它本身已经在做:
统一能力定义
例如 Workflow 不关心:
Unity CLI
和:
UE5 CLI
到底怎么写。
它只依赖:
runner.automated_run(...)
Unity 实现:
EngineRunner
↓
UnityEngineRunner
↓
BulletHellDemo.exe
UE5 实现:
EngineRunner
↓
UnrealEngineRunner
↓
BulletHellUE.exe
Workflow 看到的是统一 Contract:
contract
seed
run_id
variant
capture_times
得到的也是统一:
EngineRunResult
包含:
engine_name
telemetry
normalized_evidence
artifacts
exit_code
所以 EngineRunner 已经在回答:
不同底层系统怎样向上提供同一种能力。
MCP 则可以进一步回答:
怎样让这个能力不仅能被当前 Python Workflow 使用,还能以标准协议被其他 AI 应用使用。
Internal Interface 和 MCP Interface 的区别
可以把它们放在一起看。
当前项目
BulletHellWorkflowService
↓
Python Method Call
↓
EngineRunner
↓
Unity / UE5
调用双方:
在同一个代码库
调用方式:
普通 Python
类型契约:
Python 类型 / dict / dataclass
如果改成 MCP
可能变成:
AI Application
↓
MCP Client
↓
MCP Protocol
↓
Engine MCP Server
↓
EngineRunner
↓
Unity / UE5
调用双方可以:
不在同一个进程
甚至不在同一个项目
契约需要转成:
协议可描述的 Schema
这就是为什么:
已经有一个好的内部抽象,并不自动意味着应该上 MCP。
只有当:
跨应用复用
独立部署
动态发现
统一生态集成
真的有价值时,标准协议才开始体现优势。
MCP Tool 和普通 Function Tool 最直观的区别
假设模型 Provider 支持 Function Calling。
应用代码里直接写:
tools = [
{
"name": "run_candidate",
"description": "...",
"parameters": {...},
}
]
模型决定:
run_candidate
然后当前应用自己实现:
def run_candidate(...):
...
这种情况下:
Tool Definition
Tool Implementation
Agent Application
通常绑定在同一个项目里。
如果换成 MCP:
Tool Implementation
可以由独立:
MCP Server
提供。
Agent Application 通过 MCP Client 获取:
Tool Catalog
并调用。
所以可以粗略概括:
Function / Tool Calling
→ 模型怎样表达“我要调用工具”
MCP
→ 应用怎样标准化地获取和调用外部工具
前者在:
Model ↔ Application
边界。
后者主要在:
Application ↔ Tool Provider
边界。
这两个边界很容易被一句:
“都是调工具”
混在一起。
一个完整调用链可能同时包含两者
假设未来 Game Change Agent 真接入 MCP。
模型看到:
run_bullet_hell_verification
然后生成 Tool Call:
{
"name": "run_bullet_hell_verification",
"arguments": {
"workflow_id": "bullet_123",
"variant": "candidate"
}
}
这是:
Tool Calling。
Agent Host 收到以后发现:
这个 Tool 来自 Engine MCP Server
于是通过 MCP Client 发送:
tools/call
MCP Server 收到以后调用:
EngineRunner.automated_run(...)
最后结果一路返回。
完整链路就是:
LLM
↓
Tool Call
↓
Agent Host
↓
MCP Client
↓
MCP Server
↓
EngineRunner
↓
UE5
↓
Telemetry
↓
MCP Result
↓
Agent Host
↓
LLM
所以 Tool Calling 和 MCP 完全可以同时存在。
并不是二选一。
MCP 也不只有 Tools
如果只把 MCP 记成:
给 Agent 调函数的协议
也不完整。
MCP Server 除了 Tool,还可以暴露:
Resources
Prompts
这三个概念可以先这样理解。
Tools
更接近:
可以执行的能力。
例如:
run_candidate
generate_visual_comparison
get_engine_capabilities
通常意味着调用以后可能:
执行计算
访问系统
改变外部状态
Resources
更接近:
可以读取的上下文或数据。
如果放到这个项目里,理论上可能包括:
workflow manifest
candidate config
telemetry report
comparison report
它们本身不是:
“执行一次 Unity”
而是:
给 AI 应用读取现有 Evidence。
Prompts
可以提供:
服务端定义好的可复用 Prompt 模板。
例如一个 Engine 服务理论上可以提供:
review_bullet_hell_evidence
Prompt 模板。
不过当前 Game Change Agent 的 Prompt 仍然直接保存在项目自己的:
prompts/
目录中,并没有通过 MCP 暴露。
这三者的存在也说明:
MCP 想标准化的不只是“函数调用”,而是 AI 应用与外部能力和上下文之间的接口。
Telemetry 更像 Resource 还是 Tool Result
这个问题其实没有唯一答案。
假设:
run_candidate
执行以后直接返回:
{
"peak_alive_bullets": 198,
"player_hits": 2
}
那么 Telemetry 就是:
Tool Result
但如果 Workflow 已经执行完成,以后 Agent 想单独读取:
workflow://bullet_123/candidate/telemetry
那么它又很适合作为:
Resource
所以 Tool 和 Resource 不是:
某种数据天生属于哪一类。
更重要的是访问语义:
Tool
→ “帮我做一件事”
Resource
→ “把已经存在的信息给我”
这和 HTTP 里:
POST
与:
GET
有一点类似的直觉,但不能完全等同。
MCP 不会自动让 Tool 变安全
这一点特别重要。
假设把:
execute_shell(command)
注册成 MCP Tool。
它不会因为:
现在是 MCP
就突然变安全。
MCP 解决:
标准化描述
发现
调用
结果传递
但真正的:
权限控制
参数校验
路径限制
人工确认
操作预算
审计
仍然要由 Tool Provider 和 Host 设计。
所以如果未来把 EngineRunner 暴露成 MCP,我仍然会保留项目当前的限制。
例如 UE5 Runner:
def _require_variant(variant):
if variant != "baseline" \
and not variant.startswith("candidate"):
raise ValueError(...)
只允许:
baseline
candidate*
而不是任意 Variant。
运行路径还会:
if not resolved.is_relative_to(
self.runtime_root
):
raise ValueError(...)
也就是说运行 Artifact 必须位于:
runtime-artifacts
下面。
这种校验即使换成 MCP:
一条也不应该删。
为什么 Tool Schema 不能成为唯一安全边界
假设 MCP Tool Schema 写:
{
"variant": {
"enum": [
"baseline",
"candidate"
]
}
}
它当然很好。
模型看到以后也更容易产生合法参数。
但真正的 Server 仍然应该再次检查:
_require_variant(variant)
为什么?
因为 Server 不能假设调用它的永远是:
那个表现良好的模型
MCP Tool 以后可能被:
其他 Client
测试程序
人工脚本
错误实现
调用。
所以:
Schema
→ 描述合法输入
Server-side validation
→ 真正强制合法输入
和 Web API 一样。
不能因为前端已经校验:
年龄必须 > 0
后端就不再检查。
discover_artifacts() 为什么也是一个安全边界
EngineRunner 还有:
def discover_artifacts(
self,
run_dir: Path,
) -> dict[str, str]:
UE5 Runner 并不是:
把 run_dir 里所有文件
全部返回
而是明确白名单:
config.json
telemetry.json
player.log
capture_manifest.json
capture_10s.png
capture_20s.png
capture_30s.png
对应代码:
allowed = (
"config.json",
"telemetry.json",
"player.log",
"capture_manifest.json",
"capture_10s.png",
"capture_20s.png",
"capture_30s.png",
)
这其实和 Tool 安全是同一种思想。
如果以后暴露:
list_engine_artifacts
不应该顺手给模型一个:
read_any_file(path)
而是:
只让它看到 Workflow 明确允许暴露的 Evidence。
Tool 权限之外,还有:
Data Boundary。
为什么执行结果还要再验证
调用 Unity / UE5 成功启动:
process exit
并不意味着 Tool 执行结果一定可信。
例如 UE5 Runner 会继续检查:
telemetry.json 是否存在
JSON 是否合法
config_hash 是否匹配
completed 是否为 true
runtime_error_count 是否为 0
截图是否齐全
capture manifest 是否对应当前配置
例如:
if telemetry.get(
"config_hash"
) != canonical_hash:
raise RuntimeError(...)
所以 Tool Execution 其实分两步:
调用外部程序
↓
验证 Evidence
只有两步都通过:
Tool Result
才会进入 Agent Workflow。
这让我觉得 Tool Result 也不能被理解成:
子进程返回了什么,就原样喂给模型。
真正可靠的工具应该先回答:
这个结果是不是属于我刚才执行的操作?
Config Hash 为什么很像一种 Evidence Binding
假设 Candidate A:
config hash = AAA
但 Engine 最后输出的 Telemetry 实际来自:
旧的 Candidate B
如果系统只看到:
telemetry.json 存在
可能错误地把旧证据归到新 Candidate 上。
所以 UE5 Runner 启动时把:
ConfigHash
传进去。
Telemetry 返回以后再次检查:
telemetry.config_hash
==
current candidate hash
这相当于把:
Action
和:
Observation
绑定起来。
在 Agent Tool 里这一点很重要。
因为模型下一步决策依赖 Observation。
如果 Observation 属于错误的 Action:
Agent 的后续推理即使完全正确
也建立在错误证据上
Tool Result 应该返回多少信息
另一个工程问题是:
Tool 是不是应该把所有原始数据都直接塞回 LLM?
例如 UE5 Telemetry 可以很长。
如果每次 Tool Call 都把:
完整日志
完整配置
完整 Telemetry
所有截图信息
全部放进模型 Context:
Token 很快就会膨胀
Game Change Agent 当前已经有一个很有用的分层:
raw telemetry
↓
normalize_engine_telemetry
↓
normalized evidence
↓
deterministic evaluation
↓
Quality Review Agent
也就是说模型不一定直接阅读所有底层原始输出。
它更需要:
和当前决策相关的 Evidence
如果以后做 MCP,同样可以把:
Tool structured result
和:
完整 Resource
分开。
Tool Call 先返回摘要和 Artifact 引用。
真正需要详细内容时,再单独读取对应 Resource。
这比每轮都把所有内容塞给模型更可控。
为什么不能把所有工具都一次性告诉模型
假设一个复杂工程 Agent 最后拥有:
100 个 Tools
其中包括:
运行引擎
查 Telemetry
改配置
跑测试
查 Git
读文件
写文件
部署
发消息
...
如果每一次模型调用都把全部 Tool Schema 放进去,会带来:
更长 Context
更高选择难度
更多错误调用可能
更复杂权限管理
所以:
Tool Discovery 和 Tool Selection 本身也会成为 Agent 架构问题。
MCP 提供标准化 Tool Catalog,并不意味着:
模型每次都应该看到所有 Tool
Host 仍然可以根据:
当前 Workflow State
当前用户权限
当前任务
选择只暴露一部分能力。
比如 Game Change Workflow 在:
awaiting_authorization
状态下,就不应该允许:
run_candidate
真正进入:
authorized
以后才允许 Engine Tool。
所以:
Tool Availability 本身也应该由 State 决定。
MCP 和 Agent Framework 也不是一回事
还有三个词很容易堆在一起:
LangGraph
Tool Calling
MCP
但它们解决的层次不同。
LangGraph 这类 Agent Framework
主要帮助表达:
State
Node
Edge
Checkpoint
Workflow
也就是:
Agent 流程怎样编排。
Tool Calling
主要解决:
模型怎样表达下一步希望执行哪个工具。
MCP
主要解决:
AI 应用怎样以统一协议连接和调用外部提供的 Tool / Resource / Prompt。
它们可以组合:
LangGraph
↓
某个 Agent Node
↓
LLM Tool Call
↓
MCP Client
↓
MCP Server
↓
真实 Tool
但:
没有 LangGraph
照样可以 Tool Calling。
没有 MCP
照样可以 Tool Calling。
用了 MCP
也不代表一定有 Agent Loop。
比如一个普通 AI 应用只是:
用户点击按钮
↓
调用一个 MCP Tool
↓
显示结果
完全可以没有自主 Agent。
那 Game Change Agent 现在为什么没有必要强行接 MCP
当前项目只有一个主要 Workflow。
EngineRunner 也和 Workflow 在同一个 Python 服务里。
调用关系:
BulletHellWorkflowService
↓
EngineRunner
已经非常直接。
如果为了简历关键词强行改成:
Workflow
↓
MCP Client
↓
本机 MCP Server
↓
EngineRunner
实际上增加了:
协议层
序列化
Server 生命周期
错误处理
额外测试
却没有获得明显的复用价值。
这属于典型的:
为了使用技术而使用技术
我觉得反而不好。
目前内部 Interface 已经足够解决:
Unity 和 UE5 的实现解耦。
等以后真的出现:
多个 Agent
多个 IDE
多个独立应用
都需要统一调用 Engine Capability,再把 EngineRunner 封装成 MCP Server,就更有实际意义。
什么时候我会考虑把 EngineRunner MCP 化
如果未来需求变成:
Game Change Verification Agent
↓
Coding Agent
↓
IDE Plugin
↓
另一个测试平台
都想使用:
Engine Capability
这时 MCP 的价值就会明显增加。
我可能把 Tool 设计成:
get_engine_capabilities
run_verified_baseline
run_verified_candidate
get_verification_status
generate_visual_comparison
而不会暴露:
execute_exe
run_powershell
read_any_path
同时把 Evidence 作为 Resources:
workflow://{id}/manifest
workflow://{id}/candidate
workflow://{id}/telemetry
workflow://{id}/comparison
这样:
Tool
→ 执行动作
Resource
→ 提供证据
边界会比较清楚。
如果做成 MCP,HITL 仍然应该在 Workflow 层
假设模型调用:
run_verified_candidate
但当前 Workflow:
status = awaiting_authorization
MCP Server 能不能直接执行?
我认为不应该。
Tool Implementation 仍然应该调用当前 Workflow 的权限检查:
是否已经 authorize
当前 workflow_id 是否存在
variant 是否允许
运行预算是否还有剩余
也就是说:
MCP
只是换了一种调用入口。
原来的:
Authorization
Policy
Budget
Validation
全部仍然存在。
不能变成:
以前:
Workflow → 安全检查 → Engine
用了 MCP:
LLM → MCP → Engine
↑
绕过所有检查
那反而是架构退步。
Tool 层最危险的误区是“能执行就算完成”
一个 Tool 真正值得给 Agent 使用,我觉得至少应该回答:
输入边界是什么?
谁有权限调用?
什么时候允许调用?
调用次数有没有预算?
底层能访问哪些资源?
结果怎样验证?
失败怎样表达?
是否存在副作用?
是否可以重试?
如何审计?
Game Change Agent 当前虽然没有 MCP,但 EngineRunner 已经处理了其中很多问题:
固定 Player
→ 不允许任意程序
Runtime Path Restriction
→ 不允许任意路径
Variant Validation
→ 不允许任意运行目标
Fixed Seed
→ Observation 尽量可重复
Config Hash
→ Evidence 和 Candidate 绑定
Timeout
→ 外部进程不能无限运行
Artifact Allowlist
→ 只暴露已知证据
Telemetry Validation
→ 运行结束不等于结果可信
这些设计并不会因为以后换成 MCP 而过时。
相反:
协议越标准,越需要 Tool 本身有清楚的能力边界。
面试时我会怎样回答
如果面试官问:
Tool Calling 和 MCP 有什么区别?
我会回答:
Tool Calling 更偏模型和宿主应用之间的交互方式。模型不会真的执行函数,它只是用结构化形式告诉应用“我想调用哪个工具和哪些参数”,最终还是应用负责校验并执行。
MCP 解决的是另一个边界:AI 应用怎样用标准协议发现和调用外部工具、Resources 和 Prompts。一个 Tool 完全可以是应用内部的 Python 函数,不需要 MCP;如果以后多个 Agent 或 IDE 都要复用同一个 Engine Tool,再把它封装成 MCP Server 就比较有价值。
所以两者可以组合:模型先产生 Tool Call,Host 再通过 MCP Client 调 MCP Server 上的 Tool。
如果继续问:
你这个项目用了 MCP 吗?
我会直接回答:
当前主流程没有用 MCP。项目里的 Unity 和 UE5 通过统一 EngineRunner Python 接口接入 Workflow,因为目前调用方就在同一个服务内,这样更简单。我没有为了关键词强行加一层 MCP。
但 EngineRunner 已经把引擎能力、参数和返回证据抽象出来了。如果以后这个能力需要被多个独立 Agent 或 IDE 复用,就可以进一步把受控能力封装成 MCP Tools,把 Telemetry、Manifest 等 Evidence 暴露成 Resources。
如果继续问:
EngineRunner 和 MCP 有什么相似的地方?
我会回答:
EngineRunner 解决的是项目内部的能力抽象,让 Workflow 不需要知道 Unity 和 UE5 各自的启动方式。MCP 可以看成再把这类能力抽象扩展到跨应用边界,让不同 AI Host 用统一协议发现和调用。一个是内部接口,一个是标准化外部协议,解决的层级不同。
如果继续问:
MCP 能不能保证 Agent 调工具安全?
我会回答:
不能。MCP 标准化的是能力描述和调用方式,真正的权限控制仍然要由 Tool 和 Host 实现。比如我的 UE Runner 即使以后做成 MCP,也只会允许预注册 Player、固定 variant 和 runtime-artifacts 下的路径,不会因为有 MCP 就暴露任意 subprocess 或 shell。
我从这个问题中理解到的事情
最开始听到:
Tool Calling
Function Calling
MCP
Agent Framework
这些词时,很容易觉得它们都在描述:
“让 AI 调工具。”
但真正实现一条 Agent Workflow 后,我觉得把边界拆开反而更容易理解。
LLM
→ 负责选择或提出 Action
Tool Calling
→ 把 Action 表达成程序能理解的结构
Workflow
→ 判断当前状态下是否允许这个 Action
Tool
→ 真正执行一个受限能力
Observation
→ 把执行结果重新带回 Agent
MCP
→ 让 Tool / Resource / Prompt 能以标准接口被不同 AI 应用复用
而 Game Change Agent 当前真正做的是:
Quality Review Agent
↓
受限 Repair Action
↓
Policy Gate
↓
Deterministic Repair Tool
以及:
Workflow
↓
EngineRunner
↓
Unity / UE5
↓
Validated Evidence
它没有 MCP。
但这并不妨碍它拥有:
Tool Abstraction
Action Boundary
Execution Boundary
Evidence Boundary
反而因为这些边界已经先设计清楚,我才更容易判断:
MCP 应该什么时候出现。
我现在会把关系总结成一句比较简单的话:
Tool Calling 解决“模型想调用什么”,Tool 实现解决“这件事到底怎么做”,MCP 解决“这项能力怎样被不同 AI 应用标准化地找到和调用”。
而更重要的是:
无论有没有 MCP,真正需要认真设计的始终是 Tool 本身能做什么、不能做什么,以及调用它之前和之后系统怎样验证。
一个非常强大的:
run_any_command
即使用最标准的 MCP 暴露出来,也仍然是一个危险的 Tool。
一个只有:
run_verified_candidate
权限的受控 Tool,即使只是普通 Python 方法,也可以拥有清楚的安全边界。
所以在 Agent 工程里,我现在更在意的不是:
“这个项目有没有用 MCP?”
而是:
这个能力是否已经被抽象成一个边界明确、输入可验证、结果可检查、权限可控制的 Tool。
当这个 Tool 真正需要跨应用复用时,再让 MCP 负责把它标准化地连接出去。
延伸阅读
- Model Context Protocol:Specification
- Model Context Protocol SDK:Tools
- Model Context Protocol SDK:Resources
- Model Context Protocol SDK:Prompts
- OpenAI:Function Calling / Tool Calling