☰
Java MCP实战:基于Spring Boot优雅实现多SSE端点监听与TaoToken统一接入
2026/9/26 15:44:26 网站建设 项目流程

1. 从单端点到多端点:MCP 服务在 Spring Boot 里的真实困境

如果你正在用 Java 做 MCP(Model Context Protocol)服务端,大概率会遇到这样一个场景:一开始只做了一个/sse端点,所有客户端都往这里连,工具也全塞在一个 McpSyncServer 里。跑通 Demo 没问题,但一旦业务方说“通知服务走一套工具、聊天服务走另一套工具、报表服务再单独隔离”,代码就开始失控了。

MCP 本身是给大模型提供工具、资源、提示的协议,SSE 负责服务器到客户端的单向推送,消息端点负责客户端到服务器的请求上行。两者配合,客户端先通过 SSE 建立长连接“收听”,再通过消息端点“打电话”下发指令。问题在于:当多个业务场景需要独立会话池、独立工具集、独立鉴权时,单端点架构根本撑不住。

我试过把所有工具注册到一个 McpSyncServer 里,结果 A 场景的客户端能看到 B 场景的工具列表,会话串扰、鉴权配置散落在各个 Controller 里,改一个场景要动三处代码。更麻烦的是,每个场景对接的大模型客户端可能来自不同厂商,API Key 管理完全失控。

这篇要解决的就是这件事:用 Spring Boot +HttpServletSseServerTransport实现多 SSE 端点监听,每个端点独立会话、独立工具,同时把大模型调用通道统一收敛到 TaoToken 的 API 通道上,Key 只配一次,所有场景共用。适合已经跑通单端点 MCP、准备做工程化落地的 Java 后端。

2. TaoToken 前置:统一 Key 与 API 通道为什么必要

多端点架构里,每个 MCP 服务器最终都要调用大模型。如果每个场景各自配一套 Key、各自写一套 HTTP 客户端,配置会迅速膨胀成灾难。TaoToken 在这里扮演的角色是统一的大模型 API 通道:你只需要在控制台创建一个 API Key,所有 MCP 服务器通过同一个 base URL 和同一个 Key 发起请求,鉴权逻辑收敛到一处。

具体来说,TaoToken 提供兼容 OpenAI 风格的接口,base URL 是https://taotoken.net/api,模型对话、编码类请求都走这个入口。对 MCP 服务端而言,这意味着工具回调函数里调用大模型时,不需要关心底层是哪家模型,只认一个 endpoint 和一个 Key。

你需要提前准备两样东西:

第一,一个可用的 API Key。到控制台创建,路径是https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,创建后复制保存,后面配置里会用到。

第二,确认你要用的模型名称。可以在模型对话页面先手动测一次,地址是https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,选一个模型发一条消息,确认通道正常。

注意:API Key 不要硬编码进代码或提交到 Git。本文示例用环境变量注入,生产环境建议配合配置中心。

如果你后续要做长期编码类 Agent 或高频工具调用,可以了解 Coding Plan,地址是https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,这里不展开。

3. 可复制配置:application.yml 与多端点注册骨架

先看依赖。MCP Java SDK 的核心包是mcp-core,Servlet 传输实现也在其中。Spring Boot 用 3.x,因为HttpServletSseServerTransport依赖 Servlet 6.0 的异步能力。

<dependency> <groupId>io.modelcontextprotocol.sdk</groupId> <artifactId>mcp-core</artifactId> <version>0.10.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>

application.yml里把 TaoToken 通道和场景端点配置抽出来,避免硬编码:

