学完 Tool Use,我已经能解释模型怎样提出工具调用。但接入更多服务时,问题又变了:每个系统都有自己的接口、参数和鉴权方式;如果对方本身也是一个 Agent,我还需要知道它是否接受了任务、什么时候需要补充信息,以及最终交付了什么。
既然已经有 Function Calling 和 HTTP API,为什么还需要 MCP 与 A2A?我的理解是:前者让外部能力有共同的接入方式,后者让独立智能体之间的任务协作有共同的表达方式。
协议内容更新于 2026 年 9 月 14 日:本文以 MCP 2026-07-28、A2A 1.0.1 为基线。页面归档日期与本次技术更新日期分开记录。

图 1:Host 可以接入 MCP 服务,也可以通过 A2A 委托远端智能体。两种协议不是依次经过的上下级关系。
1. 先把三个问题分开
假设我要做一个技术资料助手,帮助我搜索文档、核对不同版本的说法,并生成报告。
| 我遇到的问题 | 对应的机制 |
|---|---|
| 模型怎样表达“搜索这段内容”? | Function Calling/Tool Calling |
| 搜索、文档、数据库怎样通过共同接口供程序使用? | MCP |
| 如何把“整理一份带证据的研究报告”委托给独立 Agent? | A2A |
Function Calling 是模型接口中的调用表达机制。MCP 是 Host 与能力提供方之间的协议。A2A 面向独立智能体之间的通信与任务协作。执行策略、权限、预算和结果验收,仍然需要运行程序负责。
这里容易出现一个误会:把 MCP 理解成“更高级的推理方法”。实际上,即使程序完全不调用 LLM,也可以作为 MCP Client 使用服务;模型也可以通过直接注册的本地函数完成 Tool Use,而不使用 MCP。
ReAct 讨论如何在行动与反馈之间继续决策;协议讨论参与方如何互相理解消息。这些能力可以组合,但解决的问题不同。A2A 与 MCP 的官方定位
2. MCP 统一的是哪一段接口?
MCP 的全称是 Model Context Protocol,模型上下文协议。它经常被比作 USB-C:不同应用通过共同接口接入外部能力。这个比喻适合入门,但不能进一步推导成“接上就自动理解业务”。
2.1 Host、Client、Server 各自做什么?
Host 是用户使用的应用或 Agent 运行环境。Client 是其中负责 MCP 通信的组件。Server 暴露工具、数据或模板,并把协议请求接到真正的业务实现上。
文档 MCP Server 后面仍然可能调用搜索引擎、数据库和文件服务。它把业务适配封装在能力提供方一侧,方便多个 Host 复用;底层 API 的差异没有凭空消失。

图 2:统一的是跨应用的接入契约;服务内部仍然需要业务适配、认证和错误处理。
2.2 Tools、Resources、Prompts
| 能力 | 技术资料助手中的例子 | 理解重点 |
|---|---|---|
| Tools | search_documents、export_report | 调用一个操作,操作也可以只是查询 |
| Resources | document://spec/v2 | 通过 URI 获取内容 |
| Prompts | compare_spec_versions | 获取带参数的提示模板 |
同一份文档可以通过读取工具获取,也可以作为 Resource 暴露。设计时应考虑调用者需要“执行查询”,还是“定位并读取已有内容”。不能简单把 Tools 等同于写入,把 Resources 当作数据库权限系统。
Prompt 模板也不是自动生效的系统指令。Host 决定如何展示或使用模板,外部模板的来源不会让它天然获得更高权限。具体能力说明见 MCP 服务端概念。
2.3 “发现服务”有两个层次
首先,Host 需要知道服务器地址或本地启动命令,这可能来自配置或目录。然后,Client 才能向已知服务器发现能力。
新版 server/discover 用于查询服务器支持的版本、能力与身份:服务端必须实现,客户端可以选择是否先调用。它不是让客户端自动扫描互联网寻找所有服务。MCP Discovery
3. 一次 MCP 工具调用怎样接到模型上?
技术资料助手发现一个文档服务后,可以获取工具列表,把合适的工具定义提供给模型。模型提出调用,Host 验证,再经 MCP Client 转发;结果经适配后回到模型对话。

