MCP 协议 2026 新特性与生产实战

一、问题背景:为什么 LLM 与外部工具需要一套协议

在大模型落地工程中,”让模型能调用工具”是 Agent 化的核心命题。2024 年之前,业界普遍的做法是各家框架(LangChain、AutoGen、CrewAI、各家 IDE 插件)各自定义 Tool Calling 格式:OpenAI 用 function_call 的 JSON Schema、Anthropic 用 tool_use 的 XML 嵌套、Google 又换了一套。如果一个 SaaS 想把自己的能力(”查订单”、”发工单”、”读 Jira”)接入到所有客户端,面临的就是经典的 N×M 集成问题——N 个客户端对接 M 个工具,需要写 N×M 套适配器。

MCP(Model Context Protocol)的提出正是为了把这条曲线压平:把”工具”和”上下文”做成一等公民的协议原语,让任何 LLM 应用(Host)通过标准协议与任何能力服务(Server)通信,工具提供方只需要实现一次 Server,即可被所有 Host 调用。这就是 LSP(Language Server Protocol)在 AI 时代的翻版——LSP 让 Neovim、VSCode、Emacs 共享一套语言服务实现,MCP 同样让 Claude Desktop、Cursor、Continue、自研 Agent 共享一套工具服务实现。

MCP 的第一个稳定版(2025-06-18)由 Anthropic 主导开源并联合 OpenAI、Google、Block、Replit 等 30+ 厂商成立治理委员会。2025-11-25 版本加入了 OAuth 2.1 标准化鉴权、Resource Templates、Elicitation 主动询问机制,并把传输层从 HTTP+SSE 重构为 Streamable HTTP。2026 年(截至 06-03)规范在生态治理、UI 扩展、Async Tasks 等方向继续演进。

二、MCP 核心架构:Host / Client / Server 与 JSON-RPC 2.0

MCP 采用经典三层架构,所有通信都基于 JSON-RPC 2.0 协议:

  • Host:发起连接的一方,是 LLM 应用本身(Claude Desktop、Cursor、JetBrains AI Assistant、Spring AI Agent 等)。它管理一个或多个 Client。
  • Client:在 Host 进程内运行,与远端 Server 维持 1:1 长连接。Client 的职责是协议解析、能力协商、消息路由,不做任何业务逻辑
  • Server:暴露 Resources / Prompts / Tools 三类原语的服务进程。它不知道背后接的是 GPT 还是 Claude,只管按 JSON-RPC 协议回应请求。
1
2
3
4
5
6
7
8
9
10
11
┌─────────────────────────────┐
│ Host (LLM App) │
│ ┌─────────┐ ┌─────────┐ │
│ │ Client │ │ Client │ │ ←─ 一个 Host 内可有多个 Client
│ └────┬────┘ └────┬────┘ │
└───────┼────────────┼────────┘
│ JSON-RPC 2.0
┌────▼────┐ ┌────▼────┐
│ Server │ │ Server │ ←─ 多个独立 Server
│ (Files) │ │ (Jira) │
└─────────┘ └─────────┘

底层 JSON-RPC 2.0 选型并非偶然。JSON-RPC 的无状态、单连接多请求、id 关联的特性正好契合 LLM 工具调用的”一次 prompt 触发多次工具调用”的模式。Google 的 A2A 协议选择了 gRPC/Protobuf + HTTP/2,而 MCP 坚守 JSON/JSON-RPC,背后的取舍是 生态兼容优先——任何语言 5 分钟内能写完 SDK,HTTP/JSON 天然穿透反向代理、CDN、API 网关。

三、2026 协议层新特性深度拆解

3.1 Streamable HTTP:彻底重构传输层

2024-11-05 版本的 MCP 使用 HTTP+SSE(Server-Sent Events)做服务端到客户端的推送。这种模式有两个生产痛点:

  1. 长连接吞噬服务器资源:每个 Client 都要独占一条 SSE 长连接,在云原生环境下,K8s Pod 数 = 客户端数,资源水位极高。
  2. 断连重放语义模糊:SSE 自带 Last-Event-ID 但缺乏标准的 resume 协议,断线后状态恢复完全由应用自己处理。
  3. 不友好的中间件生态:Nginx、API Gateway 对 SSE 的超时、缓冲策略默认不友好,常常需要 proxy_buffering offproxy_read_timeout 3600s 之类的魔改。