taotoken: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} model: gpt-4o-mini timeout: 30000 mcp: scenes: - name: notifications sse-path: /sse/notifications/* message-endpoint: /mcp/notifications/message - name: chat sse-path: /sse/chat/* message-endpoint: /mcp/chat/message - name: report sse-path: /sse/report/* message-endpoint: /mcp/report/message

这里的关键设计是:场景列表从配置读取,而不是写死在@Bean方法里。每个场景对应一个HttpServletSseServerTransport实例和一个ServletRegistrationBean,两者通过 Bean 名称配对。

配置类骨架如下,用@ConfigurationProperties绑定场景列表,再动态注册:

@Configuration @EnableWebMvc public class McpServerConfig implements WebMvcConfigurer { @Bean @ConfigurationProperties(prefix = "mcp") public McpSceneProperties mcpSceneProperties() { return new McpSceneProperties(); } @Bean public ObjectMapper objectMapper() { return new ObjectMapper(); } @Bean public List<ServletRegistrationBean<HttpServletSseServerTransport>> mcpServletBeans( McpSceneProperties props, ObjectMapper mapper) { List<ServletRegistrationBean<HttpServletSseServerTransport>> beans = new ArrayList<>(); for (McpSceneProperties.Scene scene : props.getScenes()) { HttpServletSseServerTransport transport = new HttpServletSseServerTransport(mapper, scene.getMessageEndpoint()); ServletRegistrationBean<HttpServletSseServerTransport> bean = new ServletRegistrationBean<>(transport, scene.getSsePath()); bean.setName(scene.getName() + "SseServlet"); beans.add(bean); } return beans; } }

McpSceneProperties就是一个普通的 POJO,字段List<Scene> scenes,Scene 里放name、ssePath、messageEndpoint。这样新增场景只改 YAML,不动 Java 代码。

每个HttpServletSseServerTransport实例内部维护独立的会话池。客户端连/sse/notifications时,请求由 notifications 对应的 transport 处理,会话存在它自己的池子里;连/sse/chat则完全隔离。消息端点同理,POST /mcp/notifications/message只会路由到 notifications 实例。

4. 验证请求:curl 测多端点连通与 TaoToken 鉴权生效

配置写完,启动服务,用 curl 验证。先测 SSE 端点是否正常建立长连接:

curl -N -H "Accept: text/event-stream" http://localhost:8080/sse/notifications

正常返回会看到event: endpoint和data: /mcp/notifications/message这样的初始事件,连接保持不关闭。-N参数关闭 curl 缓冲,方便实时看事件流。再开一个终端测 chat 端点:

curl -N -H "Accept: text/event-stream" http://localhost:8080/sse/chat

两个连接同时存在,互不干扰,说明多端点监听生效。

接着验证消息端点。MCP 的消息端点接收 JSON-RPC 格式请求,先发一个initialize:

curl -X POST http://localhost:8080/mcp/notifications/message \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl-test","version":"1.0"}}}'

返回里应该包含serverInfo和capabilities。如果返回 404,说明消息端点路径和 transport 构造时传的不一致;返回 500 则看日志里 transport 的异常栈。

最后验证 TaoToken 鉴权。在工具回调里调用大模型时,请求头带Authorization: Bearer ${TAOTOKEN_API_KEY}。你可以单独用 curl 测通道:

curl -X POST https://taotoken.net/api/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"ping"}]}'

返回正常 completion 说明 Key 和通道都没问题。把这个调用封装进 MCP 工具的处理函数里,所有场景共用同一个WebClient或RestTemplateBean,Key 从环境变量读。

5. 本篇常见错排查

错误一:SSE 连接建立后立刻断开。最常见原因是 Servlet 异步支持没开。检查@EnableWebMvc是否加上,以及ServletRegistrationBean是否设置了setAsyncSupported(true)。Spring Boot 默认对注册的 Servlet 开启异步,但如果你手动new ServletRegistrationBean后没配,可能被覆盖。

错误二:多个端点串会话。如果你把多个场景的 transport 注册到了同一个 URL 前缀,或者用了/*通配导致路径重叠,Servlet 容器会按注册顺序匹配。确保每个场景的sse-path前缀唯一,比如/sse/notifications/*和/sse/chat/*不会冲突,但/sse/*和/sse/notifications/*会。

错误三:消息端点返回 405。HttpServletSseServerTransport的消息端点只接受 POST。如果你用 GET 请求,会返回 405。另外确认messageEndpoint路径和客户端从 SSE 初始事件里拿到的路径一致,客户端应该用服务端下发的 endpoint,而不是自己拼。

错误四:TaoToken 调用返回 401。检查环境变量TAOTOKEN_API_KEY是否真的注入到进程里。System.getenv在 IDE 里跑和打包后跑结果可能不同。另外确认请求头格式是Bearer加 Key,中间一个空格,不要多也不要少。

错误五:动态新增场景后旧连接失效。这是 Servlet 特性决定的:ServletRegistrationBean在容器启动后无法动态增删。新增场景必须重启服务。但每个 transport 内部的工具可以在运行时通过McpSyncServer的 API 动态注册,配合定时任务或消息队列刷新工具列表,不需要重启。

提示:如果你在排查 SSE 连接问题时看到AsyncContext相关异常,优先检查 Tomcat 版本和 Servlet API 版本是否匹配。Spring Boot 3.2+ 配 Tomcat 10.1+ 是稳妥组合。

6. 接入落地:Key 管理与文档入口

多端点跑通后,下一步是把鉴权配置彻底收敛。所有 MCP 服务器调用大模型时,统一走 TaoToken 的 API 通道,Key 只在环境变量或配置中心维护一份。这样新增场景时,你只需要在 YAML 里加一段,不用再碰 Key。

创建和管理 Key 的入口在控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=,API Keys 管理页可以直接生成和吊销:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。

接入细节和参数说明看文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。如果你用的是 Claude Code 或 Anthropic 风格的客户端,对应入口在https://taotoken.net/claudecode-anthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。

实测下来,把多端点注册和统一 Key 这两件事拆开处理后,新增一个业务场景的成本从“改三个类 + 重启 + 配 Key”降到“改一段 YAML + 重启”。工具的动态刷新则完全不用重启,定时任务拉一次数据库,重建SyncToolSpecification注册进去就行。唯一要记住的坑是:Servlet 注册是启动期行为,端点本身没法热加,这是 Servlet 规范的限制,不是 MCP 的问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询