图 3:模型接口与 MCP 是两套消息边界。Host 维护工具路由以及两侧调用标识的关联。
这里有两个很实际的细节。
第一,多个 Server 都可能提供同名工具。Host 应维护可信路由,例如把模型可见名称 docs_search 映射到指定服务器的 search_documents,而不是只凭名称猜目的地。
第二,MCP 的 Schema 表达范围与具体模型 API 的支持范围未必相同。适配时要检查兼容性;如果需要简化模型可见描述,执行前仍必须按完整契约验证,不能悄悄放宽业务约束。
3.1 JSON-RPC 是信封,MCP 定义具体方法
下面是 MCP 2026-07-28 的一个普通工具调用示例。假设双方使用该版本,且本轮不需要客户端补充输入:
{
"jsonrpc": "2.0",
"id": 21,
"method": "tools/call",
"params": {
"name": "search_documents",
"arguments": {"query": "MCP 工具结果", "limit": 3},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"io.modelcontextprotocol/clientInfo": {
"name": "study-assistant", "version": "1.0"
}
}
}
}对应结果可以是:
{
"jsonrpc": "2.0",
"id": 21,
"result": {
"resultType": "complete",
"content": [
{"type": "text", "text": "找到文档 doc-17:MCP 工具结果规范。"}
],
"isError": false
}
}id 关联请求与响应;method 指定协议操作;arguments 是工具自己的参数。模型接口里的 tool call ID 与 JSON-RPC ID 可以不同,由 Host 维护对应关系。
resultType: complete 表示这次协议结果完成,不代表整项用户任务完成;还要检查工具是否报告错误以及返回内容是否满足需要。MCP 工具也支持结构化结果和输出 Schema。MCP Tools
3.2 stdio 与 Streamable HTTP
stdio 常用于 Host 启动本地 Server,通过标准输入输出交换消息;调试日志不能随意混进协议输出。Streamable HTTP 常用于远端服务,也能用于本机 HTTP 服务。
HTTP 调用除了 JSON-RPC 正文,还需要按协议带版本和方法等头部;只复制上面的 JSON,并不等于完整 HTTP 请求。方法/名称头便于网关识别请求,但服务端仍需验证头与正文一致,不能只信任调用者填入的头部。
Streamable HTTP 可以返回 JSON 或使用 SSE 流。它与早期被弃用的 HTTP+SSE transport 不是同一个版本的传输设计。Streamable HTTP 规范
4. 新版为什么转向无状态?
4.1 从连接握手,到每个请求自带信息
2025-11-25 及更早版本使用 initialize → initialized 握手。2026-07-28 移除了这套握手与协议级会话,版本和客户端能力随请求携带;客户端身份信息推荐随请求提供。
这减少了处理请求时对先前连接状态的依赖。但“旧版一定需要粘性会话”“新版随便换副本就万事大吉”都太绝对:部署仍然要处理业务数据库、身份、任务存储和缓存。

