大模型只能基于当前上下文生成下一个 token。它看不见数据库,点不了按钮,也调不动另一个团队的 Agent。所谓 Agent,就是给这次生成接上外部世界。
接法有四种,常被并列成「技术栈」。它们不在同一层:
- Function Calling 是模型原生的调用语言。解决「如何开口要一个动作」
- MCP 是标准化的手。解决动作如何被发现、隔离、执行、写回上下文
- Skill 是按需手册和本地工装。解决「怎么做」何时进入上下文,以及哪些步骤不该交给模型猜
- A2A 是外包给同事。解决对端会规划时,一件任务如何活下去
类比可以先记在这:FC 像系统调用,MCP 像 USB-C 加一层 LSP 式驱动,Skill 像按需打开的手册,A2A 像把活分给有自己大脑的承包商。
flowchart TB U[用户目标] --> R[Agent Runtime] R --> P[Planning] R --> M[Memory / Context] P --> FC[Function Calling<br/>进程内一次调用] P --> MCP[MCP<br/>外部系统] P --> SK[Skill<br/>按需手册 + 本地脚本] P --> A2A[A2A<br/>另一个 Agent] FC --> M MCP --> M SK --> M A2A --> M

运行时仍是三层:推理层决定做什么,编排层决定调用谁,能力层真正执行。协议出现在后两层。Memory 是粘合剂:每只手用完,都必须改写模型此刻看见的世界,Planning 才接得上下一步。没有这个回写,协议再标准也只是在外面空转。
Function Calling:模型提议,宿主处置
这是最早、也最薄的一层,而且不会被替代。应用把函数 schema 塞进请求;模型用约束解码、tool choice 或 JSON mode,吐出符合 schema 的调用;宿主持执行,再把 observation 以 tool 消息写回。多轮 tool loop 是运行时自己转的,协议只定义单次形状。
核心不变量是 model proposes, host disposes。模型只能提议,真正碰世界的是宿主。非法 JSON 被拒;工具失败则把错误写回上下文,让模型决定重试还是换路。
它解决的是「从自由文本到可执行意图」。发现、鉴权、跨进程、版本协商、凭证隔离,它故意不管。这些空白不是缺陷,是留给上层的。
因此所有更高级的接口,最后都会被翻译回这门语言:MCP 的 tools/list 会变成模型眼前的 schema;Skill 脚本的结果、A2A 的 Artifact,也往往经同等通道进入下一轮。它是底噪,不是被淘汰的旧物。
权衡很干脆:延迟最低、实现最简单、对模型最直接;同时零生态、零隔离,应用必须自己处理一切。
MCP:M×N 问题,加上一条信任边界
MCP 管 Agent ↔ 外部能力。设计灵感是 Language Server Protocol:多个编辑器 × 多种语言,变成多个 Host × 多种工具。消息层用 JSON-RPC 2.0,传输层可换。
JSON-RPC 在这里不是口味。请求带 id,响应用同一个 id 配对;通知没有 id。同一条通道上可以交错 tools/list、tools/call、进度通知,而不必为每个动作新开 REST 资源。方法名是动词,适合动态发现。
角色拆开,才是协议真正硬的地方:
- Server 只提供聚焦能力。声明 schema、执行、返回结果或标准错误。它不聊天,也不规划下一步。它必须好写、可组合,并且 看不见完整对话,也看不见其他 Server。
- Client 守会话,把发现到的工具交给模型,按模型决策调用,再把结果写回上下文。
- Host 是安全经纪。用户同意、能力协商、凭证隔离、取消,都在这一层。工具描述默认不可信,除非 Server 本身可信。
这三条官方原则可以收成中文:Server 要极容易实现;Server 要能互相组合;Server 不准偷看对话、不准窥视邻居。微内核就是这个意思——能力在外,策略在内。
业务开始前先 initialize,交换协议版本和能力集。Server 可能不会流式,Client 可能不会处理采样。谈不妥,就不该假装对方什么都会。近期规格还往「请求尽量自包含」走,是为了少绑 sticky session,方便水平扩展。会话可以有,但不该把生命焊在某一根 TCP 上。
三类原语不是随便分的,控制权不同:
| 原语 | 谁主导 | 副作用 | 结果去哪 |
|---|---|---|---|
| Tools | 模型决定调不调 | 通常有 | 作为观察,进入下一轮 |
| Resources | 应用决定读不读 | 设计上只读 | 作为资料注入 |
| Prompts | 用户或工作流选用 | 无 | 变成后续对话骨架 |
「改 GitHub issue」是 Tool,「仓库 README」是 Resource,「code review 清单」是 Prompt。混用会把只读数据当成动作,或把动作当成可以随便重放的资料。
发现必须先于调用。tools/list 的结果,最终仍被翻译成 Function Calling schema 给模型看。凭证留在 Server,模型看见的是参数和结果,不是 token。一次 content 写回,本质是给下一轮注意力补一组新的 K/V,和 上下文工程 是同一件事。
传输不改消息语义,只改会话怎么活:
- Stdio:本地子进程,延迟最低,不能跨机器
- 旧 SSE:长连接绑会话,断了就丢
- Streamable HTTP:一个
/mcp。短任务直接 200,长任务再升级成流。用会话头串多次请求,而不是焊死一条连接

