Agent Systems / Tool Protocol

MCP 与工具调用:把模型输出约束成可验证的外部动作

MCP 的价值不是“让模型更聪明”,而是给模型和工具之间建立可发现、可校验、可记录的协议边界。

Mechanism Lab

动画:MCP 如何把工具调用变成协议合约

动画展示 host/model 发现 MCP server 的 tools/resources/prompts,生成 tool call,经 schema gate 校验后执行,再把结构化结果或错误返回上下文。

Step 1 / 5

Discover

Host 通过 MCP client 发现 server 暴露的 tools、resources 和 prompts。

discover(server)

Animation Control

Reduced-motion users receive the same step states without continuous motion.

01 / 直觉

核心直觉

没有工具协议时,模型只能生成自由文本;有工具协议时,模型可以选择被声明过的 tool,并按 schema 生成参数。

MCP 把 host、client、server、tools、resources 和 prompts 分开:模型所在应用不需要硬编码每个工具,只需发现服务端暴露的能力。

工具调用的关键是 contract:名称、描述、输入 schema、权限、执行环境、输出格式和错误语义都必须明确。

对实证研究而言,MCP 让 Agent 能读取文件、查数据库、运行统计脚本、生成表格,但每一步都留下结构化 trace。

02 / 数学

工具调用的协议合约与状态语义

01 / 能力发现

Host 通过 MCP client 连接 server,读取可用 tools/resources/prompts。模型只应选择已声明能力。

C = discover(server) = {tools,resources,prompts}

02 / Schema 约束

每个工具有输入 schema。模型生成的 arguments 必须满足类型、必填字段和枚举约束。

valid(args, schema_tool) = true

03 / 动作生成

工具调用可以看成从模型上下文到结构化动作的映射;动作不是自然语言承诺,而是可执行对象。

a_t = f_theta(C,s_t) = {name,args}

04 / 执行隔离

Server 在自己的权限和运行环境中执行工具。Host 不应把模型文本当成本地 shell 命令直接执行。

o_t = server.execute(a_t)

05 / 结果回传

结果可能是结构化数据、文本、文件引用或错误。错误也必须回传给模型和 trace,而不是被吞掉。

r_t in {data,text,artifact,error}

06 / 审计日志

可复查系统应记录调用前状态、工具名、参数、结果、错误、权限和时间戳。

trace_t=(s_t,name,args,r_t,permission,time)

03 / 代码

Python 演示:用 schema 校验一个 MCP 风格工具调用

真实 MCP 通过协议传输;下面用纯 Python 展示同一个核心思想:先声明工具合约,再校验参数、执行工具、返回结构化结果。

from dataclasses import dataclass
from typing import Any, Callable

@dataclass
class Tool:
    name: str
    description: str
    required: set[str]
    types: dict[str, type]
    handler: Callable[[dict[str, Any]], dict[str, Any]]

def run_regression(args: dict[str, Any]) -> dict[str, Any]:
    outcome = args["outcome"]
    treatment = args["treatment"]
    controls = args.get("controls", [])
    return {
        "ok": True,
        "table": "outputs/regression.csv",
        "formula": f"{outcome} ~ {treatment} + {' + '.join(controls)}",
    }

REGISTRY = {
    "run_regression": Tool(
        name="run_regression",
        description="Estimate a simple regression and return a table path.",
        required={"outcome", "treatment"},
        types={"outcome": str, "treatment": str, "controls": list},
        handler=run_regression,
    )
}

def validate(tool: Tool, args: dict[str, Any]) -> None:
    missing = tool.required - set(args)
    if missing:
        raise ValueError(f"missing required fields: {sorted(missing)}")
    for key, expected_type in tool.types.items():
        if key in args and not isinstance(args[key], expected_type):
            raise TypeError(f"{key} must be {expected_type.__name__}")

def execute_tool_call(call: dict[str, Any]) -> dict[str, Any]:
    tool = REGISTRY.get(call.get("name"))
    if tool is None:
        return {"ok": False, "error": "unknown tool"}
    args = call.get("arguments", {})
    try:
        validate(tool, args)
        result = tool.handler(args)
        return {"ok": True, "tool": tool.name, "result": result}
    except Exception as exc:
        return {"ok": False, "tool": tool.name, "error": str(exc)}

call = {
    "name": "run_regression",
    "arguments": {
        "outcome": "wage",
        "treatment": "training",
        "controls": ["age", "education"],
    },
}

response = execute_tool_call(call)
print(response)

04 / 案例

案例:让研究 Agent 安全调用统计工具

  • 在 StatsPAI 场景中,Agent 可能需要调用 run_stata、read_table、estimate_did、render_regression_table 或 search_literature。
  • 如果没有协议边界,模型可能输出一段看似合理的 shell 命令,但路径、参数、权限和错误处理都不可控。
  • MCP 风格设计会先暴露工具清单和 schema。模型只能选择存在的工具,并提供满足 schema 的参数。Server 负责执行,Host 负责把结果和错误写回上下文。
  • 这使得审计成为可能:谁调用了哪个工具、用了什么参数、返回了什么文件、失败原因是什么、是否需要人工确认,都能被 trace 记录。

05 / 风险

常见误区

把工具描述写得过于模糊,模型不知道何时该调用、参数语义是什么。
只校验工具名,不校验参数类型、必填字段、路径范围和权限。
把模型生成的文本直接拼成 shell 命令执行,绕过 schema 和 sandbox。
没有把错误作为一等结果返回,导致 Agent 在失败后继续生成成功叙述。
工具返回过大的原始输出,挤占 context window;应返回摘要、artifact 引用和可追溯路径。
没有记录 trace,使得后续无法复查研究结论来自哪些工具调用。

参考资料