图 4:新协议不依赖先前的初始化握手。需要补充信息时,通过 MRTR 返回要求,再由客户端发起后续请求。
例如一次资料分析返回 analysis_id,下一次查询显式传这个 ID。状态依然存在,只是其归属和引用方式更清楚了。对象 ID 也不是访问凭证,读取它仍要校验身份与权限。
4.2 MRTR:缺信息时,不必把请求一直挂在那里
MRTR 是 Multi Round-Trip Requests,多次往返请求。假设导出报告时缺少输出语言,Server 可以返回下面这样的中间结果。
{
"jsonrpc": "2.0",
"id": 22,
"result": {
"resultType": "input_required",
"inputRequests": {
"report_language": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "报告使用哪种语言?",
"requestedSchema": {
"type": "object",
"properties": {
"language": {"type": "string", "enum": ["zh", "en"]}
},
"required": ["language"]
}
}
}
},
"requestState": "opaque-state-issued-by-server"
}
}这个例子要求客户端预先声明支持相应 elicitation 能力。客户端获取回答,将对应的 inputResponses 加到原请求的后续请求中;若收到了 requestState,原样回传,使用新的 JSON-RPC ID。
requestState 对客户端是不透明的。服务端若用它影响权限或业务逻辑,需保护并验证完整性;需要单次消费的场景还得自行防止重复使用。补信息的往返机制不自动保证业务只执行一次。MRTR 规范
4.3 新旧版本知识怎样保留?
下面这些变化适合建立索引,遇到实现问题再查细节:
| 主题 | 2026-07-28 的学习重点 |
|---|---|
| 初始化 | 请求携带版本与能力;版本不兼容时处理错误 |
| 服务端变化通知 | 使用 subscriptions/listen;无状态不排斥流式订阅 |
| 长任务 | 原实验性任务能力移到官方扩展 |
| 列表和资源缓存 | 根据 ttlMs、cacheScope 等信息处理新鲜度和缓存范围 |
| 断流 | 不沿用旧的 SSE 自动续传假设,需按新版规则处理重发 |
这些属于协议演进,不能据此认为所有 SDK 和服务器已经同步升级。MCP 变更说明
Roots、Sampling、Logging 已被标记为弃用,但不等于立即删除。Roots 曾用于告知工作范围,真正的隔离仍要靠权限或沙箱;Sampling 曾让服务端请求客户端使用模型。新实现应参考各自迁移路径。动态客户端注册也已进入弃用登记,授权接入应核对当前方案,而不是沿用所有旧示例。弃用功能登记
5. A2A:从“调用能力”到“交付任务”
A2A 是 Agent2Agent 协议。假如文档助手只想读取一页内容,工具接口已经很自然;如果它要委托研究 Agent“核对多个版本,找出矛盾,交付带来源的报告”,调用方还需要任务状态、补充输入和产物等共同约定。
对方内部可以使用自己的模型、工具和工作流。调用方不需要知道全部实现,但要知道对方承诺接受什么、交付什么。
5.1 Agent Card 是服务说明,不是能力证明
Agent Card 描述智能体身份、能力、可用接口和认证要求。1.0 的 supportedInterfaces 可以声明不同协议绑定;不要把 A2A 写成只能使用 JSON-RPC。
正文学习时,先关注“谁、会什么、怎么连、怎么认证”四件事。JSON-RPC、HTTP+JSON、gRPC 是公共语义的不同绑定;代码示例则应固定其中一种。
Agent Card 中的 skill 是对外能力描述,不等同于本地 SKILL.md 技能包。签名可帮助校验卡片完整性与来源,但还需要可信密钥来源,也不能证明任务答案正确。扩展卡片提供认证后可见的信息,后端仍须执行真实授权。A2A 规范
5.2 Message、Task、Artifact 不要混为一谈
| 对象 | 放到资料助手里理解 |
|---|---|
| Message | 发出需求、补充语言、交换回答 |
| Part | 消息或产物中的文字、文件、结构化内容等部分 |
| Task | 需要跟踪的一项报告整理工作 |
| Artifact | 任务生成的报告、分析结果,可逐步产生 |
| contextId | 关联相关交互,不自动共享两侧完整记忆 |
远端可以直接返回 Message,也可以返回 Task。它不完全由耗时决定,而取决于服务的交互契约。报告生成中的一句解释可以是 Message;交付的报告适合成为 Artifact,产物也不必等到最后一刻才全部出现。
6. 当任务没有立即结束
6.1 状态说明“下一步由谁推进”