不变量可以记四句:消息是带 id 的 RPC;先协商再发现再调用;Server 没有对话权;隔离是一等公民。对端如果自己会拆任务、会反问,就不该再塞进 MCP。
Skill:上下文经济学,外加确定性岛屿
Skill 不是 RPC,也没有 task 状态机。它是一份可 Git 的文件夹:SKILL.md 加可选脚本、参考和资源。
它回答 MCP 不管的问题:这一刻该看见哪段方法,以及哪一步不该让模型生成。
三级加载是对注意力的显式管理,用来对抗 system prompt 膨胀和 lost-in-the-middle:
| 层 | 装什么 | 何时进上下文 | 成本直觉 |
|---|---|---|---|
| L1 | name + description |
启动时一直在 | 每个 Skill 大约几十到一百 token |
| L2 | SKILL.md 正文 |
意图命中后再加载 | 建议控制在几千 token 内 |
| L3 | scripts/、references/、assets/ |
真正执行时才打开 | 按需,几乎无上限 |
五十个 Skill 的目录可以很便宜;真正激活的两份手册才进窗口。这是按需分页,不是把整座图书馆搬进 prompt。
为什么是本地文件夹,而不是远程注册中心:要能私有、能审计、能版本化;脚本要在本机沙箱跑,凭证不出门,也不该因为注册中心挂了就不会做事。
脚本是 确定性岛屿。转 PDF、算表、校验格式有标准答案,走采样就是在赌。Skill 把程序性知识(组织怎么写邮件、合规红线)从模型权重里剥出来,同时把不该幻觉的步骤交给代码。