2025-06-18 规范正式引入 Streamable HTTP 取代 HTTP+SSE,2026 年已经稳定。核心变化:

  • 单端点、双方法:Server 只暴露一个 URL(例 https://api.example.com/mcp),同时支持 POSTGET
  • POST 的两种返回模式:客户端用 Accept: application/json, text/event-stream 表达”我两种都能收”。Server 可以选择:简单请求直接 Content-Type: application/json 一次性返回 JSON;流式响应则升级为 text/event-stream这是 Streamable HTTP 的精髓——流式是可选的,不强制
  • GET 订阅流:客户端可以不带 body 发起 GET 来”订阅”Server 主动推送(通知、异步结果),这是单向 server→client 的通道。
  • 可恢复性(Resumability):Server 在断开前可以发带 id 的 SSE 事件,客户端重连时通过 Last-Event-ID 头 resume。
  • 会话可选:Server 可以决定是否发 Mcp-Session-Id 头启用有状态会话,纯无状态工具(翻译、计算)连会话头都不用发。
1
2
3
4
5
6
7
# 客户端发送带 streaming 能力的请求
curl -X POST https://mcp.example.com/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2025-11-25" \
-H "Authorization: Bearer <token>" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_orders","arguments":{"user_id":42}}}'

Why this design:设计哲学是”HTTP 是一等公民,SSE 是可选加速器”。这让 MCP Server 能直接跑在 Lambda、Cloud Run Workers、Vercel Functions 这类无服务器平台上——以前 SSE 长连接根本跑不起来。

3.2 OAuth 2.1:把鉴权从”魔法字符串”变成”工业级”

2025-11-25 之前,MCP Server 的鉴权是”各显神通”:有的用静态 API Key,有的直接读 HTTP Header,没有任何标准。这导致两个问题:

  1. 用户无法用同一个 Token 在多个 Host 之间漫游(Claude Desktop 不能用企业 SSO 发的 JWT 调内部 Jira MCP Server)。
  2. 工具提供方无法实现细粒度的 Scope 授权(”这个 Token 只能读不能写”)。

2025-11-25 规范把鉴权统一为 OAuth 2.1(基于 IETF draft-ietf-oauth-v2-1) + 三个配套 RFC:

规范作用
RFC 8414Authorization Server Metadata,客户端发现 /metadata 端点
RFC 9728Protected Resource Metadata,MCP Server 声明”我受哪个 AS 保护”
RFC 7591Dynamic Client Registration,让 Host 端能自动注册 OAuth Client
draft-ietf-oauth-client-id-metadata-document用 URL 作为 Client ID,免去注册流程

关键工作流

1
2
3
4
5
6
7
1. Host 调 MCP Server 的任意受保护端点
2. Server 返回 401 + WWW-Authenticate: Bearer realm="...",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"
3. Host 拉取 PRM,发现 authorization_servers
4. Host 拉取 AS Metadata,发现 token_endpoint, authorization_endpoint
5. 走标准 OAuth 2.1 授权码 + PKCE 流程
6. 拿到 access_token 后重试请求,带 Authorization: Bearer <token>

Why this design:不发明新协议,复用 OAuth 2.1 这套已经被 IdP(Okta、Auth0、Keycloak、企业自建 CAS)成熟实现的体系。Client ID Metadata Documents 这个新草案允许 Host 把自己的 logo、policy 托管到一个 URL,Server 直接通过 URL 注册 Client——这是 2026 年的关键 UX 改进。

3.3 UI Extensions:把工具调用从”纯文本”升级为”富交互”

2025 规范里 Server 只能返回 textimage 内容块。2026 年规范加入了 UI Extensions(draft-spec 阶段),允许 Tool 的返回结果携带结构化的 UI 描述:

  • ui://schema:声明返回的是 UI 组件树(类似 React JSON Schema)
  • application/json+ui:MIME type 携带可渲染的 UI payload
  • ui://interactive:声明支持用户点击后回传(例:地图选择器、文件选择器)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [{
"type": "ui_extension",
"mimeType": "application/vnd.mcp.ui+json",
"data": {
"component": "FilePicker",
"props": {
"root": "/data/reports",
"extensions": [".pdf", ".docx"],
"multi": true
}
}
}]
}
}

