← Agent 工程 · 第 2 篇

工具调用与 MCP:协议之外,还要有执行边界

定义参数、结果、错误和权限契约,用离线工具分发器示例防止模型输出直接执行。

学完这一篇,你能做到

设计一个可以校验、授权和观察的工具接口,理解 MCP 的职责范围。

工具调用的可靠性来自契约,而不来自模型输出看起来有多合理。模型提出工具名称与参数,应用决定这些请求能否执行。

MCP 提供工具发现与调用等标准化接口,但协议互通不会自动赋予访问权限,也不会替你处理业务幂等。

定义四类契约

参数契约说明字段、类型、范围与必填项。结果契约说明成功时返回什么。错误契约区分参数错误、无权限、暂时失败和结果不确定。权限契约则限制谁能操作哪些资源。

名称也应表达清楚的行为。把“查询状态”和“修改状态”合在一个含糊工具里,会增加模型误选和权限判断的难度。

对于有副作用的工具,还要定义操作 ID、重试条件与必要确认。一个正确的 JSON schema,只解决参数结构问题。

执行器必须拥有最终决定权

模型输出只能进入已注册的分发器。禁止把工具名称直接当函数名执行,也不要把输出交给 eval 或通用 shell。

服务端重新验证参数和权限,不能只相信客户端或模型已经检查。返回的信息也应限制大小、结构与敏感字段。

离线练习工具分发

这个 Python 示例只实现一个本地单词计数工具。它不是 MCP 服务端,重点是允许清单与参数校验。授权工具集合由应用提供。

def dispatch(call, allowed_tools):
    if not isinstance(call, dict) or set(call) != {"name", "arguments"}:
        raise ValueError("invalid call")
    name = call["name"]
    if name != "word_count" or name not in allowed_tools:
        raise PermissionError("tool not allowed")
    args = call["arguments"]
    if not isinstance(args, dict) or set(args) != {"text"}:
        raise ValueError("invalid arguments")
    text = args["text"]
    if not isinstance(text, str) or len(text) > 2000:
        raise ValueError("invalid text")
    return {"count": len(text.split())}

result = dispatch({
    "name": "word_count", "arguments": {"text": "small useful tools"}
}, {"word_count"})
assert result == {"count": 3}
try:
    dispatch({"name": "run_shell", "arguments": {}}, {"word_count"})
except PermissionError:
    print("Unregistered tool rejected")
else:
    raise AssertionError("unsafe tool accepted")

真实的计数规则需要定义语言与分词方法。这里的空格分词仅适合示例,不能据此宣称支持所有语言的准确统计。

MCP 解决互通,应用解决授权

接入时核对当前协议版本、能力声明、工具列表和输入输出 schema。不要把教程里的片段当成完整协议消息;真实请求还需要满足该版本的消息元数据要求。

工具返回的文本也可能携带不可信指令。它是外部结果,不应直接升级为系统规则。标准化连接越容易,资源边界就越需要清楚。

下一步

从一个只读工具开始,写出参数、结果与错误类型。增加未知字段、超长输入和未授权工具三个测试用例。

之后再加入有副作用的操作,为它设计操作 ID、确认与恢复流程。下一篇讨论进程中断后,任务如何继续而不重复执行。

资料来源与延伸阅读

资料核验:2026-11-10。本文为原创工程讲解,教学示例不代表线上实测。