Agent 协议:MCP、A2A、AG-UI
如果你正在做 Agent 应用,几乎一定会遇到这三个问题:
- Agent 怎么安全、标准地接外部工具和数据;
- Agent 怎么和别的 Agent 协作;
- Agent 怎么和用户界面稳定地实时交互。
MCP、A2A、AG-UI 对应的正好是这三个层面。很多团队早期会把它们混在一起理解,最后要么“全都自己封装一层”,要么把一个协议硬塞进不适合的场景,系统复杂度暴涨,维护成本也很快失控。
这篇文章把三者拆开讲清楚:
- MCP 解决 Agent 与工具/上下文系统的连接问题;
- A2A 解决 Agent 与 Agent 的跨系统协作问题;
- AG-UI 解决 Agent 与用户界面的事件化交互问题。
MCP
MCP 全称是 Model Context Protocol,即模型上下文协议。
它的核心目标很直白:给“LLM 应用(Host)和外部能力(Server)”之间定义一套统一的、可协商能力的协议层。这样应用不用为每个工具系统都单独写一套适配通信协议,也不需要把每个外部系统都改造成同一种 SDK 形态。
官方对 MCP 的定位是“上下文交换协议”,它不规定你的 Agent 如何推理,不规定你必须用哪家模型,也不规定你应用层怎么设计工作流。它只把“连接、发现、调用、通知、协商”这层标准化。
如果把一个完整 Agent 系统拆成三层:
- 上层是用户交互;
- 中层是 Agent 编排与决策;
- 下层是工具、数据、执行环境。
MCP 很明确地工作在中下层边界,重点是把“可供 Agent 使用的能力”暴露成统一协议对象。
它本质上是“能力总线”,不是“业务编排框架”。
flowchart LR
U[用户] --> H[MCP Host\n如 Claude Desktop / IDE Agent]
H --> C[MCP Client]
C <-->|JSON-RPC 2.0| S1[MCP Server A]
C <-->|JSON-RPC 2.0| S2[MCP Server B]
S1 --> T[Tools]
S1 --> R[Resources]
S1 --> P[Prompts]
S2 --> T2[Tools]
S2 --> R2[Resources]
subgraph Transport
ST[STDIO]
SH[Streamable HTTP\nPOST/GET + 可选 SSE]
end
C -.基于传输层.- ST
C -.基于传输层.- SH
协议组成
MCP 的消息格式基于 JSON-RPC 2.0。
网络层面:
- 本地模式用标准输入输出流;
- 远端模式用 HTTP(POST/GET),可选 SSE 做流式返回。
你可以把它理解为:
- 语义标准统一在 JSON-RPC;
- 传输通道按部署场景替换。
MCP 可以按两层来理解:
数据层
数据层基于 JSON-RPC 2.0,负责消息语义,主要包含:
- 生命周期管理:
initialize、能力协商、初始化完成通知等; - Server 侧能力:
tools、resources、prompts; - Client 侧能力:
sampling、elicitation、logging等; - 通知机制:列表变化、进度、状态更新。
这个分层的价值在于:你不用关心底层到底是本地进程管道还是网络连接,方法语义是一致的。
传输层
截至当前稳定规范,官方标准传输是两种:
stdio:本地子进程通信,适合本机工具;Streamable HTTP:HTTP POST/GET,支持返回 JSON 或text/event-stream流。
规范明确指出:Streamable HTTP 取代了更早版本中的 HTTP+SSE 传输定义,语义更清晰,也更适合做远端部署和可恢复流。
核心原语
MCP 最关键的三类 Server 原语:
Tools:可执行动作,模型可调用;Resources:上下文数据源,供应用和模型读取;Prompts:可复用提示模板,常由用户触发。
官方还给了一个很好用的控制层级:
- Prompts 偏用户控制;
- Resources 偏应用控制;
- Tools 偏模型控制。
这个区分非常实用,能避免“什么都做成 Tool”的反模式。
发送方与接收方是谁
MCP 的参与者有三个角色:
MCP Host:比如桌面 AI 客户端、IDE 插件容器;MCP Client:Host 内部创建的连接组件;MCP Server:真正提供能力的一端。
消息方向并不是单向。
常见方向:
- Client -> Server:
tools/list、tools/call、resources/read; - Server -> Client:通知、请求采样、请求用户补充信息。
因此它不是传统“纯后端 API”模型,而是可双向交互的协议会话。
示例
官方教程里最常见的是 Python FastMCP,非常适合从零起步。我们以此做一个最小 Weather Server,暴露两个工具:
get_alertsget_forecast
uv init weather
cd weather
uv venv
uv add "mcp[cli]" httpx
然后编写以下脚本:
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather")
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
async def make_nws_request(url: str) -> dict[str, Any] | None:
headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
async with httpx.AsyncClient() as client:
try:
resp = await client.get(url, headers=headers, timeout=30.0)
resp.raise_for_status()
return resp.json()
except Exception:
return None
@mcp.tool()
async def get_alerts(state: str) -> str:
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
data = await make_nws_request(url)
if not data or "features" not in data:
return "Unable to fetch alerts or no alerts found."
return "No active alerts for this state." if not data["features"] else "Fetched"
def main():
mcp.run(transport="stdio")
if __name__ == "__main__":
main()
A2A
A2A 的全称是 Agent2Agent Protocol,常见中文是 智能体到智能体协议。
它的定位是让不同组织、不同框架、不同部署环境里的 Agent 可以标准化协作,避免一对一私有协议集成。
A2A 关心的是“Agent 间任务协作”,不是“Agent 调单个工具”。
A2A 想解决的问题是:
- Agent 不该被迫“伪装成工具”才能协作;
- 跨框架多 Agent 场景不该每次都重做通信层;
- 长任务、流式、异步通知应该是协议内建能力。
它强调“不暴露内部实现细节的协作”,也就是 opaque execution。你知道对方能力和协议入口,但不需要知道对方内部 memory、toolchain、模型编排细节。
sequenceDiagram
participant Client as A2A Client(客户端 Agent)
participant Server as A2A Server(远端 Agent)
participant Auth as Auth Server
Client->>Server: GET /.well-known/agent-card.json
Server-->>Client: Agent Card(能力、技能、认证要求)
Client->>Auth: 按 Agent Card 的安全方案取凭证
Auth-->>Client: Token / Credential
Client->>Server: SendMessage(JSON-RPC over HTTP)
alt 即时结果
Server-->>Client: Message
else 长任务
Server-->>Client: Task(submitted / working ...)
Server-->>Client: SSE 推送 TaskStatusUpdate / ArtifactUpdate
opt 超长任务
Server-->>Client: Webhook Push Notification
end
end
协议组成
A2A 的核心绑定里最常见的是:
JSON-RPC 2.0 over HTTP(S);SSE用于流式事件;- 规范还定义了
gRPC和HTTP+JSON/REST绑定。
对大多数团队来说,JSON-RPC over HTTPS + SSE 是第一落地点,迁移成本最低。
核心对象
A2A 的模型对象非常完整,工程上重点记住这几类:
Agent Card:Agent 的“能力名片”,含 endpoint、skills、认证方式;Task:有生命周期的工作单元;Message:一次通信回合,带role;Part:Message/Artifact 的最小内容单元;Artifact:任务产出物,可以增量更新。
这套对象组合的意义是:
- 可以先协商能力,再发任务;
- 可以即时回复,也可以切成长任务;
- 可以在任务执行中持续流式更新结果;
- 可以把结果沉淀成结构化产物,而不是只吐一段文本。
发送方与接收方
在 A2A 里:
- 发送请求的一般是
A2A Client; - 处理请求的一般是
A2A Server(Remote Agent)。
Role 语义也在协议里有定义:
ROLE_USER:客户端到服务端的消息;ROLE_AGENT:服务端到客户端的消息。
如果任务很长,Server 还会作为主动发送方,通过 SSE 或 Webhook 把状态变化发给 Client。
Agent Card
A2A 里最容易被低估的对象就是 Agent Card。
它承担了几个关键职责:
- 发现:客户端知道“这个 Agent 是谁、会什么”;
- 连接:知道该请求哪个 endpoint;
- 安全:知道该用哪种认证方案;
- 协议选择:知道支持哪些 transport / binding;
- 扩展声明:知道是否需要扩展能力。
规范里还给了 well-known 地址约定:
https://{server_domain}/.well-known/agent-card.json
这让大规模注册发现和自动化编排更可行。
Task 生命周期
A2A 的工程价值,很大一部分来自它把任务生命周期协议化了。
一个任务不是只有“成功/失败”两种状态,而是可以经历:
- submitted
- working
- input-required
- auth-required
- completed / failed / canceled / rejected
这会直接改善两类体验:
- 产品体验:用户能看到任务进度,不是一直转圈;
- 系统体验:调度器可以根据状态做超时、重试、分派。
示例
这里我们使用官方的 @a2a-js/sdk 包。
npm install @a2a-js/sdk express
然后编写核心代码:
import express from "express";
import { v4 as uuidv4 } from "uuid";
import type { AgentCard, Message } from "@a2a-js/sdk";
import {
AgentExecutor,
DefaultRequestHandler,
InMemoryTaskStore,
RequestContext,
ExecutionEventBus,
} from "@a2a-js/sdk/server";
import {
AGENT_CARD_PATH,
agentCardHandler,
jsonRpcHandler,
UserBuilder,
} from "@a2a-js/sdk/server/express";
const card: AgentCard = {
name: "Hello Agent",
description: "A simple A2A hello server",
protocolVersion: "0.3.0",
version: "0.1.0",
url: "http://localhost:4000/a2a/jsonrpc",
skills: [
{ id: "chat", name: "Chat", description: "Say hello", tags: ["chat"] },
],
capabilities: { pushNotifications: false },
defaultInputModes: ["text"],
defaultOutputModes: ["text"],
};
class HelloExecutor implements AgentExecutor {
async execute(ctx: RequestContext, bus: ExecutionEventBus): Promise<void> {
const msg: Message = {
kind: "message",
messageId: uuidv4(),
role: "agent",
parts: [
{ kind: "text", text: `Hello from A2A, context=${ctx.contextId}` },
],
contextId: ctx.contextId,
};
bus.publish(msg);
bus.finished();
}
cancelTask = async (): Promise<void> => {};
}
const handler = new DefaultRequestHandler(
card,
new InMemoryTaskStore(),
new HelloExecutor(),
);
const app = express();
app.use(
`/${AGENT_CARD_PATH}`,
agentCardHandler({ agentCardProvider: handler }),
);
app.use(
"/a2a/jsonrpc",
jsonRpcHandler({
requestHandler: handler,
userBuilder: UserBuilder.noAuthentication,
}),
);
app.listen(4000);
AG-UI
AG-UI 的全称是 Agent-User Interaction Protocol,中文可以叫 智能体-用户交互协议。
它的关注点和 MCP、A2A 不同:它解决的是“用户界面如何稳定地消费 Agent 执行过程中的事件流”,并允许用户输入、工具反馈、状态变化在同一条交互通道里双向流动。
AG-UI 主要在“前端应用 <-> Agent 后端”之间工作。
它不是 LLM 推理协议,不是工具调用协议,也不是跨 Agent 协议。它是“交互编排协议”:
- 把长时间运行的 agent run 拆成可消费事件;
- 把工具调用、状态变更、文本流、活动进度统一事件化;
- 让前端用一致方式渲染和反馈。
这件事看起来简单,实际上是 Agent 产品化最容易崩的地方。没有协议化事件层,UI 往往会陷入“几十个 websocket/HTTP 回调拼起来”的不可维护状态。
flowchart LR
UI[用户界面\nWeb/App] <--> Client[AG-UI Client\n如 HttpAgent]
Client <--> Backend[Agent Backend\nLangGraph / CrewAI / ADK 等]
Backend --> E1[Lifecycle Events]
Backend --> E2[Text Events]
Backend --> E3[Tool Events]
Backend --> E4[State Events]
Backend --> E5[Custom/Raw Events]
subgraph Transport
T1[HTTP SSE]
T2[HTTP Binary]
T3[WebSocket / Webhook]
end
Client -.传输无关.- T1
Client -.传输无关.- T2
Client -.可扩展.- T3
AG-UI 官方文档给的边界非常实用:
- MCP:Agent <-> 工具与数据;
- A2A:Agent <-> Agent;
- AG-UI:Agent <-> 用户应用。
在真实系统里,一个 Agent 经常会三者同时使用:
- 前端与 Agent run 用 AG-UI;
- Agent 内部访问工具用 MCP;
- Agent 需要委派其它 Agent 用 A2A。
边界清晰之后,代码层的分层会稳定很多。
协议组成
AG-UI 明确是 transport-agnostic(传输无关)的。
官方常见形态:
- HTTP SSE:易调试、兼容面广;
- HTTP Binary:更高性能与更低带宽;
- 也支持 WebSocket、webhook 等模式。
它的关键不是“只能用某一种传输”,而是“事件语义保持一致”。
事件模型
AG-UI 是典型事件协议,核心结构是 BaseEvent 与事件类型集合。
常见事件类别:
- 生命周期事件:
RUN_STARTED、RUN_FINISHED、RUN_ERROR、STEP_STARTED、STEP_FINISHED; - 文本事件:
TEXT_MESSAGE_START、TEXT_MESSAGE_CONTENT、TEXT_MESSAGE_END; - 工具事件:
TOOL_CALL_START、TOOL_CALL_ARGS、TOOL_CALL_END; - 状态事件:
STATE_SNAPSHOT、STATE_DELTA、MESSAGES_SNAPSHOT; - 活动事件:
ACTIVITY_SNAPSHOT、ACTIVITY_DELTA; - 特殊事件:
RAW、CUSTOM。
发送方与接收方
AG-UI 的发送方与接收方会在一个 run 生命周期内反复切换:
- 后端 Agent -> 前端:连续发事件流;
- 前端 -> 后端:发送用户输入、交互动作、上下文补充、控制指令。
这意味着 AG-UI 天生是双向交互模型,而不是“前端拉一次结果”的静态接口。
示例
如果你要快速做一个可演示、可迭代的前端 Agent 应用,AG-UI 官方推荐路径是先用 CLI 脚手架。
npx create-ag-ui-app@latest
npm run dev
脚手架会生成一套可运行的客户端与服务端组合,包含 AG-UI 事件流处理。默认示例里可以直接访问本地页面验证交互。
基于 HttpAgent 思路,可以抽象成:
import { HttpAgent, EventType } from "@ag-ui/client";
const agent = new HttpAgent({
url: "http://localhost:8787/agent",
agentId: "demo-agent",
threadId: "thread-001",
});
agent
.runAgent({
tools: [],
context: [],
})
.subscribe({
next: (event) => {
if (event.type === EventType.TEXT_MESSAGE_CONTENT) {
// 把文本增量写入 UI
}
if (event.type === EventType.STATE_DELTA) {
// 应用状态补丁
}
},
});
AG-UI 后端适配通常只做三件事:
- 把框架原生回调翻译成 AG-UI 事件;
- 保证事件顺序和 run/step 边界;
- 把用户输入与控制信号映射回框架执行层。
这也是为什么它适合接在 LangGraph、CrewAI、ADK、Mastra 等不同框架前面,做统一交互层。
AG-UI 的 STATE_SNAPSHOT + STATE_DELTA 组合值得重点用好:
- 首屏或重连先发 Snapshot;
- 持续交互阶段只发 Delta;
- Delta 采用可回放的补丁语义;
- 前端维护本地状态机,不把 UI 逻辑塞进后端 prompt。
这样可以显著降低前端抖动、重绘和状态错乱问题。