Host 端的渲染器(Webview、Electron、SwiftUI)按 spec 渲染成原生组件,用户交互后通过 Elicitation 机制把结果回传给 Server。

Why this design:避免 Server 返回 10MB 的 base64 图片或 markdown 表格,让 Host 用原生 UI 组件渲染——既降 token 成本又提交互质量。Mapbox、File Picker、Date Range 这类高频 UI 终于不用每个 Agent 各写一份。

3.4 Resource Templates:从”固定路径”到”参数化资源”

旧规范里 Resource 是固定 URI(如 file:///docs/readme.md)。2025-11-25 加入 ResourceTemplate

1
2
3
4
# 声明一个模板
@mcp.resource("weather://{city}/{date}")
def get_weather(city: str, date: str) -> str:
return fetch_weather(city, date)

Host 端 LLM 可以做”参数绑定”:根据用户问句自动填充 city=Beijing, date=2026-06-03 并发起 resources/read。2026 年进一步支持:

  • 多参数模板db://{schema}/{table}/{id}
  • 可选段docs://{lang}/{path*}lang 可缺省,path* 捕获多级)
  • List 操作的模板感知resources/templates/list 返回可用模板列表,LLM 推理时把模板当作”可调用的能力”。

3.5 Async Tasks 与 Server-Initiated Requests

传统 MCP 是”请求-响应”模型,但很多工具调用是长任务(视频转码、批量 ETL)。2026 规范加入:

  • tasks/create + tasks/result:把工具调用建模为 task,Client 立即拿到 task_id,Server 在后台执行,完成后通过 SSE 或 Webhook 推送结果。
  • Server-Initiated Sampling:Server 在执行长任务时主动调 LLM 推理(例:让 LLM 总结转码结果),这是 2025 规范就有但 2026 加了完整的 Progress Notification 协议——notifications/progress 携带 progressTokenprogress 数值(0-100)。
1
2
3
4
// Server 推送进度
{"jsonrpc":"2.0","method":"notifications/progress",
"params":{"progressToken":"task-abc","progress":47,
"total":100,"message":"Encoding frame 4712/10000"}}

四、Python 实战:官方 SDK 1.x 写文件系统 MCP Server

为什么用文件系统做示例:足够简单、能覆盖 Resources / Tools / Prompts 三大原语、不依赖外部服务,方便复制即跑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# file_mcp_server.py
# 运行:uv run --with "mcp[cli]" mcp run file_mcp_server.py
# 测试:mcp inspector(可视化调试 UI)
from mcp.server.fastmcp import FastMCP, ResourceTemplate
from pathlib import Path
import os

# 构造 Server 实例。host="0.0.0.0" + transport="streamable-http" 启用 2026 推荐的传输
mcp = FastMCP(
name="FileMCP",
instructions="提供受限的文件系统访问能力",
host="127.0.0.1", # 生产环境务必绑 127.0.0.1 防 DNS rebinding
port=8765,
)

# 业务配置:白名单根目录,路径穿越防御的关键
ALLOWED_ROOTS = [Path(os.environ.get("MCP_FS_ROOT", "/tmp/mcp_fs")).resolve()]

def safe_resolve(path: str) -> Path:
"""解析路径并校验是否在白名单内。"""
p = Path(path).resolve()
if not any(p.is_relative_to(root) for root in ALLOWED_ROOTS):
raise PermissionError(f"路径 {path} 不在白名单内")
return p

# ============ Resource:可被 LLM 读取的上下文数据 ============
@mcp.resource("file://{path*}")
def read_file(path: str) -> str:
"""读取文件内容。path* 捕获多级路径。"""
p = safe_resolve(path)
return p.read_text(encoding="utf-8")

# ============ ResourceTemplate:参数化资源 ============
@mcp.resource("stats://{path*}")
def file_stats(path: str) -> dict:
"""获取文件元信息。"""
p = safe_resolve(path)
return {
"size": p.stat().st_size,
"mtime": p.stat().st_mtime,
"is_dir": p.is_dir(),
}

# ============ Tool:可被 LLM 调用的函数 ============
@mcp.tool()
def write_file(path: str, content: str) -> str:
"""写入文件。path 必须在白名单内。"""
p = safe_resolve(path)
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(content, encoding="utf-8")
return f"已写入 {len(content)} 字符到 {p}"

@mcp.tool()
async def long_search(keyword: str, directory: str) -> str:
"""异步搜索关键字。演示 async tool 用法。"""
p = safe_resolve(directory)
matches = []
# 真实场景应使用 asyncio.to_thread 避免阻塞 event loop
for f in p.rglob("*.txt"):
if keyword in f.read_text(encoding="utf-8", errors="ignore"):
matches.append(str(f))
return f"找到 {len(matches)} 个匹配: {matches[:10]}"

# ============ Prompt:模板化提示词 ============
@mcp.prompt()
def summarize(path: str) -> str:
"""生成文件摘要的提示词模板。"""
return f"请阅读 file://{path} 并用 100 字内总结其核心内容。"

# ============ 入口 ============
if __name__ == "__main__":
# 2026 推荐使用 streamable-http;stdio 仍保留用于本地桌面客户端
mcp.run(transport="streamable-http")

逐行讲解

  • safe_resolve:所有路径操作的前置守卫。MCP 的安全模型里,Server 自身就是最后一道防线——Host 的沙箱可能并不可信。
  • Resource vs ResourceTemplate vs Tool 的区别:Resource 是被动数据,LLM 主动 read;Tool 是 LLM 主动 invoke 的副作用动作;Prompt 是”提示词模板”,Host 把变量填入后注入到对话。
  • transport="streamable-http":2026 推荐。stdio 仍然保留给 Claude Desktop 这类本机进程集成。
  • 异常处理:MCP SDK 会自动把 Python 异常转成 JSON-RPC error 返回,无需手动包装。

五、Java 实战:Spring AI MCP Starter 1.0+ 企业级实现

为什么用 Spring AI:Spring AI 1.0 GA(2025-Q4)正式把 MCP 纳入第一方支持,提供 spring-ai-starter-mcp-server 自动配置,比手写 JSON-RPC 节省 80% 样板代码。

5.1 Maven 依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
</dependencies>

5.2 业务实现:Jira 工单 MCP Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// JiraTools.java
// 用 @Tool 注解暴露工具方法,Spring AI 自动注册为 MCP Tools
package com.example.mcp.jira;

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.Map;

@Service
public class JiraTools {

private final WebClient jiraClient;
private final AuditLog auditLog; // 自定义审计组件

public JiraTools(WebClient jiraClient, AuditLog auditLog) {
this.jiraClient = jiraClient;
this.auditLog = auditLog;
}

/**
* 查询工单。@ToolParam 描述会作为 JSON Schema 暴露给 LLM。
* description 是给 LLM 看的"何时调用我"的自然语言说明。
*/
@Tool(description = "根据关键字搜索 Jira 工单。返回最多 20 条结果。")
public List<JiraIssue> searchIssues(
@ToolParam(description = "搜索关键字,例如 '登录失败'") String keyword,
@ToolParam(description = "工单状态过滤:open / in_progress / closed,缺省为 open")
String status
) {
String s = (status == null || status.isBlank()) ? "open" : status;
// 审计:所有工具调用必须留痕,满足合规要求
auditLog.record("searchIssues", Map.of("keyword", keyword, "status", s));

// 调 Jira REST API(生产应加 Resilience4j 限流 + 重试)
return jiraClient.get()
.uri(uriBuilder -> uriBuilder.path("/rest/api/3/search")
.queryParam("jql", "text ~ \"{0}\" AND status = {1}", keyword, s)
.queryParam("maxResults", 20)
.build())
.retrieve()
.bodyToFlux(JiraIssue.class)
.collectList()
.block();
}

/**
* 创建工单。返回值会作为 MCP Tool result 序列化为 JSON。
*/
@Tool(description = "创建一条新的 Jira 工单。")
public String createIssue(
@ToolParam(description = "工单标题") String summary,
@ToolParam(description = "工单详情描述") String description,
@ToolParam(description = "工单优先级:1-5,1 最高") int priority
) {
if (priority < 1 || priority > 5) {
// MCP 异常会被 SDK 包装为 JSON-RPC error,LLM 看到的是结构化错误
throw new IllegalArgumentException("priority 必须在 1-5 之间");
}
auditLog.record("createIssue", Map.of(
"summary", summary, "priority", priority));

// ... 实际调用 Jira API
return "Issue CREATED-12345";
}

/**
* 异步工具。返回 Mono 会被 Spring AI 包装为 Async Task。
* 适合耗时 > 5s 的场景。
*/
@Tool(description = "异步批量处理工单,返回任务 ID。")
public Mono<String> batchProcess(
@ToolParam(description = "工单 ID 列表") List<String> issueIds,
@ToolParam(description = "处理动作:close / reassign") String action
) {
return Mono.fromCallable(() -> {
String taskId = "task-" + System.currentTimeMillis();
// 提交到线程池
TaskExecutor.EXECUTOR.submit(() -> doBatch(issueIds, action, taskId));
return taskId;
});
}

private void doBatch(List<String> ids, String action, String taskId) {
// 实际批处理,通过 SSE 推 progress 通知
}
}

5.3 配置:application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring:
ai:
mcp:
server:
# 启用 Streamable HTTP 传输(2026 推荐)
transport: streamable_http
# 注册为 Spring Bean 的 Tool 都会被自动发现
stdio: false
# 端点路径
endpoint: /mcp
security:
oauth2:
resourceserver:
jwt:
# 对接企业 IdP,校验 Bearer Token
issuer-uri: https://idp.example.com/

server:
port: 8080

# 自定义:审计日志
audit:
log-path: /var/log/mcp/audit.log

5.4 Spring Security 集成:保护 MCP 端点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// SecurityConfig.java
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {

@Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http
.authorizeExchange(ex -> ex
// MCP 端点必须认证
.pathMatchers("/mcp/**").authenticated()
// 公开的 OAuth 元数据端点
.pathMatchers("/.well-known/**").permitAll()
.anyExchange().denyAll())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
// CSRF 必须关,Streamable HTTP 客户端不会带 CSRF token
.csrf(csrf -> csrf.disable())
// CORS 配置:限制允许的 Origin
.cors(cors -> cors.configurationSource(corsSource()))
.build();
}

private CorsConfigurationSource corsSource() {
CorsConfiguration cfg = new CorsConfiguration();
cfg.setAllowedOrigins(List.of("https://claude.ai", "https://cursor.sh"));
cfg.setAllowedMethods(List.of("GET", "POST"));
cfg.setAllowedHeaders(List.of("Authorization", "Content-Type",
"MCP-Protocol-Version", "Last-Event-ID"));
UrlBasedCorsConfigurationSource src = new UrlBasedCorsConfigurationSource();
src.registerCorsConfiguration("/mcp/**", cfg);
return src;
}
}