图 5:典型路径示意,不要求每个任务经历全部状态。等待补充输入与进入终态,是不同的处理分支。
客户端看到执行中状态,可以等待更新;看到需要输入,应获取缺失信息;看到需要授权,应进入可信授权流程。完成后读取并验收产物,失败或拒绝时保留原因。
终态任务不能简单当作还能继续工作的对象。若要修改已完成的报告,应按服务契约发起新的工作,并在合适的上下文中关联之前结果。A2A 任务生命周期
6.2 怎样拿到结果?
| 方式 | 优点 | 需要处理 |
|---|---|---|
| 直接响应 | 交互简单 | 请求等待时间与超时 |
| 轮询 | 易于接入和恢复查询 | 轮询间隔、截止时间、请求负载 |
| 流式更新 | 及时呈现状态和产物变化 | 断流、事件归并、当前状态核对 |
| 推送通知 | 客户端无需持续等待 | 回调认证、重复通知、地址安全 |
推送或流式能力需要对方声明支持,不能默认每个 Agent 都实现。一次连接断开,也不等于远端任务取消;请求取消更不等于撤销已经发生的业务操作。A2A 异步操作
6.3 客户端如何组织处理?
下面是使用自定义适配层的 Python 伪代码。send、get_task、decode_result 等名称不是某个官方 SDK 的固定接口;适配层负责认证、序列化和协议版本。示例使用轮询,保留超时后查询所需的任务标识。
import asyncio
from uuid import uuid4
async def delegate(api, card_url, brief, resolve_input,
timeout=120, poll_interval=2):
card = await api.fetch_card(card_url)
api.check_interface_and_trust(card)
api.prepare_auth(card) # 使用可信凭证,不让模型生成凭证
loop = asyncio.get_running_loop()
deadline = loop.time() + timeout
async def bounded(operation, *args, **kwargs):
remaining = deadline - loop.time()
if remaining <= 0:
raise TimeoutError("deadline_reached")
return await asyncio.wait_for(
operation(*args, **kwargs), timeout=remaining
)
reply = await bounded(api.send, text=brief,
message_id=str(uuid4()))
kind, value = api.decode_result(reply)
if kind == "message":
return {"status": "answered", "message": value}
task = value
while True:
if task.state == "TASK_STATE_COMPLETED":
return {"status": "completed", "artifacts": task.artifacts}
if task.state in {"TASK_STATE_FAILED", "TASK_STATE_REJECTED",
"TASK_STATE_CANCELED"}:
return {"status": task.state, "task_id": task.id}
if task.state == "TASK_STATE_AUTH_REQUIRED":
return {"status": "needs_auth", "task_id": task.id}
try:
if task.state == "TASK_STATE_INPUT_REQUIRED":
answer = await bounded(resolve_input, task)
reply = await bounded(
api.send, text=answer, task_id=task.id,
context_id=task.context_id, message_id=str(uuid4())
)
# 先消费返回消息/产物,再读取权威任务快照。
api.record_reply(reply)
else:
await bounded(asyncio.sleep, poll_interval)
task = await bounded(api.get_task, task.id)
except TimeoutError:
return {"status": "local_timeout", "task_id": task.id,
"context_id": task.context_id}这段流程没有自动重发首次委托。首次发送超时可能意味着“对方已经接收,但响应丢失”,需要按服务提供的去重和查询机制恢复,不能直接假定任务未创建。有了 messageId 也不应默认所有服务都保证发送幂等。任务完成后,调用方还要检查报告覆盖范围、来源和格式。
7. 放到同一个案例里看
用户要求:“比较两个版本的技术规范,整理差异,并说明哪些变化影响现有实现。”
资料助手先通过 MCP 搜索和读取规范,保存来源与版本标识;再通过 A2A 委托研究 Agent 分析。委托内容除了文件,还应说明目标、比较范围和验收条件。
研究 Agent 自行选择内部工具,必要时通过自己的 MCP 服务读取补充资料。它发现目标系统版本不明确,返回需要输入;资料助手获取真实版本后继续任务。最终报告作为产物返回,资料助手检查引用和结论,再交给用户。