只接 MCP,会得到一堆没有方法的手。只堆 Skill,会做事但出不了沙箱。
A2A:不透明的同事,活的是任务
A2A 管 Agent ↔ Agent。对端有自己的目标理解、工具箱和失败重试。协议因此必须以 Task 为中心,并且必须 不透明:你看不到对方的思考、计划、工具实现和记忆。交换的只是声明过的能力和这次递过去的信息。这是安全,也是知识产权。
五个对象把语义钉死:
- Agent Card:
/.well-known/下的名片。端点、鉴权、技能、是否支持流、输入输出模态。同时承担发现、广告和鉴权协商 - Task:一件有寿命的活。稳定
taskId,自己经历状态 - Message:过程里的话
- Part:文本、文件或结构化数据
- Artifact:交付物。它不是聊天记录
状态分成三类:
- 进行中:
submitted、working - 暂停:
input-required、auth-required - 终态:
completed、failed、canceled、rejected
stateDiagram-v2 [*] --> submitted submitted --> working working --> inputRequired: 缺信息 working --> authRequired: 缺凭证 working --> completed working --> failed working --> canceled working --> rejected inputRequired --> working: 同一 taskId 补消息 authRequired --> working: 补授权 completed --> [*] failed --> [*] canceled --> [*] rejected --> [*]
input-required 在 MCP 里没有对应物。工具缺参数,直接报错;同事缺信息,会停住等你。同一 taskId 再补一条消息,任务从暂停回到 working。它可以停四秒,也可以停四天。终态通常不可原地重启,方便引用和审计。
三种 id 必须拆开:
- taskId 钉住这一件活
- contextId 钉住多轮记忆
- JSON-RPC 的 id 只钉住这一次请求
混用会把「再问一句」做成「再开一件新任务」,或把一次 HTTP 超时当成任务失败。
流的是任务进展,不是某个函数的 stdout。短任务可以阻塞到终态;长任务订阅 working、部分 Artifact、input-required;更长的用轮询或 webhook。
原理对照
| Function Calling | MCP | Skill | A2A | |
|---|---|---|---|---|
| 像什么 | 系统调用 | USB-C + LSP 驱动 | 按需手册 + 本地工装 | 有名片的承包商 |
| 发现 | 无,schema 写死 | tools/list / resources/list |
L1 元数据一直在系统提示里 | Agent Card |
| 状态 | 单次调用 | 请求尽量自包含,会话可选 | 加载即用 | 有寿命的 Task |
| 信任边界 | 同一进程,无隔离 | Server 看不见对话和邻居;Host 做经纪 | 本地文件夹 + 沙箱 | 完全不透明 |
| 失败 | 非法 JSON 或工具报错,写回模型 | 结构化错误回 Host,Server 不规划 | 指令含糊或脚本失败 | 追问、改路、部分交付、拒绝 |
| 保证 | 模型能发出可解析意图 | 能力可组合,且默认隔离 | 上下文成本可控,确定步骤可重复 | 委托一件活,而不交出大脑 |
| 不保证 | 生态、鉴权、跨进程 | 对端会思考 | 能碰到沙箱外的系统 | 一次往返就结束 |
组合,以及怎样接到一件真事上
健康的叠法通常是:Skill 教模型怎么正确使用一组 MCP 工具;主 Agent 用 A2A 把子目标交给专业同事,对方自己再挂 MCP 和 Skill;收尾用本地脚本做校验和导出。
不健康的叠法就那几条:把 Skill 冻成 MCP Server,丢掉渐进披露和本地确定性;把 A2A 当远程 Function Calling,把同事降成 API;只接 MCP 不接 Skill,工具很多但没有方法;只堆 Skill,出不了沙箱。
举一件完整的事:写一份符合公司合规的竞品调研报告。
- 用户给出目标。Memory 开始记下任务边界。
- Skill L1 命中「合规调研报告」,再加载 L2:章节、红线、语气。渐进披露。
- MCP 调内部知识库和 CRM。Server 看不到整段对话,只拿到这次查询需要的参数。隔离。
- A2A 把「外部竞品深挖」交给研究 Agent。先读 Card,再提交 Task。对方可能
input-required问范围,最后回 Artifact。不透明执行。 - 主 Agent 综合结果,继续规划。所有观察写回 Memory。
- 本地 Skill 脚本做格式校验和 PDF 导出。确定性岛屿。
每一步换的不是「更新的协议」,而是对端的智能和该守的边界。
按对端选接口:动作只在本进程 → Function Calling;动作在外部系统、要发现和隔离 → MCP;缺方法和确定性步骤 → Skill;对端会规划、会反问、交付需要时间 → A2A。
协议字段会改。相对稳的是三件事:划清哪些是一次调用、哪些是按需手册、哪些必须是另一段有寿命的任务;把会话、权限、取消、审计做稳;管住模型此刻看见什么——短记忆、长记忆、Prompt 和上下文工程。
出对话框的方式不会变:先约定这次生成能碰到什么,再让协议把那一次碰到,收成下一轮还能用的事实。