1. 从一次 32600 报错说起Java 自建 MCP 接 Dify 到底卡在哪如果你正在用 Java Spring AI 写自己的 MCP Server然后想把它挂到 Dify 上当成工具用大概率会经历这样一个过程本地mvn spring-boot:run跑得好好的用 curl 打/sse也能看到事件流结果一填进 Dify 的 MCP 配置里点保存就给你来一句Failed to connect to MCP server: code32600 messageSession terminated by server dataNone这个报错信息非常“迷惑”它既不说缺哪个类也不说版本不匹配只告诉你会话被服务端终止了。我一开始以为是网络问题换了端口、关了防火墙、把localhost改成内网 IP全都没用。后来把 Dify 容器日志和 Spring Boot 日志对着看才发现问题出在MCP 协议握手阶段的 JSON-RPC 版本/序列化行为不一致而根因基本都指向同一个东西spring-ai-starter-mcp-server-webmvc的依赖版本。这篇就围绕这个场景展开你已经有或者正准备写一个 Spring AI 的 MCP Server目标是把它接进 Dify 当工具调用同时用 TaoToken 统一管理模型侧的 Key 和 API 通道。我会给出可直接复制的pom.xml版本组合、Dify 侧的 MCP 连接配置片段、启动后验证工具列表的步骤以及 32600 这类报错的排查顺序。适合谁看会写 Spring Boot、对 MCP 概念有基本了解、正在被依赖版本和连接配置折磨的 Java 开发者。不需要你精通 Dify 源码跟着配就行。2. 前置准备MCP Server 骨架与 TaoToken 通道2.1 先确认你的 MCP Server 形态Dify 目前接入 MCP 工具主流方式是SSEServer-Sent Events传输。所以你的 Spring AI MCP Server 必须是 webmvc 或 webflux 的 SSE 版本而不是 stdio 版本。stdio 版本是给本地命令行客户端用的Dify 作为服务端去连你走的是 HTTP SSE。一个最小可用的 MCP Server 需要暴露两个端点端点作用方法/sse建立 SSE 长连接返回 sessionIdGET/mcp/message客户端发送 JSON-RPC 请求POSTSpring AI 的spring-ai-starter-mcp-server-webmvc会自动帮你注册这两个端点你只需要写ToolCallbackProvider把工具方法暴露出去。2.2 TaoToken 在这里的角色MCP Server 本身只负责“工具”但你的工具内部如果要调大模型比如做一个“总结文本”的工具就需要一个模型 API 通道。这时候用 TaoToken 统一管理 Key 比较省事一个 Key 走多个模型切换模型不用改代码只改配置。TaoToken 的 API 地址是https://taotoken.net/api兼容 OpenAI 风格的调用格式。你可以在它的控制台里生成 API Key然后在 Spring AI 的application.yml里把 base-url 指过去。这样 MCP 工具内部调模型、以及你后续在 Dify 里配模型都能走同一条通道Key 不用散落在多个地方。需要先拿到 Key 的话去控制台创建https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面有 base-url 和鉴权头的写法。3. 可复制配置pom.xml 版本组合与 Dify 连接片段3.1 依赖版本这是 32600 的高发区先说结论如果你用的是 Spring AI 1.0.0 正式版附近的 MCP starter和 Dify 的 MCP 客户端握手时很容易触发 32600。换成下面这个版本组合我实测能稳定连上!-- Spring AI 集成 MCP Server (WebMVC / SSE) -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-mcp-server-webmvc/artifactId version1.1.0-M1-PLATFORM-2/version /dependency !-- Spring AI OpenAI 兼容客户端用于走 TaoToken 通道 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-openai/artifactId version1.1.0-M1-PLATFORM-2/version /dependency注意两点第一spring-ai-starter-mcp-server-webmvc和spring-ai-starter-model-openai的版本要一致不要一个 1.0.0 一个 1.1.0-M1混用会导致ToolCallback接口签名对不上编译期就报错。第二如果你在 JFrog 上搜这个 artifact确认仓库里有1.1.0-M1-PLATFORM-2这个版本再写进 pom。版本号里的PLATFORM是平台构建标记和普通 milestone 不是一回事别手写成1.1.0-M1那个可能不存在。3.2 application.ymlMCP Server 与模型通道server: port: 8080 spring: ai: mcp: server: name: my-java-mcp-server version: 1.0.0 sse-endpoint: /sse sse-message-endpoint: /mcp/message openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-miniTAOTOKEN_API_KEY用环境变量注入别硬编码进仓库。base-url结尾不要带/v1Spring AI 的 OpenAI starter 会自己拼路径带了会变成/v1/v1/chat/completions。3.3 工具暴露一个最小 Tool 示例Component public class EchoTools { Tool(description 回显输入文本用于验证 MCP 工具链路是否打通) public String echo(ToolParam(description 要回显的文本) String text) { return echo: text; } }然后在配置类里注册Configuration public class McpConfig { Bean public ToolCallbackProvider toolCallbackProvider(EchoTools echoTools) { return MethodToolCallbackProvider.builder() .toolObjects(echoTools) .build(); } }启动后/sse端点会推送tools/list的响应里面应该能看到echo这个工具。3.4 Dify 侧 MCP 连接配置在 Dify 的“工具”-“MCP”里新增一个 MCP Server配置大致如下{ name: java-mcp-local, transport: sse, url: http://你的服务器IP:8080/sse, headers: {} }如果你的 Dify 跑在 Docker 里而 MCP Server 跑在宿主机上localhost是不通的要写宿主机的内网 IP或者用host.docker.internalLinux 下需要额外加--add-host。这一步踩坑的人特别多连接超时和 32600 有时候就是地址写错导致的。4. 验证请求从工具列表到调用链路4.1 先用 curl 验证 SSE 端点在配 Dify 之前先确认 MCP Server 自己是活的curl -N http://127.0.0.1:8080/sse正常会看到类似输出并且连接保持不关闭event: endpoint data: /mcp/message?sessionId8f3a...拿到sessionId后另开一个终端发 JSON-RPC 请求curl -X POST http://127.0.0.1:8080/mcp/message?sessionId8f3a... \ -H Content-Type: application/json \ -d { jsonrpc: 2.0, id: 1, method: tools/list, params: {} }如果返回里包含echo工具的定义说明 MCP Server 侧没问题。这一步能过Dify 还连不上问题就在 Dify 的网络或配置。4.2 在 Dify 里验证工具列表保存 MCP 配置后Dify 一般会有一个“测试连接”或自动拉取工具列表的动作。成功的话你会在工具列表里看到echo。这时候建一个最简单的 Agent 应用把echo工具挂上去输入“调用 echo 工具文本是 hello”看它能不能返回echo: hello。调用链路是这样的Dify Agent - MCP Client (SSE) - /sse 建立会话 - POST /mcp/message 发 tools/call - Spring AI 执行 EchoTools.echo - 结果原路返回 Dify如果工具列表能拉到但调用时报错重点看 Spring Boot 日志里有没有MethodToolCallback相关的异常通常是参数名对不上或者ToolParam描述缺失。4.3 模型侧走 TaoToken 的验证如果你的工具内部要调模型可以在工具方法里注入ChatClientTool(description 用模型总结一段文本) public String summarize(ToolParam(description 待总结文本) String text) { return chatClient.prompt() .user(请总结 text) .call() .content(); }跑通后模型请求会走https://taotoken.net/api。你可以在 TaoToken 控制台的调用记录里看到这次请求确认 Key 和通道都生效。想先单独验证模型通道是否通可以用模型对话页面发一条测试消息https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite。5. 本篇常见错排查32600 与其他连接问题5.1 code32600 Session terminated by server这是本篇的核心报错。排查顺序按下面来第一确认spring-ai-starter-mcp-server-webmvc版本是1.1.0-M1-PLATFORM-2或更新的平台构建版。旧版本在 JSON-RPC 初始化握手的protocolVersion字段处理和 Dify 客户端不一致服务端会直接终止会话。第二确认没有同时引入spring-ai-starter-mcp-serverstdio 版和 webmvc 版。两个 starter 同时存在时Bean 冲突会导致 SSE 端点注册异常表现也是会话被终止。第三检查server.servlet.context-path。如果你设了 context-pathDify 里的 url 要带上这个前缀否则请求打到错误路径SSE 建立失败。5.2 连接超时 / Connection refusedDify 在 Docker 里、MCP Server 在宿主机localhost指向容器自己。改成宿主机内网 IP或者给 Dify 容器加host.docker.internal映射。另外确认 MCP Server 监听的是0.0.0.0而不是127.0.0.1Spring Boot 默认监听所有网卡但如果你在application.yml里写了server.address: 127.0.0.1外部就连不上。5.3 工具列表为空ToolCallbackProvider没注册或者Tool方法所在的类没有被 Spring 扫描到。检查Component和包路径。还有一种情况是方法返回值类型不被支持MCP 工具方法建议返回String或简单 POJO返回Mono/Flux需要 webflux 版本的支持。5.4 调用工具时报参数错误ToolParam的description不要省Dify 侧生成调用参数时会参考它。参数名要和 JSON 里的 key 一致Java 编译后参数名可能丢失建议在 pom 里开-parameters编译选项plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId configuration parameterstrue/parameters /configuration /plugin6. 后续怎么走Key 管理与编码场景把 MCP Server 接进 Dify 只是第一步。实际用起来你很快会遇到两个需求一是模型 Key 要统一管理二是工具和 Agent 的调用要能长期跑。Key 管理这块TaoToken 的 API Keys 页面可以创建和轮换 Keyhttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite。建议给 MCP Server 和 Dify 各建一个 Key方便按来源排查调用量。如果你后面要把这套东西用到长期编码或 Agent 场景比如让 Claude Code 这类工具也走统一通道可以看下 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite。它解决的是多工具、多模型共用一套 Key 和额度的问题和 MCP 工具链配合起来比较顺。最后留一个我踩过的坑改完 pom 版本后一定要mvn clean再启动。MCP starter 的传递依赖里有 JSON 序列化库增量编译有时不会更新导致你以为换了版本其实没生效然后继续对着 32600 发呆。