图 6:两侧可以各自管理工具与上下文,通过明确的输入、任务状态和产物完成协作。
这里的 MCP URI、远端任务 ID、产物下载地址都属于具体系统的命名与权限范围。把一个本机路径发给远端 Agent,并不意味着对方能读取;需要提供实际可访问且权限受控的内容或引用。
7.1 哪些情况下不需要增加协议?
| 系统需求 | 我会优先考虑 |
|---|---|
| 单个程序内稳定、明确的操作 | 本地函数或现有 API |
| 多个 Host 复用同一组外部能力 | MCP |
| 独立服务之间需要任务委托、补输入和产物交付 | A2A |
| 同一进程里切换角色、调用子 Agent | 先判断内部接口是否足够 |
远端 Agent 可以被包装成 MCP Tool。这样做时,对外暴露的是工具调用契约;采用 A2A,则强调任务协作契约。不能仅按“对面有没有 LLM”或者“是不是长任务”选协议,MCP 自身也可以通过扩展处理任务。
8. 我的思考:统一之后,难题去了哪里?
8.1 协议让接口可对接,业务含义仍然需要澄清
两个 Server 都提供 search,不代表它们搜索相同范围、保证相同时效或返回相同质量。我的关注点从“字段能不能接上”,转向“调用者和提供方是否对结果有一致预期”。
如果需要做选择,我会先定义一组真实任务,再比较直接 API、MCP 接入和远端委托的效果。成功率、恢复成本、延迟和维护负担,都比是否采用热门协议更有说服力。
8.2 无状态,让我重新思考状态归属
我最初把无状态理解成不保存数据。现在更在意:任务属于谁,哪一方提供权威状态,下一次请求怎样明确引用它。
协议级会话减少之后,业务状态不会自动变简单。明确 ID 有利于跟踪,但也必须处理有效期、权限、版本和重复操作。把所有状态塞进一个模型看不见的会话,或者全部塞进对话文本,都未必是好设计。
8.3 委托执行以后,验收责任仍然存在
A2A 让远端 Agent 能自主完成内部步骤,但调用方仍需要描述清楚交付标准。一个 completed 状态只说明对方报告任务完成;它没有替我证明引用准确、计算正确或用户目标已经满足。
我会把“任务完成”和“产物验收”设计成两个判断。需要证据的报告,就检查来源;需要实际文件,就确认文件存在、可读取、版本正确。
8.4 接入越容易,越需要清楚的信任边界
Agent Card、工具描述、文档内容和返回结果提供信息,但不自动授予权限。协议复用认证机制,运行系统仍要决定谁能访问哪份数据、哪些操作需要确认,以及失败后怎样恢复。
这与上一篇 Tool Use 的认识是一致的:结构化接口提供了可以检查的入口,真正的可靠性来自对这些入口实施明确规则。
9. 面试时怎样回答?
MCP 与 Function Calling 有什么区别? Function Calling 表达模型提出的调用;MCP 约定能力提供方与 Host 之间的发现、访问和通信。Host 可以把 MCP 工具适配到模型接口,它们不是替代关系。
A2A 会取代 MCP 吗? 两者主要面向不同交互契约:能力访问与智能体任务协作。可以同时使用,也不必为了多 Agent 就全部引入。
无状态以后,任务状态存在哪里? 业务系统仍可保存状态,通过明确的任务或对象 ID 引用。无状态讨论的是协议请求对隐含会话的依赖。
任务请求超时怎么办? 先区分本地等待结束与远端执行状态。已有任务 ID 时查询状态;首次发送结果不确定时使用约定的去重/恢复机制,不盲目重新创建任务。
怎样判断是否值得引入协议? 看复用需求、独立部署边界、协作生命周期和维护成本,再用实际任务验证收益。协议名本身不是架构合理性的证据。
版本参考:MCP 版本说明、A2A 发布记录。文中伪代码用于解释调用方控制流程,接入时需按选定 SDK 和协议绑定实现适配层。