返回文章列表

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