逐行讲解

  • transport: streamable_http:明确启用 2026 推荐传输。Spring AI 默认就是它,但写出来是反”魔法配置”的最佳实践。
  • oauth2ResourceServer.jwt:让 MCP Server 作为 OAuth 2.1 Resource Server 验证 Bearer Token。每个 Tool 调用都会自动经过 Spring Security 过滤器,Token 里的 scope 字段可以映射到方法级权限(用 @PreAuthorize)。
  • csrf.disable():Streamable HTTP 是无状态 API,不需要 CSRF 保护。
  • cors 严格白名单:Streamable HTTP 最大的攻击面是 DNS rebinding + 跨 Origin 攻击,必须把允许的 Host 列清楚。

六、生产实践:鉴权、限流、可观测性、Token 成本

6.1 鉴权分层

  • L1 传输层:mTLS 或 CloudFront/ALB 层的 IP 白名单,挡掉 90% 扫描流量。
  • L2 协议层:OAuth 2.1 Bearer Token,由 IdP 签发。这是 MCP 规范规定的”必备”。
  • L3 应用层:每个 Tool 方法上的 @PreAuthorize("hasAuthority('SCOPE_jira:write')"),把 Scope 映射到方法权限。

6.2 限流

MCP 的限流不能用传统 QPS 思维,因为一个用户的一次 LLM 调用可能触发 5-20 次 Tool Call。应该按 Session 维度 限流:

