
前言
在进入正文之前,先交代一下这些文章的来龙去脉。
AgentForge 是一个面向 Java 开发者、从 LLM 最底层能力开始构建 的开源 Agent 框架。它不从高度封装的 Agent API 起步,而是先建立稳定、统一、可扩展的模型抽象,再逐层向上锻造 Tool、Memory、Middleware、Reasoning 与 Agent Runtime 等能力。
AgentForge = Agent + Forge:Agent 代表能理解目标、进行推理、调用工具并完成任务的智能体,Forge 则强调把原始智能持续加工、塑形、强化,最终锻造成真正可用的产品。
本系列《AgentForge 核心模块设计原理》沿着这条自底向上的路径,逐个模块拆解它的设计原理与实现细节。本文聚焦 MCP(Model Context Protocol,模型上下文协议)的历史演进与底层原理——从 Function Calling 的 M×N 集成难题讲到 JSON-RPC 协议设计、Host/Client/Server 三角色与传输机制,并给出基于 Java 的 Client/Server 搭建与对接 Agent 的完整流程。
- 开源仓库(GitHub):https://github.com/changluya/AgentForge
- 项目文档站:https://changluya.github.io/AgentForge/
- Gitee 镜像:https://gitee.com/changluJava/agent-forge
- 开源协议:MIT
git clone https://github.com/changluya/AgentForge.git
cd AgentForge
mvn clean install -DskipTests
如果这套「自底向上」的设计对你有帮助,欢迎到 GitHub 给 AgentForge 点一个 Star。
一、背景与问题引入
1.1、场景驱动:一个真实的集成难题
在开发一个 AI Agent 产品的过程中,我们就遇到了这样一个具体问题:
用户希望 Agent 能查天气、查数据库、读 GitHub、发钉钉消息……而这些能力,团队里不同的同学已经用不同语言、不同框架写好了一堆「工具服务」。有的用 Python 写、有的用 Node 写、有的干脆就是一段 shell。
问题是:每接一个工具,Agent 应用就要为它写一次适配代码。工具一多,应用端就成了一个大杂烩:一会儿要引入 Python 进程,一会儿要封装 HTTP,一会儿要写 JSON Schema。换个模型厂商,Schema 格式又不一样,全部重来。
这不是某一家产品的痛点,而是整个行业的痛点。MCP 正是为它而生。
1.2、Function Calling 的「M × N 集成灾难」
在 MCP 出现之前,主流做法是各家模型厂商的 Function Calling / Tool Use:
- OpenAI 用
tools[].function.{name,description,parameters} - Anthropic 用
tools[].{name,description,input_schema} - 各家 Prompt 模板、强制调用参数、并行调用能力都不完全一致
于是形成一个典型的 M × N 问题:
M 个 AI 应用(Host / Agent 框架)
×
N 个外部工具(DB、GitHub、Slack、文件系统……)
=
M × N 份适配代码
每一次新增工具,都要在 M 个应用里重复接入;每一次新增应用,都要重新对接 N 个工具。成本随规模平方级增长。
1.3、设计灵感:来自 LSP 的类比
MCP 的架构范式直接承袭 LSP(Language Server Protocol):
LSP:M 个编辑器 × N 种编程语言 → M + N(协议居中)
MCP:M 个 AI 应用 × N 个外部能力 → M + N(协议居中)
正如 LSP 让「编辑器」与「语言服务」通过统一协议解耦,MCP 让「AI 应用」与「外部能力」通过统一协议解耦。这个类比是理解 MCP 一切设计的钥匙——Host 管编排、Server 管能力、协议管通信。
1.4、本文要回答的问题
- MCP 从哪里来?治理如何演进?
- 它有哪些版本?每个版本改了什么?
- 当前(2026-07-28)的协议设计全貌是什么?
- 一个 MCP Client / Server 内部是怎么跑起来的?
- 这些设计背后有哪些权衡?
二、起源与版本历史
2.1、发布与开源
- 2024-11-05:MCP 首个规范版本发布,定义 client-server 架构、JSON-RPC 2.0、
tools/resources/prompts三大原语,以及stdio与HTTP+SSE两种传输。 - 2024-11-25:Anthropic 公开宣布并开源 MCP,同时发布 Python / TypeScript SDK 与 Google Drive、Slack、GitHub、Git、Postgres 等参考 Server。
注意:版本号
2024-11-05早于公开宣布日2024-11-25。这是因为 MCP 采用日期型版本号,其含义是「最后一次包含不兼容变更的日期」,而非发布日。
2.2、版本号与状态规则
- 版本标识形如
YYYY-MM-DD;协议版本不因向后兼容的更新而递增。 - 每个修订有三种状态:Draft(草稿)、Current(当前)、Final(已定稿)。
- 单个特性还可被标记为 Deprecated(弃用),受正式的特性生命周期与弃用策略约束(2026-07-28 引入,至少 12 个月弃用窗口)。
2.3、版本时间线与采用里程碑
| 日期 | 事件 | 类型 |
|---|---|---|
| 2024-11-05 | 首个规范 2024-11-05:client-server、JSON-RPC 2.0、tools/resources/prompts、stdio + HTTP+SSE |
规范修订 |
| 2024-11-25 | Anthropic 宣布并开源 MCP,发布 Python / TS SDK 与参考 Server | 发布 |
| 2025-03-19 | Microsoft 在 Copilot Studio 引入 MCP 支持 | 采用 |
| 2025-03-26 | 规范 2025-03-26:OAuth 2.1 授权、Streamable HTTP 取代 HTTP+SSE、工具注解、音频、补全、JSON-RPC 批处理 |
规范修订 |
| 2025-03-26 | OpenAI 在 Agents SDK 引入 MCP | 采用 |
| 2025-04-09 | Google Gemini 承诺支持 MCP | 采用 |
| 2025-05-19 | Microsoft 在 Build 2025 全面支持 MCP;微软与 GitHub 加入指导委员会 | 治理/采用 |
| 2025-05-21 | OpenAI 在 Responses API 支持远程 MCP Server,加入指导委员会 | 采用/治理 |
| 2025-06-18 | 规范 2025-06-18:结构化工具输出、elicitation、资源链接;移除 JSON-RPC 批处理;MCP-Protocol-Version 头 |
规范修订 |
| 2025-09-05 | 官方 PHP SDK 发布 | 生态 |
| 2025-09-08 | 官方 MCP Registry 预览发布 | 生态 |
| 2025-11-25 | 规范 2025-11-25(一周年):OpenID Connect Discovery、图标、标准枚举 elicitation、sampling 支持工具调用、实验 Tasks、JSON Schema 2020-12 |
规范修订 |
| 2025-12-09 | Anthropic 将 MCP 捐赠给 Linux 基金会下新成立的 Agentic AI Foundation(AAIF) | 治理 |
| 2026-01-26 | MCP Apps 作为首个官方扩展发布 | 扩展 |
| 2026-05-21 | 下一版 2026-07-28 发布候选(Release Candidate) |
规范修订 |
| 2026-06-29 | 四个一级(Tier 1)SDK 发布实现该候选版的 Beta 版本 | 生态 |
| 2026-07-28 | 规范 2026-07-28 成为 Current;四个一级 SDK 同日支持 |
规范修订 |
2.4、各版本关键设计变化
2025-03-26:传输与授权的大跨步
- 新增 OAuth 2.1 授权框架;
- 用 Streamable HTTP 取代 HTTP+SSE(当时仍保留 session:
Mcp-Session-Id头、可 GET 打开独立 SSE 流、可用Last-Event-ID恢复); - 新增工具注解、音频内容、参数补全、JSON-RPC 批处理。
2025-06-18:结构化输出与安全定位
- 新增结构化工具输出(
outputSchema/structuredContent)、elicitation(服务端向用户索要输入)、工具结果中的资源链接; - 把 MCP Server 明确定位为 OAuth Resource Server,要求 Resource Indicators(RFC 8707);
- 移除 JSON-RPC 批处理;
- 要求后续 HTTP 请求携带
MCP-Protocol-Version头。
2025-11-25:一周年版本
- 授权服务端发现增强为 OpenID Connect Discovery 1.0,支持通过
WWW-Authenticate做增量授权范围同意; - 工具/资源/模板/提示词支持图标元数据;
- elicitation 枚举改为标准化的 titled/untitled/单/多选与 URL 模式;
- sampling 支持工具调用;新增 OAuth Client ID Metadata Documents 注册机制;
- 引入实验性 Tasks;确立 JSON Schema 2020-12 为默认方言。
2026-07-28:无状态化范式修订(当前版本)
被称为「发布以来最大修订」,要点:无状态核心、移除握手与 session、server/discover、MRTR、订阅流、扩展框架、授权加固、正式弃用策略。详见第四章。
2.5、治理
- 2025 年起,Microsoft、GitHub、OpenAI 等先后加入 MCP 指导委员会;
- 2025-12-09,Anthropic 将 MCP 捐赠给 Linux 基金会下新建的 Agentic AI Foundation(AAIF)(由 Anthropic、Block、OpenAI 共同发起),官方声明治理模型不变;
- 规范采用 SEP(Specification Enhancement Proposal) 的 PR 工作流。
三、核心概念
3.1、Host / Client / Server 三角色
MCP 采用 client-host-server 架构,注意这里有两个「C」,容易混淆:
┌──────────────────────────────────────────────────────────┐
│ Host 进程(AI 应用,如 Claude Desktop / IDE / Agent 框架) │
│ ├── Client 1 ──1:1──► Server 1(本地文件 & Git) │
│ ├── Client 2 ──1:1──► Server 2(数据库) │
│ └── Client 3 ──1:1──► Server 3(远程 SaaS API) │
└──────────────────────────────────────────────────────────┘
- Host(宿主):创建并管理多个 Client,控制连接权限与生命周期,执行安全策略与用户授权决策,聚合上下文给 LLM。
- Client(客户端):由 Host 创建,与某一个 Server 严格 1:1;对每个请求附带协议版本与能力;双向路由消息;管理订阅与通知;维持 Server 间的安全边界。
- Server(服务端):通过原语对外暴露具体能力,独立、单一职责;可以是本地进程,也可以是远程服务。
重点:MCP 是无状态协议——每个请求自带处理所需的全部信息(版本、能力、身份),Server 不得依赖连接或先前请求推断状态。
3.2、三大 Server 原语与控制权
| 原语 | 由谁控制 | 用途 | 对应方法 |
|---|---|---|---|
| Tools | 模型控制(model-controlled) | 可被 LLM 调用的函数 | tools/list、tools/call |
| Resources | 应用控制(application-controlled) | 可读上下文,按 URI 标识 | resources/list、resources/read |
| Prompts | 用户控制(user-controlled) | 可复用的提示词模板 | prompts/list、prompts/get |
设计上三者的「控制权」明显不同:工具由模型自主决定调用,资源由应用决定何时注入上下文,提示词由用户显式选择。
3.3、四条设计原则
- Server 应极易构建:复杂编排由 Host 承担,Server 只专注单一能力。
- Server 应高度可组合:多个 Server 可无缝组合,共享同一协议。
- Server 不能读整个对话、也看不到其他 Server:完整对话历史只留在 Host;Server 之间相互隔离,跨 Server 交互由 Host 控制。
- 特性可渐进添加:核心协议最小化,其他能力按需声明与协商。
3.4、能力协商(Capability Negotiation)
能力协商是 MCP 的核心机制,但协商方式在 2026-07-28 发生了根本变化:
- 旧模型(≤ 2025-11-25):通过
initialize握手,一次性交换并确定整个 session 的能力。 - 新模型(2026-07-28):无握手;客户端在每个请求的
_meta.io.modelcontextprotocol/clientCapabilities中声明能力;服务端通过server/discover一次性公布其支持的版本与能力。
典型能力:服务端 tools / resources / prompts / logging / completions;客户端 roots / sampling / elicitation。
重要规则:服务端 MUST NOT 依赖客户端未声明的能力;若处理某请求需要客户端未声明的能力,服务端 MUST 返回
-32021 MissingRequiredClientCapability并在data.requiredCapabilities中列出。
四、协议核心设计
4.1、消息模型:JSON-RPC 2.0
MCP 所有消息都遵循 JSON-RPC 2.0,且消息 MUST 为 UTF-8。消息类型:
// 1) Request:请求,MUST 带非 null 的 id(不得与未响应的请求 id 重复)
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { /* ... */ } }
// 2) Result Response:成功响应,MUST 带 resultType(2026-07-28 起)
{ "jsonrpc": "2.0", "id": 1, "result": { "resultType": "complete", "content": [ /* ... */ ] } }
// 3) Error Response:失败响应,MUST 带 code 与 message
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32602, "message": "Invalid params" } }
// 4) Notification:通知,MUST NOT 带 id
{ "jsonrpc": "2.0", "method": "notifications/tools/list_changed" }
设计要点:MCP 规定「客户端只发请求/通知,服务端只发响应/通知」——服务端不主动发请求(2026-07-28 之后),客户端不发响应。这一约束是无状态与单向流设计的基石。
4.2、_meta:请求级协议元数据
所有协议元数据随消息体携带(传输层可选地镜像到信封头)。客户端请求的 _meta 字段:
| 键 | 类型 | 必填 | 说明 |
|---|---|---|---|
io.modelcontextprotocol/protocolVersion |
string | 是 | 本次请求使用的协议版本 |
io.modelcontextprotocol/clientCapabilities |
object | 是 | 客户端相关能力 |
io.modelcontextprotocol/clientInfo |
object | 否 | 客户端名称与版本(SHOULD) |
io.modelcontextprotocol/logLevel |
string | 否 | 本请求的最小日志级别 |
服务端 SHOULD 在每个结果的 _meta 中回 io.modelcontextprotocol/serverInfo。此外 traceparent / tracestate / baggage 被保留用于 OpenTelemetry 追踪上下文。
重点:
protocolVersion与clientCapabilities缺失即为畸形请求,服务端 MUST 以-32602拒绝(HTTP 上返回400 Bad Request)。这就是「请求自描述、不依赖连接状态」的具体落地。
4.3、resultType:多态结果设计
2026-07-28 为所有结果引入必填的 resultType:
"complete":正常完成,result含最终内容;"input_required":需要客户端补充输入(见 4.8 MRTR);- 扩展可新增其他
resultType,但必须来自核心集合或已声明扩展; - 客户端遇无法识别的
resultTypeMUST 视为非法; - 对不返回
resultType的旧版服务端,客户端 MUST 按"complete"处理。
4.4、错误模型与错误码
MCP 将错误分为两类,处理方式不同:
| 类型 | 含义 | 返回方式 | 模型可自纠 |
|---|---|---|---|
| Protocol Error | 请求结构问题(未知工具、畸形请求、服务端错误) | JSON-RPC error 对象 |
通常不能 |
| Tool Execution Error | 业务/API/校验错误 | result.isError = true + 文本 |
能 |
错误码分区策略:
-32700、-32600~-32603:标准 JSON-RPC 通用错误;-32000~-32019:遗留实现自定义区(新实现不应再分配);-32020~-32099:保留给 MCP 规范(见 Schema 错误定义)。2026-07-28 定义:
| 码 | 名称 |
|---|---|
-32020 |
HeaderMismatch |
-32021 |
MissingRequiredClientCapability |
-32022 |
UnsupportedProtocolVersion |
历史码 -32002(资源未找到,≤2025-11-25)被 -32602 取代,但客户端SHOULD 仍兼容旧服务端的 -32002。
4.5、传输设计
协议语义在所有传输上一致;传输只负责「分帧、投递、取消、终止」。标准传输有两种:
stdio(规范)
- Client 以子进程方式启动 Server,通过其标准流通信;
- 每个消息一行 JSON,不得内嵌换行;
- Server MUST NOT 往
stdout写非 MCP 消息;日志走stderr; - 取消:发
notifications/cancelled(单通道,无 per-request stream); - 关闭:关 stdin → 等退出 →
SIGTERM→SIGKILL(Windows 用TerminateProcess/Job Objects)。
Streamable HTTP(2026-07-28 形态)(规范)
- 单一 MCP endpoint,只用 POST;每个请求独立一个 POST;
- 请求头 MUST 含
Accept: application/json, text/event-stream、MCP-Protocol-Version、Mcp-Method,tools/call/resources/read/prompts/get还要Mcp-Name; - 响应可为单个 JSON,或请求级 SSE 流(先推通知,最后一条是最终响应);
- 取消 = 关闭该请求的响应流;
- 2026-07-28 移除:HTTP GET 流、
Mcp-Session-Id、Last-Event-ID可恢复性。
POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather
{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": { "name": "get_weather", "arguments": { "location": "Seattle, WA" },
"_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {} } } }
弊端说明:旧版 HTTP+SSE(2024-11-05)自 2025-03-26 被取代,2026-07-28 正式归入 Deprecated,新实现不应采用。
注意:
x-mcp-header允许把原始类型参数(string/integer/boolean,不含 number)镜像成Mcp-Param-{Name}头,便于网关路由;但头对中间网络可见,不得标记密码/Token/PII。
4.6、生命周期:legacy 握手 vs modern 逐请求
Legacy(≤ 2025-11-25):
initialize → 服务端返回能力 → notifications/initialized → 正常操作 → 关闭
initialize 请求/响应结构:
// 请求
{ "jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": { "protocolVersion": "2025-06-18",
"capabilities": { "roots": { "listChanged": true }, "sampling": {} },
"clientInfo": { "name": "ExampleClient", "version": "1.0.0" } } }
// 响应
{ "jsonrpc": "2.0", "id": 1,
"result": { "protocolVersion": "2025-06-18",
"capabilities": { "tools": { "listChanged": true } },
"serverInfo": { "name": "ExampleServer", "version": "1.0.0" } } }
Modern(2026-07-28):无握手;版本与能力随每个请求的 _meta 携带;server/discover 可选,用于一次性获取版本/能力/身份。
双时代兼容是现实刚需(新旧 Server 并存),官方给出探测机制:
- stdio:先发
server/discover;成功 → 现代;返回非现代错误或超时 → 回退initialize; - HTTP:先发现代请求;遇
400先看响应体——可识别的现代错误 → 现代;否则回退initialize。
注意:时代判定是服务端属性,应缓存(stdio 按进程、HTTP 按 origin),避免每请求探测。
4.7、无状态化设计(2026-07-28 核心)
| 变化 | 说明 |
|---|---|
移除 session 与 Mcp-Session-Id |
请求可由任意 Server 实例处理,适配无状态部署 |
移除 initialize/initialized 握手 |
版本与能力改为每请求 _meta |
新增 server/discover |
一次性公布 supportedVersions / capabilities / identity |
| 列表端点不再随连接变化 | tools/list 不因连接而异,但可因授权凭据而异 |
无状态的直接要求:跨调用的状态必须由 Server 显式返回一个 handle(如 basket_id),并在后续调用中由客户端作为普通参数传回。协议本身没有「状态句柄」概念——从线缆视角看,它只是一个普通字符串。
4.8、MRTR:多轮往返请求
旧版允许 Server 通过 SSE 流主动发请求(sampling/createMessage、elicitation/create、roots/list)。2026-07-28 改为:
- Server 在响应里返回
InputRequiredResult(resultType: "input_required"),inputRequests字段描述所需输入; - Client 补齐后,用新的 id 重放原请求,并在
params.inputResponses中带回答案(可带requestState透传服务端状态)。
Client ── tools/call(id:1) ──► Server
Client ◄─ InputRequiredResult(inputRequests: elicitation/create)── Server
Client ── tools/call(id:2, 原参数 + inputResponses) ──► Server
Client ◄─ 最终 result ── Server
重点:MRTR(Multi Round-Trip Requests,SEP-2322)把「服务端反向请求」改造为「客户端主动重试」,从而与无状态模型自洽。
4.9、订阅与通知
2026-07-28 用长生命周期的 POST 响应流 subscriptions/listen 取代旧的 GET 流与 resources/subscribe:
- Client 声明订阅类型(
toolsListChanged、promptsListChanged、resourcesListChanged、resourceSubscriptions); - Server 先回
notifications/subscriptions/acknowledged,随后在该流推送变更通知; - 每条通知带
_meta.io.modelcontextprotocol/subscriptionId关联来源订阅。
注意:请求级通知(
notifications/progress、notifications/message)只走该请求自己的响应流。另外,2026-07-28 移除了 SSE 可恢复性,断流即丢请求。
4.10、列表与缓存
tools/list、prompts/list、resources/list等支持分页(cursor/nextCursor)与缓存;- 2026-07-28 要求列表/读取结果带
ttlMs(新鲜度提示)与cacheScope(public/private,控制共享中间层是否可缓存); - Server SHOULD 稳定排序,便于客户端缓存与 LLM Prompt Cache 命中;
- 工具定义包含
name、title、description、inputSchema(默认 JSON Schema 2020-12)、可选outputSchema、icons、annotations。
工具定义与调用示例:
// tools/list → tools[]
{ "name": "get_weather",
"title": "Weather Information Provider",
"description": "Get current weather information for a location",
"inputSchema": { "type": "object",
"properties": { "location": { "type": "string", "description": "City name or zip code" } },
"required": ["location"] },
"outputSchema": { "type": "object", "properties": { "temperature": { "type": "number" } } } }
// tools/call 成功响应(非结构化文本 + 结构化内容并存)
{ "jsonrpc": "2.0", "id": 2, "result": {
"resultType": "complete",
"content": [{ "type": "text", "text": "{\"temperature\": 22.5, \"conditions\": \"Partly cloudy\"}" }],
"structuredContent": { "temperature": 22.5, "conditions": "Partly cloudy" },
"isError": false
}}
注意:工具名 1~128 字符、区分大小写、仅允许
A-Za-z0-9_-与.,且在单个 Server 内唯一;inputSchemaMUST 是合法 JSON Schema(不能为null);无参数工具推荐{ "type": "object", "additionalProperties": false }。
五、核心 MCP Client / Server 的底层原理
本节回答:抛开框架,MCP 客户端/服务端内部到底怎么跑?
5.1、消息编解码与请求关联
无论是 stdio 还是 HTTP,客户端核心都是一张 id → 等待中的 Future 路由表:
发送请求: id = nextId++; pending.put(id, future); transport.send(jsonRpcRequest)
收到消息: if 有 id 且 有 result/error → pending.remove(id).complete(...)
if 无 id 且 method=notifications/* → 交给通知分发器
以 LangChain4j 为例,McpOperationHandler 内部就是 Map<Long, CompletableFuture<JsonNode>>:收到「带 id 且带 result/error」的消息就完成对应 Future;收到 ping 请求就回 McpPingResponse;收到 notifications/message 就转给日志消费者。
重点:协议是异步请求-响应,但实际使用通常暴露同步方法(
CompletableFuture.get()或阻塞等待),所以「超时」必须在客户端强制实现——这是 MCP 规范明确建议的。
5.2、客户端分层
┌──────────────────────────────────────────────┐
│ Client / Protocol 层 │
│ 版本协商、能力声明、id→Future 路由、超时 │
├──────────────────────────────────────────────┤
│ Transport 层(stdio / Streamable HTTP) │
│ 消息分帧、收发、取消、关闭 │
└──────────────────────────────────────────────┘
关键点:同步/异步的选择、超时与取消、双时代探测与缓存、子进程重启与请求重试。
5.3、传输实现拆解
stdio: 用 ProcessBuilder 启动子进程,两个后台线程分别读 stdout / 写 stdin;消息按行分帧;stderr 仅日志;进程退出时上层可重启并重试丢失请求。
Streamable HTTP: 基于普通 HTTP 客户端,POST 单一 endpoint;根据响应的 Content-Type 决定「JSON 直读」还是「SSE 流式逐条读,最后一条为最终响应」;取消即关闭响应流。
5.4、服务端:tools/list 与 tools/call
服务端要做的事:解析 JSON-RPC → 按 method 分发 → 调用业务实现 → 封装标准响应/错误。要点:
- 声明能力:
{ "capabilities": { "tools": { "listChanged": true } } }(旧版在initialize结果、新版在server/discover结果); tools/list返回工具定义(支持分页/缓存、稳定排序);tools/call执行并返回content/structuredContent/isError;- 严格区分「协议错误(JSON-RPC error)」与「工具执行错误(
isError=true)」。
5.5、参考实现的层次结构
LangChain4j(Java):
McpToolProvider (ToolProvider)
│ 聚合 1..N 个 McpClient,过滤/改名
DefaultMcpClient (McpClient)
│ 管理 JSON-RPC 生命周期(initialize、listTools、callTool)
McpTransport(Stdio / StreamableHttp / WebSocket / Docker)
│ 只负责消息收发
McpToolExecutor实现框架的ToolExecutor,内部调用McpClient.executeTool(...);ToolSpecificationHelper负责 MCPinputSchema↔ 框架ToolSpecification的转换(支持 object/array/enum/anyOf)。
Spring AI(Java):
SyncMcpToolCallback(client, tool)/AsyncMcpToolCallback把 MCP 工具适配成ToolCallback;SyncMcpToolCallbackProvider从多个McpSyncClient聚合工具。
5.6、主流「对接模式」对比
方式 A:框架侧工具适配器(Adapter / Provider)——主流
客户端 SDK 里实现一个「MCP 工具 → 框架工具」的适配层(即 5.5 中的模式)。
- 优点:接入成本低、动态发现、与框架工具系统复用同一套循环;
- 缺点:需要框架侧实现协议客户端与 Schema 映射。
方式 B:MCP 网关 / 注册中心聚合——平台化
用网关(如 AWS Bedrock AgentCore Gateway)或注册中心(MCP Registry)把多个 MCP Server 聚合到统一入口。
- 优点:集中治理、鉴权、限流、可观测;
- 缺点:引入网关运维成本;多一层网络跳数。
方式 C:动态物化为本地工具——取巧
运行时拉取 tools/list,用动态代理或代码生成把 MCP 工具伪装成本地方法。
- 优点:对上层透明;
- 缺点:难以承载复杂 JSON Schema 参数;调试不直观;不推荐长期使用。
方式 D:MCP Server 升级为独立 Runtime / SubAgent
把 Server 当作独立 Agent 运行时,主 Agent 以「任务委派」方式调用(类似 A2A)。
- 优点:适合复杂、有状态、长任务;
- 缺点:重量级,背离 MCP「简单 Server」的初衷。
结论:绝大多数场景选 方式 A,这是投入产出比最高的路线。
5.7、基于 Java 快速搭建 MCP Client / Server
用官方 MCP Java SDK(
io.modelcontextprotocol.sdk)可以在几十行内跑通 Client/Server。要点:SDK 要求 JDK 17+;核心模块mcp内置 STDIO / Streamable HTTP / SSE(legacy) 三种传输;客户端默认用 JDKHttpClient,JSON 默认 Jackson,服务端 core 内置 Servlet 传输;提供同步 / 异步双 API(异步基于 Reactor)。
1)引入依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-bom</artifactId>
<version>2.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
</dependency>
2)Client:连接本地 stdio Server 并调用工具
// 1. 传输:启动本地子进程(例如官方 everything server)
ServerParameters params = ServerParameters.builder("npx")
.args("-y", "@modelcontextprotocol/server-everything", "dir")
.build();
McpTransport transport = new StdioClientTransport(params, McpJsonDefaults.getMapper());
// 2. 同步客户端 + 能力 + 超时
McpSyncClient client = McpClient.sync(transport)
.requestTimeout(Duration.ofSeconds(10))
.capabilities(ClientCapabilities.builder()
.roots(true).sampling().elicitation().build())
.build();
// 3. 初始化(完成版本/能力协商)
InitializeResult init = client.initialize();
String version = init.protocolVersion();
// 4. 发现工具 + 调用工具
ListToolsResult tools = client.listTools();
CallToolResult result = client.callTool(CallToolRequest.builder("calculator")
.arguments(Map.of("operation", "add", "a", 2, "b", 3))
.build());
// 5. 优雅关闭
client.closeGracefully();
连接远程 Server 只需换传输(Streamable HTTP):
McpTransport transport = HttpClientStreamableHttpTransport
.builder("http://your-mcp-server")
.endpoint("/mcp")
.build();
注意:
listTools()/listResources()/listPrompts()都支持分页,需循环直到nextCursor == null才能拿全量工具。
3)Server:暴露一个工具
StdioServerTransportProvider transportProvider =
new StdioServerTransportProvider(McpJsonDefaults.getMapper());
McpSyncServer server = McpServer.sync(transportProvider)
.serverInfo("my-server", "1.0.0")
.capabilities(ServerCapabilities.builder().tools(true).build())
.build();
var calculator = SyncToolSpecification.builder()
.tool(Tool.builder("calculator", inputSchema)
.description("Basic calculator")
.build())
.callHandler((exchange, request) -> {
int a = (int) request.arguments().get("a");
int b = (int) request.arguments().get("b");
return CallToolResult.builder()
.content(List.of(new McpSchema.TextContent("Result: " + (a + b))))
.build();
})
.build();
server.addTool(calculator);
暴露为远程 HTTP 服务时,把传输换成 core 内置的 HttpServletStreamableServerTransportProvider(或 Spring WebMVC / WebFlux 提供者),并把 MCP endpoint 注册到 /mcp。
4)三种传输的 Java 对照
| 传输 | Client 类 | Server 类 | 备注 |
|---|---|---|---|
| STDIO | StdioClientTransport |
StdioServerTransportProvider |
本地子进程 |
| Streamable HTTP | HttpClientStreamableHttpTransport |
HttpServletStreamableServerTransportProvider |
远程;Spring 另有 WebMVC/WebFlux |
| SSE(legacy) | HttpClientSseClientTransport |
HttpServletSseServerTransportProvider |
已弃用,仅兼容旧 Server |
5)更上层的一键方案
- Spring AI:Boot Starter +
@McpTool/@McpToolParam注解 +SyncMcpToolCallback,把 MCP 工具直接适配成 Spring 的工具回调。 - AgentScope Java:
McpClientBuilder+Toolkit.registerMcpClient(...),见第六章。
5.8、Java 生态实现选型
| 方案 | 定位 | 连接 | 工具适配 | 适合 |
|---|---|---|---|---|
| 官方 MCP Java SDK | 协议层参考实现 | McpClient / McpServer |
自行适配 | 需要完全掌控协议细节 |
| Spring AI | Spring 生态集成 | McpSyncClient / Starter |
SyncMcpToolCallback / @McpTool |
Spring Boot 应用 |
| LangChain4j | LLM 应用框架 | McpTransport + DefaultMcpClient |
McpToolExecutor / McpToolProvider |
已用 LangChain4j 的 AI Service |
| AgentScope Java | 多 Agent 框架 | McpClientBuilder |
Toolkit.registerMcpClient |
多 Agent 编排、需要工具治理 |
重点:选型不看「谁更全」,而看「你的 Agent 运行在哪个生态」。协议行为由 SDK 保证,差异主要在工具治理、注解体验与编排能力。
六、对接 Agent 的 MCP 设计流程
本节回答:把一个或多个 MCP Server 接进 Agent 时,应该按什么流程设计?参考 AgentScope(Java) 的实现,可归纳为「连接 → 发现 → 映射 → 执行 → 生命周期 → 安全」六步。
6.1、总体流程
① 连接 Connect 选传输、建 Client、初始化/能力协商、超时
↓
② 发现 Discover tools/list(分页)→ 工具清单
↓
③ 映射 Map MCP Tool → Agent 工具定义(inputSchema 透传 + 命名空间消歧)
↓
④ 执行 Execute Agent 工具循环 → tools/call → content/isError → 回填
↓
⑤ 生命周期 Lifecycle listChanged/subscriptions、连接复用、关闭/重启
↓
⑥ 安全 Governance 审批、白名单、Tool Poisoning、凭据
6.2、两个关键时机:发现期拉取 vs 执行期调用
核心结论:
tools/list(拉取工具)发生在 Agent 启动 / 注册期,进主循环之前;tools/call(调用工具)发生在 每轮 ReAct 的 Act 阶段,模型选中工具之后。二者时机不同、频次也不同。
【启动 / 注册期 · 通常只做一次】
connect → initialize / server.discover(版本 + 能力协商)
└─ tools/list ──► 消歧 · 过滤 · 分组 ──► 注册进 Agent 的“工具表”(缓存)
(此表即后续每轮提供给模型的工具声明)
【每次 run · 每轮对话循环】
loop {
Think:组装请求(把缓存的工具声明序列化进 LLM 的 tools[])→ 调 LLM
├─ 模型未选工具 → 结束本轮
└─ 模型返回 tool_call(s)
Act: 按 name 查“工具表” → tools/call ──► MCP Server 执行
└─ content / structuredContent / isError 写回 memory → 回到 Think
}
【运行期 · 可选】
notifications/tools/list_changed / subscriptions/listen
└─ 重新 tools/list → 刷新工具表(一般安排在轮次之间,避免打断推理)
几个容易混淆的点:
tools/list不是每轮都发:发现一次后缓存;模型每轮请求用的都是这份缓存,不会反复调用协议。- 「把工具声明给模型」≠
tools/list:Think 阶段只是把缓存声明序列化进 LLM 请求的tools[]字段,属于本地数据组装。 tools/call只在模型选中工具时发生:一轮响应可能包含 0 个、1 个或多个 tool_call;Act 阶段逐个(或并行)执行。- 刷新时机:仅当服务端声明
listChanged(或订阅subscriptions/listen)时才需重新发现;刷新安排在轮次之间,避免中途改变工具集导致模型上下文不一致。 - 懒发现变体:少数框架在首次会话 / 首次用到时才拉取,或按分组懒激活子集;但「先发现、后执行」的骨架不变。
时机对照表:
| 动作 | 时机 | 频次 | 触发者 |
|---|---|---|---|
connect / initialize |
Agent 启动 | 一次 | Agent 初始化 |
tools/list(拉取) |
注册期(循环前) | 一次;listChanged 时再拉 |
Agent 初始化 / 变更通知 |
| 工具声明进 LLM 请求 | 每轮 Think | 每轮 | 每次模型调用 |
tools/call(调用) |
每轮 Act | 有 tool_call 时 | 模型选择 + 工具循环 |
close |
Agent 销毁 | 一次 | 生命周期管理 |
6.3、步骤一:连接(Connect)
- 选传输:本地工具用 stdio(子进程),远程用 Streamable HTTP,仅兼容旧服务端才用 SSE。
- 建 Client:声明能力(roots / sampling / elicitation)、设置请求超时与初始化超时。
- 版本兼容:新规范为逐请求
_meta;兼容旧服务端时做双时代探测(见 4.6)。AgentScope 通过protocolVersions(...)显式声明支持的版本:
McpClientWrapper client = McpClientBuilder.create("filesystem-mcp")
.stdioTransport("npx", "-y", "@modelcontextprotocol/server-filesystem", "/tmp")
.timeout(Duration.ofSeconds(120))
.initializationTimeout(Duration.ofSeconds(30))
.protocolVersions("2024-11-05", "2025-03-26", "2025-06-18")
.buildAsync().block();
注意:AgentScope 默认只声明
2024-11-05,连接较新的 Server 必须显式protocolVersions(...),否则会报 "Unsupported protocol version"。HTTP 传输可另加.header("Authorization", "Bearer ...")注入凭据。
6.4、步骤二:工具发现(Discover)
- 用
tools/list拉全量(分页循环);服务端声明listChanged时可订阅变更。 - 命名空间消歧:AgentScope 用
mcp__{server_name}__{tool_name},避免多 Server 重名(例如两个 Server 都有search)。 - 过滤:白名单 / 黑名单(
enableTools/disableTools),做最小暴露。 - 分组:按 Server 或用途分组,按需激活(tool groups),减少一次性塞给模型的工具数量。
Toolkit toolkit = new Toolkit();
toolkit.registerMcpClient(mcpClient).block(); // 全量注册
// 或过滤 + 分组
toolkit.registration().mcpClient(mcpClient)
.enableTools(List.of("read_file", "list_directory"))
.disableTools(List.of("delete_file"))
.group("filesystem").apply();
6.5、步骤三:映射为 Agent 工具——本质是转换成 Function Calling 协议
核心结论:MCP 工具接入 Agent 的本质,就是一次协议转换——把 MCP
tools/list拉到的工具,转换成当前模型 Provider 的 Function Calling 协议(OpenAI 的tools[].function/ Anthropic 的tools[]),随每次 LLM 请求下发给模型选择;模型返回tool_call后,再反向映射回 MCPtools/call。一句话:MCP 管「来源与执行」,Function Calling 管「呈现与选择」。
MCP Server ──tools/list──► 工具清单 Tool{name, description, inputSchema}
│ 适配层:MCP → Function Calling
▼
模型 Function-Calling schema
(OpenAI tools[].function / Anthropic tools[])
│ 随每次 LLM 请求下发
▼
LLM 选择 → tool_call
│ 适配层:反向解析 name + arguments
▼
MCP Server ◄──tools/call── 原始工具名 + arguments
│
CallToolResult ──► tool result 消息 ──► 回灌 LLM
正向映射(MCP Tool → Function Calling):
| MCP Tool(来源) | OpenAI | Anthropic |
|---|---|---|
name |
function.name |
name |
description |
function.description |
description |
inputSchema(JSON Schema 2020-12) |
function.parameters |
input_schema |
outputSchema |
一般忽略(或用于 strict) | 忽略 |
annotations / icons |
部分映射 | 部分映射 |
inputSchema本身就是 JSON Schema,几乎可直接透传为parameters/input_schema——这正是 MCP 与 Function Calling 能低成本对接的关键。
反向映射(模型 tool_call → MCP tools/call):
| 模型返回 | 处理 |
|---|---|
name |
去掉消歧前缀 → 还原成 MCP 原始工具名 |
arguments(OpenAI 是 JSON 字符串;Anthropic input 是 对象) |
归一化成 Map → tools/call 的 arguments |
id / tool_use_id |
保留,用于把结果关联回该次调用 |
结果回灌(MCP CallToolResult → 模型 tool result):
| MCP 返回 | 转成 |
|---|---|
content[](text / image / audio / resource) |
ToolMessage / tool_result block 的内容 |
structuredContent |
结构化输出链路(或序列化进文本) |
isError = true |
标记该 tool result 为错误,交给模型自纠 |
几个容易踩的点:
- 这是格式转换,不是协议翻译:各家的 Function Calling 字段名不同,
inputSchema → parameters / input_schema由 Provider Adapter 负责。 - 每次 LLM 请求都要带工具声明(Chat API 无状态),但
tools/list只拉一次并缓存——两者别混。 - 名字要消歧:多 Server 聚合后,模型看到的
name与 MCP 原始name可能不一致,需维护映射表。 - arguments 形态不一:OpenAI 是 JSON 字符串、Anthropic 是对象,映射时要归一化。
- MCP 不负责「模型选工具」:选择靠模型的 Function Calling 能力,MCP 只保证「工具定义统一 + 执行统一」。
6.6、步骤四:执行与回填(Execute)
- Agent 的 ReAct/Tool 循环选中工具后 → 发起
tools/call(name, arguments)。 - 结果处理三条分流:
content(text / image / audio / resource)→ 拼接为文本回灌模型;structuredContent→ 透传给支持结构化输出的链路;isError = true→ 作为「工具执行错误」回给模型自纠(不要抛异常中断对话);- JSON-RPC
error(协议错误)→ 走框架错误处理链路。
Agent tool loop
│ 选中工具 github__search_repos
▼
MCP callTool("search_repos", args)
▼
result.content / structuredContent / isError
▼
回填对话,模型继续下一轮
6.7、步骤五:生命周期与热更新(Lifecycle)
- 连接复用:一个 Client 长连接服务整个 Agent 生命周期。
- 热更新:
notifications/tools/list_changed→ 重新tools/list;2026-07-28 用subscriptions/listen订阅变更。 - 关闭:stdio 关 stdin → 等待 → 强杀;HTTP 关闭连接。
- 容错:stdio 子进程崩溃自动重启并重试;HTTP 超时重试。
6.8、步骤六:安全与治理(Governance)
- Human-in-the-loop:敏感工具调用前审批(AgentScope 有 Permission / Hook 体系,其他框架可用中间件实现)。
- 最小暴露:只注册必要的工具(白名单优先)。
- Tool Poisoning:工具描述与注解一律作为不可信输入。
- 凭据:HTTP 传输用 OAuth / API Key(
header(...)),stdio 用环境变量。
6.9、参考实现对比
| 框架 | 连接 | 发现 / 注册 | 执行适配 | 特色 |
|---|---|---|---|---|
| AgentScope Java | McpClientBuilder(StdIO / SSE / StreamableHTTP) |
Toolkit.registerMcpClient |
Toolkit 统一工具表 | 工具过滤 / 分组、Higress 网关语义检索、elicitation 回调 |
| LangChain4j | McpTransport + DefaultMcpClient |
McpToolProvider |
McpToolExecutor |
与 AiServices / ToolProvider 对齐 |
| Spring AI | McpSyncClient / McpAsyncClient |
SyncMcpToolCallbackProvider |
SyncMcpToolCallback |
Boot Starter、@McpTool 注解、Security 集成 |
重点:无论用哪个框架,Agent 接入 MCP 的骨架都是一致的——「Client 连接 → tools/list 发现 → 工具适配 → tools/call 执行 → 生命周期与安全治理」。差别只在 API 形态与治理能力。
6.10、底层调用全过程(逐帧详解)
本节把「MCP 接入 Agent」拆到协议帧级别:从 Agent 启动连接,到工具发现、格式转换、模型选择、工具执行、结果回灌、循环收敛、刷新与关闭,每一步都给出真实的 JSON / 调用片段。示例统一使用:Server 别名
weather,MCP 原始工具名get_weather,暴露给模型的工具名weather__get_weather。
全景时序(一次完整 run):
[启动期]
0. 装配 MCP Client(传输 + 能力 + 超时)
1. 建立连接(stdio 起子进程 / HTTP 建连接)
2. 初始化:server/discover(现代)或 initialize(传统)
3. tools/list(分页拉全量)
4. 消歧/过滤/分组 → 写入 ToolRegistry,并建映射表
llmName(weather__get_weather) ⇄ (server=weather, mcpName=get_weather, executor)
[每次 run]
loop {
5. Think:组装 ChatRequest(tools = 缓存的声明转 provider 格式)→ 调 LLM
6. LLM 返回:text 或 tool_calls[]
7. 若有 tool_calls → Act:
a. 用 llmName 查映射表得到 (mcpName, executor)
b. arguments 归一化成 Map
c. before-tool 中间件(审批/日志)
d. executor → tools/call(JSON-RPC)→ MCP Server 执行
e. result(content/structuredContent/isError) → tool result 消息(关联 call id)
f. after-tool 中间件
g. 写回 memory
8. 回到 5(带 tool results)
9. LLM 不再返回 tool_calls → 输出最终答案 → run 结束
}
[运行期] 10. list_changed / subscriptions → 重新 tools/list → 刷新注册表
[销毁期] 11. close:stdio 关 stdin→等待→强杀;HTTP 关闭连接
帧 0-1:装配与连接
- 选传输:本地用 stdio(
ProcessBuilder起子进程),远程用 Streamable HTTP。 - 装配 Client:声明能力(roots / sampling / elicitation)、请求超时、初始化超时。
帧 2:初始化(现代版 server/discover)
// → 请求(stdio:一整行 JSON)
{
"jsonrpc": "2.0",
"id": "discover-1",
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": { "name": "MyAgent", "version": "1.0.0" },
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
// ← 响应
{
"jsonrpc": "2.0",
"id": "discover-1",
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": { "tools": { "listChanged": true } },
"_meta": { "io.modelcontextprotocol/serverInfo": { "name": "weather-server", "version": "2.1.0" } }
}
}
传统版(≤ 2025-11-25):先
initialize请求/响应协商版本与能力,再补一条notifications/initialized。现代版无握手,能力随每个请求的_meta携带。
帧 3:工具发现(tools/list)
// → 请求
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
// ← 响应
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a city",
"inputSchema": {
"type": "object",
"properties": { "location": { "type": "string", "description": "City name" } },
"required": ["location"]
}
}
],
"nextCursor": null,
"ttlMs": 300000,
"cacheScope": "public"
}
}
注意:
nextCursor非空时要继续拉下一页;ttlMs/cacheScope是缓存提示。拉完后进入注册阶段。
帧 4:注册与映射(本地内存结构)
ToolRegistry(缓存,供每轮 Think 复用)
┌────────────────────────┬──────────┬──────────────┬─────────────────────────┐
│ llmName │ server │ mcpName │ executor │
├────────────────────────┼──────────┼──────────────┼─────────────────────────┤
│ weather__get_weather │ weather │ get_weather │ McpToolExecutor(#1) │
└────────────────────────┴──────────┴──────────────┴─────────────────────────┘
llmName:加 Server 前缀消歧后、暴露给模型的名字。mcpName:MCP Server 上的原始工具名,tools/call时使用。executor:绑定该 Server Client 的执行器。
帧 5:Think(组装 LLM 请求,转换成 Function Calling)
把注册表里的声明转换成 Provider 的 Function Calling 格式,随请求下发(这一步不是 tools/list,是本地序列化):
{
"model": "gpt-x",
"messages": [
{ "role": "system", "content": "You are a helpful assistant." },
{ "role": "user", "content": "杭州今天天气怎么样?" }
],
"tools": [
{
"type": "function",
"function": {
"name": "weather__get_weather",
"description": "Get current weather for a city",
"parameters": {
"type": "object",
"properties": { "location": { "type": "string", "description": "City name" } },
"required": ["location"]
}
}
}
]
}
帧 6:LLM 选择工具(返回 tool_call)
{
"id": "chatcmpl-1",
"choices": [
{
"finish_reason": "tool_calls",
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_abc",
"type": "function",
"function": { "name": "weather__get_weather", "arguments": "{\"location\":\"Hangzhou\"}" }
}
]
}
}
]
}
帧 7:Act(反向映射 → tools/call)
- 用
llmName = weather__get_weather查表 →(server=weather, mcpName=get_weather, executor)。 - 解析
arguments(OpenAI 是 JSON 字符串)→{"location":"Hangzhou"}。 - 触发 before-tool 中间件(审批 / 日志 / 埋点)。
// → tools/call
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "Hangzhou" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
// ← 响应
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"resultType": "complete",
"content": [{ "type": "text", "text": "Hangzhou: 26°C, sunny" }],
"structuredContent": { "tempC": 26, "condition": "sunny" },
"isError": false
}
}
HTTP 传输下,同一请求还会带上头:
MCP-Protocol-Version、Mcp-Method: tools/call、Mcp-Name: get_weather。
帧 8:结果回灌(MCP result → tool result 消息)
{ "role": "tool", "tool_call_id": "call_abc", "content": "Hangzhou: 26°C, sunny" }
content[]拼接为文本;structuredContent可走结构化链路。isError=true→ 作为「工具执行错误」回给模型自纠;JSON-RPCerror→ 走协议错误链路。- 触发 after-tool 中间件,写完 memory 后回到帧 5。
帧 9:再次 Think 并收敛
第二次 LLM 请求带上 tool result;模型不再返回 tool_calls,直接输出最终答案,run 结束。若一轮响应里有多个 tool_call,则逐个(或并行)执行帧 7-8,再合并回灌。
帧 10-11:运行期刷新与关闭
运行期(可选):
← notifications/tools/list_changed (Server 声明 listChanged 时)
→ tools/list(重新拉取)→ 更新 ToolRegistry(安排在轮次之间)
销毁期:
stdio:关闭 stdin → 等待进程退出 → 超时 SIGTERM → 再超时 SIGKILL
HTTP :关闭连接 / 连接池
底层状态与数据结构一览:
| 结构 | 作用 |
|---|---|
pending: id → Future |
JSON-RPC 请求/响应关联 |
ToolRegistry |
llmName → (server, mcpName, executor, schema) 映射与缓存 |
messages / memory |
system + 历史 + tool result,逐轮累积 |
capabilities |
双方能力,决定可用特性(tools/resources/sampling…) |
protocolVersions |
支持的版本集合,用于协商与回退 |
分支与异常:
- 协议错误 vs 工具执行错误:前者走 JSON-RPC
error,后者走result.isError=true,后者应回给模型自纠。 - 版本不匹配:
UnsupportedProtocolVersionError(-32022)→ 从supported选版本重试;旧 Server → 回退initialize。 - 超时 / 取消:客户端强制超时;stdio 发
notifications/cancelled,HTTP 关闭响应流。 - 并行 tool_calls:一轮可含多个调用,需按
id分别回灌,顺序不影响关联。 - 列表变更:
list_changed触发重拉,刷新注册表与映射。
三方时序图(Agent/Host ↔ MCP Client ↔ MCP Server ↔ LLM):
sequenceDiagram
participant H as Agent/Host
participant C as MCP Client
participant S as MCP Server
participant M as LLM
Note over H,C: 启动期(一次)
C->>S: server/discover(或 initialize)
S-->>C: supportedVersions + capabilities
C->>S: tools/list
S-->>C: tools[]
Note over H: 消歧/过滤/分组 → ToolRegistry
loop 每轮对话
H->>M: ChatRequest(messages + tools(Function Calling 格式))
M-->>H: tool_calls[]
H->>C: 反向映射(llmName → server + mcpName)
C->>S: tools/call(name, arguments)
S-->>C: content / structuredContent / isError
H->>M: tool result(关联 call id)
M-->>H: 最终答案 或 继续调用工具
end
Note over C,S: 运行期(可选)list_changed → 重新 tools/list
补充帧 A:传统版初始化(≤ 2025-11-25)
现代版无握手;连接旧 Server 时走 initialize 握手:
// → initialize
{ "jsonrpc": "2.0", "id": 1, "method": "initialize",
"params": { "protocolVersion": "2025-06-18",
"capabilities": { "roots": { "listChanged": true } },
"clientInfo": { "name": "MyAgent", "version": "1.0.0" } } }
// ← 响应
{ "jsonrpc": "2.0", "id": 1,
"result": { "protocolVersion": "2025-06-18",
"capabilities": { "tools": { "listChanged": true } },
"serverInfo": { "name": "weather-server", "version": "2.1.0" } } }
// → 客户端就绪通知(无 id)
{ "jsonrpc": "2.0", "method": "notifications/initialized" }
补充帧 B:HTTP + SSE 流式响应(长耗时工具)
Streamable HTTP 下,客户端 POST 单个请求;服务端可能返回请求级 SSE 流,先推进度、最后一条才是最终响应:
POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather
{ "jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": { "name": "get_weather", "arguments": { "location": "Hangzhou" },
"_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {} } } }
HTTP/1.1 200 OK
Content-Type: text/event-stream
X-Accel-Buffering: no
event: message
data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"p1","progress":1,"total":3}}
event: message
data: {"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"p1","progress":2,"total":3}}
event: message
data: {"jsonrpc":"2.0","id":3,"result":{"resultType":"complete","content":[{"type":"text","text":"Hangzhou: 26°C, sunny"}],"isError":false}}
最后一条 SSE 事件是最终响应,随后流关闭。请求级通知(
notifications/progress)只在该请求的响应流上出现。
补充帧 C:并行多工具调用
一轮 LLM 响应可返回多个 tool_call;Agent 逐个(或并行)执行,再按 id 关联回灌多条 tool result:
// ← LLM 返回两个工具调用
{ "role": "assistant", "tool_calls": [
{ "id": "call_a", "type": "function", "function": { "name": "weather__get_weather", "arguments": "{\"location\":\"Hangzhou\"}" } },
{ "id": "call_b", "type": "function", "function": { "name": "time__now", "arguments": "{}" } }
] }
// → 两次 tools/call(可并行),id 分别为 3、4
{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "get_weather", "arguments": { "location": "Hangzhou" } } }
{ "jsonrpc": "2.0", "id": 4, "method": "tools/call", "params": { "name": "now", "arguments": {} } }
// ← 两条 tool result,按 call id 关联
{ "role": "tool", "tool_call_id": "call_a", "content": "Hangzhou: 26°C, sunny" }
{ "role": "tool", "tool_call_id": "call_b", "content": "2026-10-05T10:00:00+08:00" }
补充帧 D:MRTR(工具需要额外输入)
当 Server 需要客户端补充输入时,不再主动发请求,而是返回 resultType: "input_required";客户端补齐后用新 id 重放:
// ← 第一次 tools/call 的中间结果
{ "jsonrpc": "2.0", "id": 3, "result": {
"resultType": "input_required",
"inputRequests": {
"login": { "method": "elicitation/create",
"params": { "message": "GitHub username?", "mode": "form",
"requestedSchema": { "type": "object", "properties": { "name": { "type": "string" } }, "required": ["name"] } } }
},
"requestState": "eyJsb2NhdGlvbiI6IkhhbmcgemhvdSJ9"
}}
// → 用新 id=4 重放,并带上 inputResponses + requestState
{ "jsonrpc": "2.0", "id": 4, "method": "tools/call",
"params": { "name": "get_weather", "arguments": { "location": "Hangzhou" },
"inputResponses": { "login": { "action": "accept", "content": { "name": "octocat" } } },
"requestState": "eyJsb2NhdGlvbiI6IkhhbmcgemhvdSJ9" } }
补充帧 E:两类错误帧(务必区分)
// 工具执行错误:走 result.isError —— 回给模型自纠
{ "jsonrpc": "2.0", "id": 3, "result": { "resultType": "complete",
"content": [{ "type": "text", "text": "Invalid location: no such city." }], "isError": true } }
// 协议错误:走 JSON-RPC error —— 交给框架错误处理
{ "jsonrpc": "2.0", "id": 3, "error": { "code": -32602, "message": "Unknown tool: get_weather_x" } }
补充帧 F:取消与超时
// stdio:客户端发取消通知(引用请求 id)
{ "jsonrpc": "2.0", "method": "notifications/cancelled", "params": { "requestId": 3, "reason": "user aborted" } }
HTTP 传输没有取消通知:关闭该请求的响应流即视为取消(2026-07-28 移除 SSE 可恢复性,断流即丢请求,需用新 id 重发)。
补充帧 G:分页拉取
// 第一页
{ "jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {} }
// ← result 中 nextCursor = "c1"
// 第二页
{ "jsonrpc": "2.0", "id": 5, "method": "tools/list", "params": { "cursor": "c1" } }
// ← result 中 nextCursor = null → 结束
内部机制:pending 路由与超时
pending: Map<id, Future>
发送 tools/call(id=3) → pending["3"] = F3
收到带 id=3 的响应 → pending.remove("3").complete(result)
超时 → F3 失败;pending.remove("3")(stdio 另发 notifications/cancelled)
重点:协议是异步的,
id是唯一关联键;客户端必须在Future上加超时(收到notifications/progress可重置时钟,但仍需保留最大超时)。
完整一轮「协议帧流水」(浓缩总览)
| 序号 | 方向 | 帧 / 动作 | 关键字段 |
|---|---|---|---|
| 1 | C→S | server/discover |
_meta.protocolVersion / clientCapabilities |
| 2 | S→C | discover 结果 | supportedVersions / capabilities |
| 3 | C→S | tools/list |
cursor |
| 4 | S→C | 工具清单 | tools[].inputSchema / nextCursor |
| 5 | 本地 | 注册 + 映射 + 过滤/分组 | llmName ⇄ (server, mcpName) |
| 6 | H→M | ChatRequest | tools[](Function Calling 格式) |
| 7 | M→H | tool_calls[] |
id / name / arguments |
| 8 | C→S | tools/call |
name / arguments / Mcp-Name 头 |
| 9 | S→C | 结果 | content / structuredContent / isError |
| 10 | H→M | tool result | tool_call_id / content |
| 11 | M→H | 最终答案 | 无 tool_calls → 结束 |
| 12 | S→C | (运行期)list_changed |
触发重拉 tools/list |
6.11、设计检查清单
- 传输是否匹配部署形态(本地 stdio / 远程 HTTP)?
- 是否处理了协议版本兼容(双时代)?
- 三类超时是否设置(请求 / 初始化 / 空闲)?
- 工具名是否做了命名空间消歧?
- 是否做了工具白名单 / 最小暴露?
-
isError是否正确回给模型自纠? - 是否监听工具列表变更并热更新?
- 是否有人类审批与审计日志?
- 连接是否随 Agent 生命周期正确关闭 / 重启?
七、安全与授权设计
7.1、授权演进(规范)
- 2025-03-26:引入 OAuth 2.1 授权框架;
- 2025-06-18:MCP Server 定位为 OAuth Resource Server,要求 Resource Indicators(RFC 8707),防止 Token 被复用;
- 2025-11-25:支持 OpenID Connect Discovery 1.0、增量授权范围同意(
WWW-Authenticate)、推荐 Client ID Metadata Documents; - 2026-07-28:进一步对齐 OAuth 2.0 / OIDC 部署实践,弃用动态客户端注册(RFC 7591),改推 Client ID Metadata Documents;要求校验
iss(RFC 9207)、按 issuer 隔离凭据。
注意:授权仅适用于 HTTP 类传输;stdio 传输 SHOULD NOT 使用该框架,而应从环境变量获取凭据。
7.2、传输安全
Streamable HTTP 的硬性要求:
- 所有入站连接 MUST 校验
Origin,非法则403(防 DNS rebinding); - 本地运行 SHOULD 只绑定
127.0.0.1,不要0.0.0.0; - SHOULD 实现认证;
- 服务端 MUST 校验镜像头与请求体一致(不一致返回
400+HeaderMismatch)。
7.3、工具安全
- Human-in-the-loop:工具由模型控制,SHOULD 始终保留人类拒绝调用的能力;应用应明确展示暴露了哪些工具、在调用时给出视觉提示、对敏感操作弹确认;
- Tool Poisoning:工具注解/描述必须当作不可信输入;
- 输入/输出安全:Server MUST 校验所有输入、做访问控制、限流、清理输出;Client SHOULD 在传给 LLM 前校验结果、设置超时、记录审计;
x-mcp-header:仅用于路由,不得标记敏感字段;- 图标安全:图标 URI 只允许 HTTPS 或
data:,拒绝javascript:/file:等;不经凭据抓取;校验 MIME 与内容(防 SVG 内嵌脚本)。
八、扩展机制与治理
8.1、扩展框架
2026-07-28 在 ClientCapabilities / ServerCapabilities 中新增 extensions 字段:
- 标识符使用反向 DNS 前缀(如
io.modelcontextprotocol/ui); - 每个扩展自带设置对象与独立版本;
- 一方支持、另一方不支持时,支持方 MUST 回退核心行为或明确拒绝。
8.2、MCP Apps
2026-01-26 作为首个官方扩展发布,让 Server 能在沙箱 iframe 中渲染交互式 UI(表格、表单等),标识为 io.modelcontextprotocol/ui。
8.3、Tasks 扩展
把实验性任务从核心协议迁出为官方扩展 io.modelcontextprotocol/tasks,用 tasks/get 轮询 + tasks/update 补充输入,取代阻塞式 tasks/result。
8.4、弃用策略
2026-07-28 建立正式的特性生命周期与弃用策略:Active / Deprecated / Removed 三态,弃用窗口至少 12 个月,并维护弃用特性登记表。当前被弃用者:
- Roots、Sampling、Logging(迁移方向:工具参数/资源 URI/配置传递目录;直接用 Provider API;
stderr或 OpenTelemetry); - HTTP+SSE 传输(迁移到 Streamable HTTP);
includeContext的"thisServer"/"allServers";- OAuth Dynamic Client Registration。
九、设计权衡与对比
9.1、传输设计权衡:stdio vs Streamable HTTP
| 维度 | stdio | Streamable HTTP |
|---|---|---|
| 部署 | 本地子进程 | 远程服务 |
| 分帧 | 换行分隔 JSON | HTTP POST,响应 JSON 或 SSE |
| 状态 | 无(2026-07-28) | 无(2026-07-28 移除 session) |
| 鉴权 | 环境变量 | OAuth 2.1 / OIDC |
| 取消 | notifications/cancelled |
关闭响应流 |
| 适用 | 本地工具、桌面宿主 | 云部署、多租户、负载均衡 |
9.2、有状态 vs 无状态
旧版用 session 提供「连接内上下文」,实现简单但与水平扩展、负载均衡冲突;2026-07-28 选择无状态,把状态显式化(handle 作为普通参数),换取云原生友好。代价是每次请求都要携带元数据、跨调用状态需应用自管。
9.3、服务端反向请求 vs MRTR
旧版服务端可主动发请求(sampling/elicitation/roots),依赖双向流与有状态连接;MRTR 把它改成「客户端重试 + inputResponses」,使交互符合「客户端只发请求」的单向模型,是去状态化的关键配套设计。
9.4、MCP vs Function Calling / LSP / A2A
| 对比 | 关系 |
|---|---|
| Function Calling | MCP 不替代它,而是把工具声明与执行标准化;模型侧仍走各家的 function calling |
| LSP | 架构同源(Host/Client/Server、JSON-RPC);MCP 是 LSP 思路在 AI 能力领域的迁移 |
| A2A | MCP 解决「AI ↔ 工具」,A2A 解决「Agent ↔ Agent」;二者互补,可组合 |
9.5、常见设计陷阱
- 命名冲突:聚合多个 Server 时工具名可能重名,必须前缀消歧;不要依赖
serverInfo.name(可自报、不校验、不保证唯一)。 - 版本碎片化:2025 系与 2026 系并存,客户端必须双时代兼容。
- SSE 不可恢复:2026-07-28 移除
Last-Event-ID,断流必须整请求重发。 - 无状态不等于无状态数据:跨调用状态要靠工具自己返回显式 handle,Server 不能依赖连接状态。
- stdio 日志污染:Server 绝不能往
stdout写非 MCP 消息,日志走stderr。 - 超时缺失:无状态协议下,客户端必须强制超时,否则线程可能永久挂起。
- 能力越界:Server MUST NOT 依赖客户端未声明的能力,否则应返回
-32021。
十、总结与展望
- MCP 用 JSON-RPC 2.0 + 三大原语 + 标准传输,把「AI 应用 × 外部能力」的 M×N 集成成本降为 M+N,设计范式承袭 LSP。
- 版本演进体现了一条清晰主线:从「有状态连接」走向「无状态请求」——
2025-03-26换传输、2025-06-18强化结构与安全、2025-11-25完善授权与生态、2026-07-28彻底无状态化并引入 MRTR/扩展框架/弃用策略。 - 当前版本
2026-07-28是范式级修订:请求自描述、server/discover、MRTR、subscriptions/listen、扩展机制与正式弃用策略。 - 一个 MCP Client 的核心是 Transport + 协议层(id→Future 路由、超时、取消)+ 工具适配;一个 Server 的核心是 method 分发 + 能力声明 + 错误分流。
- 治理上,MCP 已从单一厂商开源项目演进为 Linux 基金会 AAIF 下的中立标准;后续可关注扩展生态(MCP Apps / Tasks)、注册中心(MCP Registry)与授权加固的落地。
参考资料
官方入口:规范文档 modelcontextprotocol.io | 官方博客 blog.modelcontextprotocol.io | 官方代码 github.com/modelcontextprotocol | 官方注册中心 registry.modelcontextprotocol.io
一、官方规范(当前版本 2026-07-28)
[1]. MCP 官方规范(2026-07-28)
[2]. MCP 文档索引 llms.txt
[4]. MCP 基础协议与消息模型 Basic
[5]. MCP 版本与兼容性 Versioning and Compatibility
[6]. MCP 2026-07-28 变更日志 Key Changes
[7]. MCP 完整 Schema(schema.json / schema.ts)
1.1、传输(Transports)
[9]. MCP 传输总览 Transports
[10]. MCP stdio 传输
[11]. MCP Streamable HTTP 传输
1.2、服务端特性(Server Features)
[12]. MCP Tools
[13]. MCP Resources
[14]. MCP Prompts
[15]. MCP Discovery(server/discover)
1.3、消息模式与授权(Patterns & Authorization)
[16]. MCP 消息模式总览 Patterns
[17]. MCP 多轮往返请求 MRTR
[18]. MCP 订阅与通知 Subscriptions(subscriptions/listen)
[19]. MCP 授权 Authorization
二、历史版本规范
[20]. MCP 2025-11-25 规范 | 变更日志
[21]. MCP 2025-06-18 规范 | 变更日志 | 生命周期(initialize 握手)
[22]. MCP 2025-03-26 规范 | 变更日志
[23]. MCP 2024-11-05 初版规范
三、官方博客(blog.modelcontextprotocol.io)
[24]. MCP 官方博客首页
[25]. The 2026-07-28 Specification(正式版发布)
[26]. The 2026-07-28 MCP Specification Release Candidate
[27]. Beta SDKs for the 2026-07-28 MCP Spec RC
[28]. MCP Apps - Bringing UI Capabilities To MCP Clients
[29]. One Year of Model Context Protocol(2025-11-25 一周年)
[30]. Introducing the MCP Registry(2025-09-08 预览)
[31]. Announcing the official PHP SDK for MCP(2025-09-05)
四、官方仓库与 SDK(github.com/modelcontextprotocol)
[32]. MCP GitHub 组织
[33]. 规范与文档仓库 modelcontextprotocol
[34]. 文档仓库 docs
[35]. 参考 Server 仓库 servers
[36]. 调试工具 MCP Inspector
[37]. 注册中心 registry | 官方 Registry 站点
[38]. 一致性测试 conformance
[39]. 官方 SDK:Python | TypeScript | Java | C# | Go | Kotlin | PHP | Ruby | Rust | Swift
[40]. 官方扩展:MCP Apps(ext-apps) | Tasks(ext-tasks) | Auth(ext-auth)
五、Anthropic 公告
[41]. Introducing the Model Context Protocol(2024-11-25 开源发布)
[42]. Donating the Model Context Protocol and establishing the Agentic AI Foundation(2025-12-09)
六、相关标准与规范
[43]. JSON-RPC 2.0 规范
[44]. Language Server Protocol(LSP,MCP 的灵感来源)
[45]. JSON Schema 2020-12
[46]. OAuth 2.1(IETF draft)
[47]. RFC 8707 Resource Indicators for OAuth 2.0
[48]. RFC 9207 OAuth 2.0 Authorization Server Issuer Identification
[49]. RFC 7591 OAuth 2.0 Dynamic Client Registration(已被 MCP 弃用)
[50]. OpenID Connect Discovery 1.0
七、延伸阅读(第三方)
[51]. Model Context Protocol Specification Version Timeline(版本演进时间线)
[52]. LangChain4j MCP 官方教程
[53]. Spring AI MCP Utilities
八、Java 构建与 Agent 接入参考
[54]. MCP Java SDK 官方文档
[55]. MCP Java SDK Quickstart(依赖 / BOM)
[56]. MCP Java SDK Client
[57]. MCP Java SDK Server
[58]. AgentScope Java MCP 文档
[59]. AgentScope Java(GitHub)
[60]. AgentScope Python MCP 教程
[61]. Spring AI MCP 总览
[62]. Spring AI MCP Client Boot Starter
整理者:长路 创建时间:2026.10.5 更新时间:2026.10.5
评论区请在客户端页面查看