
1. MCP 规范升级后Java 服务器到底改了什么MCP 规范升级这件事落到 Java 服务器上最直观的变化是协议核心从「有状态会话」转向「无状态 HTTP」。以前客户端要先initialize、拿到Mcp-Session-Id、后续请求都带着这个会话标识走粘性路由升级到 2026-07-28 之后每个请求自带MCP-Protocol-Version、Mcp-Method、Mcp-Name这些头部负载均衡器可以随便轮询任意实例都能处理响应元数据还能缓存。这对 Java MCP 服务器意味着什么意味着你原来写在会话里的东西——文档句柄、搜索结果、用户上下文——不能再挂在协议层了得挪到应用模型里用显式标识符在客户端和服务器之间传递。同时server/discover取代了initialize握手tools/list的响应可以带ttlMs和cacheScope工具输出支持 JSON Schema 2020-12structuredContent也不再局限于对象类型。我这次拿 Helidon 的 urgency-mcp 做示例它是个把患者投诉转工单、用模型打紧急度分数的服务。场景很典型企业里已经有一批老客户端还在走 2025-06-18 的初始化流程你不能因为服务器升级就把它们全断了。所以这篇的重点不是「怎么把旧代码删掉重写」而是「怎么在路由边界加一层适配让两条协议路径共存」。适合谁看正在维护 Java MCP 服务器、准备跟进新规范、又不想破坏现有集成的后端同学。下面从统一 Key 管理讲起再给可复制的配置骨架和验证动作。2. 用 TaoToken 统一 Key先把鉴权和通道理顺MCP 服务器升级时鉴权是最容易被忽略又最容易出事的一环。老流程里会话建立后后续请求靠Mcp-Session-Id隐式关联身份新流程无状态了每个请求都得自己证明「我是谁、我要调什么」。这时候如果 Key 散落在各个服务的application.yaml、环境变量、CI 配置里迁移会非常痛苦。我的做法是用 TaoToken 把模型调用的 Key 统一收口。它兼容 OpenAI 风格的接口Java 侧只要改base_url和api_key两个值不用动业务代码。官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。具体到 urgency-mcp它的application.yaml里有urgency.provider决定走本地模型还是远程模型。远程那条路径需要 embedding 和评分模型我把远程调用的 Key 统一指向 TaoToken本地路径保持不动。这样切换 provider 时只改一个配置项不用满项目找 Key。注意Key 不要硬编码进application.yaml提交到仓库用环境变量注入配置里写占位符。如果你还没建 Key去控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 生成然后在 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 管理。接入细节看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。这一步做完服务器侧的鉴权就从「会话绑定」变成了「每请求携带」正好对上无状态规范的要求。3. 可复制的 config.toml 与 settings.json 骨架MCP 客户端和服务器两侧的配置格式不一样客户端常用settings.json服务器侧我习惯用config.toml管运行时参数。下面给两份骨架你可以直接抄。先看服务器侧的config.toml重点是协议版本、路径、鉴权和 provider 选择# config.toml - urgency-mcp 服务器运行时配置 [mcp] path /urgency server_name helidon-mcp-urgency # 新规范版本用于无状态路径判定 protocol_version 2026-07-28 # 兼容旧客户端保留 2025 流程 legacy_protocol_version 2025-06-18 stateless true [mcp.cache] # tools/list 的缓存元数据 ttl_ms 300000 cache_scope public [urgency] provider openai # 可选 local / openai [urgency.providers.openai] base_url https://taotoken.net/api api_key ${TAOTOKEN_API_KEY} # 从环境变量注入 model model-scorer-openai.dnet embedding_model text-embedding-3-small embedding_dimensions 1536 [urgency.providers.local] model model-scorer-local.dnet location ../urgency/model embedding_model sentence-transformers/all-MiniLM-L6-v2 embedding_dimensions 384再看客户端侧的settings.json关键是声明协议版本和发现方式{ mcpServers: { urgency: { url: http://localhost:9090/urgency, protocolVersion: 2026-07-28, transport: http, headers: { MCP-Protocol-Version: 2026-07-28, Accept: application/json }, discover: true, cache: { enabled: true, ttlMs: 300000 } } } }discover: true表示客户端启动时走server/discover而不是initialize。如果你的客户端 SDK 还停留在旧版本把protocolVersion改成2025-06-18、discover设为false服务器侧的兼容路径会接住它。依赖这块别忘了Helidon 的 MCP 扩展和注解处理器要单独列dependency groupIdio.helidon.webserver/groupId artifactIdhelidon-webserver/artifactId /dependency dependency groupIdio.helidon.extensions.mcp/groupId artifactIdhelidon4-extensions-mcp-server/artifactId version${mcp.extension.version}/version /dependency注解处理器路径里helidon-bundles-apt管核心依赖helidon4-extensions-mcp-codegen管 MCP 代码生成两个都要加否则Mcp.Tool不会生成对应路由。4. 验证请求新旧两条路径都要跑通配置写完得用真实请求验证。先起服务mvn clean package java --enable-preview -jar target/urgency-mcp.jar默认监听 9090。先测旧客户端路径模拟一个还在用 2025-06-18 的调用方curl -X POST http://localhost:9090/urgency \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -d {jsonrpc:2.0,id:1,method:initialize,params:{protocolVersion:2025-06-18,capabilities:{},clientInfo:{name:readiness,version:1.0.0}}}如果返回里带Mcp-Session-Id说明兼容路径正常老客户端不受影响。再测新规范的无状态路径走server/discovercurl -X POST http://localhost:9090/urgency \ -H Content-Type: application/json \ -H Accept: application/json \ -H MCP-Protocol-Version: 2026-07-28 \ -H Mcp-Method: server/discover \ -d {jsonrpc:2.0,id:1,method:server/discover}预期返回服务器身份、支持的协议版本、工具能力和缓存元数据且不带Mcp-Session-Id。接着测工具列表确认缓存提示生效curl -X POST http://localhost:9090/urgency \ -H Content-Type: application/json \ -H MCP-Protocol-Version: 2026-07-28 \ -H Mcp-Method: tools/list \ -d {jsonrpc:2.0,id:2,method:tools/list}返回里应该能看到ttlMs: 300000和cacheScope: public。最后测工具调用注意Mcp-Name和params.name要一致curl -X POST http://localhost:9090/urgency \ -H Content-Type: application/json \ -H MCP-Protocol-Version: 2026-07-28 \ -H Mcp-Method: tools/call \ -H Mcp-Name: getUrgency \ -d {jsonrpc:2.0,id:3,method:tools/call,params:{name:getUrgency,arguments:{phrase:chest pain and shortness of breath}}}成功的话content里是文本形式的分数structuredContent里是数字分数两条输出同时返回方便不同客户端按需取用。5. 本篇常见错排查迁移过程中踩的坑基本集中在头部校验和路由分支上列几个高频的。报错一Missing MCP-Protocol-Version header。说明请求走了 2026 路径但没带版本头。检查客户端settings.json里的headers是否真的发出去了有些 SDK 会把自定义头吞掉需要显式配置。报错二Mcp-Session-Id is not allowed。这是预期行为——无状态路径主动拒绝会话头。如果你的客户端还在自动附加Mcp-Session-Id要么升级 SDK要么让它走 2025 路径。别去改服务器放宽校验那等于把无状态契约破坏了。报错三Mcp-Method does not match body method。头部写的Mcp-Method和 JSON-RPC body 里的method不一致。网关和负载均衡器靠头部路由body 靠服务器执行两者必须对齐。写个拦截器统一注入别手写。报错四tools/call返回name mismatch。Mcp-Name头和params.name不一致。规范要求两处都写且值相同。封装一个调用工具方法把这两个值绑在一起传。报错五旧客户端突然 404。大概率是路由分支条件写错了。适配层只在「路径匹配 版本头等于 2026-07-28」时才拦截任一条件不满足就chain.proceed()放行给 Helidon 原生路由。检查你的McpRequestLoggingFeature是不是把没有版本头的请求也拦了。报错六缓存不生效。tools/list每次都重新拉。确认响应里带了ttlMs和cacheScope且客户端settings.json里cache.enabled为true。服务端和客户端两边都要开。排查顺序建议先看请求头再看路由分支最后看业务逻辑。协议层的问题九成在头部。6. 后续怎么走把适配层当成过渡不是终点这套方案的核心思路是「路由边界适配」老的 Helidon 注解路径原样保留新的无状态契约在拦截器里单独处理两条路径最终都调用同一个McpUrgencyServer.score()。领域代码一行没动协议升级被隔离在边界层。但要说清楚StatelessMcpProtocolHandler是临时适配器。等 Helidon MCP 扩展原生支持 2026-07-28 契约后这个分支可以缩小甚至删掉而紧急评分逻辑完全不受影响。这就是兼容性优先的价值——升级不等于重写。如果你在做长期编码或 Agent 类项目需要频繁调模型、跑多轮工具调用可以看看 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 配合统一的 Key 管理会省不少事。想先验证模型通不通直接去模型对话 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentchatutm_campaignrewrite 试一条请求最快。最后提醒一句一致性测试套件会随规范演进今天能过的场景明天可能改名或新增。把MCP_CONFORMANCE_SCENARIOS做成可覆盖的参数别写死在脚本里升级时才不会手忙脚乱。