1
2
3
4
5
6
// Resilience4j 限流配置
RateLimiterConfig config = RateLimiterConfig.custom()
.limitForPeriod(100) // 每个 session 100 次/分钟
.limitRefreshPeriod(Duration.ofMinutes(1))
.timeoutDuration(Duration.ofSeconds(5))
.build();

Mcp-Session-Id 作为限流 key,而非 IP。一个真实用户用一个会话执行 100 次工具调用是合理的;用 100 个 IP 各调 1 次是攻击。

6.3 可观测性

必须打的关键 Span:

  • mcp.request:JSON-RPC 方法名、id、duration
  • mcp.tool.call:Tool 名称、参数摘要(脱敏后)、返回大小
  • mcp.token.usage每个 Tool 的输入输出 token 计数——这是成本黑洞
1
2
3
4
5
6
7
8
9
10
// Micrometer + OpenTelemetry
@Observed(name = "mcp.tool.call", contextualName = "tool")
public String createIssue(...) {
Counter.builder("mcp.tool.calls")
.tag("tool", "createIssue")
.tag("status", "success")
.register(meterRegistry)
.increment();
return ...;
}

把 trace 接到 Jaeger/Tempo,关联到上游 LLM 的 trace——这样一次完整 Agent 调用的成本、延迟、调用链一目了然。

6.4 Token 成本控制

MCP 最大的隐藏成本是把 Tool 返回灌进 LLM 的 context window。三招降本:

  1. 返回截断read_file 类工具默认只返回前 2000 token,超出返回 truncated: true 标志。
  2. 摘要回写:在 Server 端先 LLM 摘要一次再返回(”小模型预处理”模式)。
  3. 结构化输出:让 Tool 返回 JSON 而非自然语言,JSON 在 prompt 里能更紧凑。

七、避坑指南:5 个常见坑

  1. DNS Rebinding 攻击:Streamable HTTP 的致命陷阱。开发时本地 Server 习惯绑 0.0.0.0,生产必须 127.0.0.1(本机)或加严格的 Origin Header 校验。规范明确要求”Origin 头无效必须 403”。

  2. stdio 的 stdout 污染:stdio 模式下 Server 进程 print("debug info") 到 stdout 会破坏 JSON-RPC 帧。所有日志必须走 stderr。Python 用 logging.StreamHandler(sys.stderr),Java 用 logbacktarget=System.err

  3. 会话状态丢失:Streamable HTTP 默认无状态,工具调用间的上下文(如”上一步查到的工单 ID”)不能依赖 Server 内存。要么让 LLM 把状态塞回 prompt,要么改用 Database Session。

  4. Tool 描述质量:LLM 决定调哪个 Tool,靠的就是 @Tool(description=...) 里的自然语言。描述模糊会导致 LLM 永远不调或乱调。规范建议 description 包含:”何时调用 + 输入是什么 + 返回什么 + 一个调用示例”。

  5. OAuth Scope 粒度爆炸:给每个 Tool 一个独立 Scope 会导致用户授权体验极差。正确做法是按”资源域”分组(jira:read / jira:write / db:read),一个域下所有 Tool 共享。

八、思想总结:MCP 之于 Agent 的真正价值

MCP 的成功不只在协议本身,而在它赌对了一个生态命题:Agent 时代的能力复用范式,必须从”私有插件”走向”开放协议”。LSP 让 Neovim 在 10 年前能共享 Clangd 这种重量级语言服务,MCP 让 Claude/Cursor/自研 Agent 在 2026 年能共享任何 SaaS 能力。

对工程师而言,MCP 的真正价值在于 把”集成工作”从项目级降为配置级——以前给一个 Agent 接入 Jira 需要写 200 行胶水代码,现在只需要在 Cursor 的 MCP 配置里加 5 行 JSON。对架构师而言,MCP 给了我们”在 LLM 之上搭中台”的标准化抓手:所有业务能力封装为 MCP Server,前端 LLM 应用不再关心数据在哪、API 怎么调。

2026 年的 MCP 仍在快速演进,下一阶段值得关注的方向是:

  • 多模态 Tool Return:返回原生音视频流而非 base64。
  • 联邦发现:跨组织的 MCP Server 注册表(类似 Maven Central for Tools)。
  • 沙箱执行:让 Server 能直接执行用户提供的代码片段,类似 Jupyter Kernel。

但万变不离其宗——协议先行、生态跟上、商业模式自然涌现。这正是 30 年前 HTTP、20 年前 REST、10 年前 gRPC 走过的路。MCP 正在走同一条路,而我们这代工程师,正站在它从玩具走向生产的关键节点上。


说明:本文基于 2025-11-25 MCP 规范(已落地)以及 2026 年规范草案(Streamable HTTP 稳定化、UI Extensions、Async Tasks)整理,部分 2026 演进方向基于已公开的 SEP(Specification Enhancement Proposal)做合理推断。生产落地时请以 modelcontextprotocol.io 最新规范为准。