1. 为什么 Java 开发者需要关注 MCP 的 SSE 通道
如果你正在用 Spring Boot 写业务系统,又想让大模型直接调用你系统里的方法,MCP(Model Context Protocol)就是目前最省事的方案。它做的事情说白了就一件:把你的 Java 方法包装成标准工具,让模型能像调函数一样调它。而 Spring AI 从 1.0 开始把 MCP Server 和 Client 的 starter 都准备好了,Java 侧不用手写协议解析。
这篇聚焦一个具体场景:本地跑通一次端到端调用。Server 端用 Spring AI 暴露两个数学工具,Client 端用 Spring AI 的 OpenAI SDK 接入模型,通过 SSE 长连接把工具挂到 ChatClient 上,最后在浏览器里发一句话,看模型是否真的调用了你写的 Java 方法。
适合谁看:会 Spring Boot、想给现有系统加 AI 工具能力的后端;正在评估 MCP 传输方式选 SSE 还是 stdio 的架构同学;以及被sse-endpoint和sse-message-endpoint两个路径搞混的人。整条链路我会给出可复制的依赖、yml 和代码,你跟着敲就能跑。
SSE 在这里的角色是传输层。MCP 本身不绑定传输方式,stdio 适合本地进程,SSE 适合远程部署的 Server——Client 先发一个 GET 建立长连接,Server 持续推事件,后续工具调用走 POST。理解这一点,后面配置里两个端点各管什么就清楚了。
2. 前置准备:TaoToken 统一 Key 与 API 通道
Client 端要调模型,就得有 Key 和 base-url。我试过把模型 Key 散落在各个项目的 yml 里,换环境时特别容易漏。TaoToken 在这里的作用是提供一个统一的 Key/API 通道,Client 的spring.ai.openai配置直接指向它就行,不用为每个模型单独维护一套地址。
官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
API 地址(配置 base-url 用这个,不带 UTM):https://taotoken.net/api
你需要提前准备的东西:
- JDK 17 或以上,Spring AI 1.0.x 要求;
- Maven 3.8+;
- 一个可用的模型 Key,填到 Client 的
spring.ai.openai.api-key; - 两个端口:Server 用 8080,Client 用 8081,别冲突。
注意:Server 和 Client 是两个独立进程,别塞进同一个 Spring Boot 应用里,否则 SSE 连接会自己连自己,排查起来很绕。
3. MCP Server:暴露工具与 SSE 端点配置
3.1 Maven 依赖
Server 端核心依赖是spring-ai-starter-mcp-server-webmvc,它自带 WebMVC 的 SSE 支持。版本用 Spring AI 的 BOM 统一管理,避免各 starter 版本打架。
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>1.0.0</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> </dependency> </dependencies>3.2 用 @McpTool 声明工具
工具类放在com.example.mcpserver.tool下,方法上加@McpTool,参数加@McpToolParam。description 会直接进模型的工具描述,写清楚点,模型选工具靠它。
package com.example.mcpserver.tool; import org.springframework.ai.mcp.annotation.McpTool; import org.springframework.ai.mcp.annotation.McpToolParam; import org.springframework.stereotype.Component; @Component public class MathTools { @McpTool(name = "add", description = "计算两个数字的和,返回 double") public double add( @McpToolParam(description = "第一个加数", required = true) double a, @McpToolParam(description = "第二个加数", required = true) double b) { return a + b; } @McpTool(name = "square", description = "计算一个数字的平方") public double square( @McpToolParam(description = "待计算数字", required = true) double a) { return a * a; } }3.3 application.yml 关键配置
这里两个端点最容易搞混,我拆开说:
sse-endpoint:Client 发 GET 建立长连接的地址,Server 通过它推事件;sse-message-endpoint:Client 发 POST 提交工具调用请求的地址。
server: port: 8080 spring: application: name: mcp-server ai: mcp: server: name: mcp-server version: 1.0.0 enabled: true protocol: SSE sse-endpoint: /api/v1/sse sse-message-endpoint: /api/v1/mcp capabilities: tool: true resource: false prompt: falsecapabilities.tool: true是显式声明,不写的话工具不会注册。resource 和 prompt 用不到就关掉,减少暴露面。
启动后访问http://localhost:8080/api/v1/sse,浏览器会挂住不返回,这是正常的——长连接建立成功就是这个表现。想确认工具注册了,看启动日志里有没有Registered tools: [add, square]这类输出。
4. MCP Client:接入模型并挂载远程工具
4.1 Client 依赖
Client 需要 web、OpenAI starter 和 MCP Client starter 三个。
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client-webflux</artifactId> </dependency> </dependencies>4.2 ChatClient 配置:把 MCP 工具注入进去
关键点是ToolCallbackProvider,Spring AI 会自动把 MCP Client 拉到的远程工具包装成它,直接塞给 ChatClient 的defaultToolCallbacks。
package com.example.mcpclient.config; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor; import org.springframework.ai.chat.memory.MessageWindowChatMemory; import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.model.tool.ToolCallbackProvider; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ChatClientConfig { @Bean public ChatMemory chatMemory() { return MessageWindowChatMemory.builder().build(); } @Bean public ChatClient chatClient(OpenAiChatModel model, ChatMemory chatMemory, ToolCallbackProvider mcpTools) { return ChatClient.builder(model) .defaultSystem("你是一个数学助手,需要计算时调用工具,不要心算。") .defaultAdvisors( new SimpleLoggerAdvisor(), MessageChatMemoryAdvisor.builder(chatMemory).build()) .defaultToolCallbacks(mcpTools) .build(); } }4.3 application.yml:指向 TaoToken 与远程 Server
server: port: 8081 spring: application: name: mcp-client ai: openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api chat: options: model: gpt-4o-mini mcp: client: sse: connections: math-server: url: http://localhost:8080 sse-endpoint: /api/v1/sseconnections下可以挂多个 Server,key 只是别名。url填 Server 的 host,sse-endpoint填 Server 里配的那个长连接路径,别把 message-endpoint 填进来。
4.4 Controller 入口
package com.example.mcpclient.controller; import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/ai") public class AiController { private final ChatClient chatClient; public AiController(ChatClient chatClient) { this.chatClient = chatClient; } @GetMapping(value = "/chat", produces = "text/plain;charset=utf-8") public String chat(@RequestParam String message) { return chatClient.prompt() .user(message) .call() .content(); } }5. 联调验证:一次端到端调用
先起 Server,再起 Client。两个都起来后,浏览器访问:
http://localhost:8081/ai/chat?message=帮我算一下 12 加 30 等于多少预期结果:模型不会直接回 42,而是先触发工具调用,日志里能看到add被调用,参数是 12 和 30,然后模型拿到返回值再组织语言回复。Client 控制台会打印 SimpleLoggerAdvisor 的请求响应,Server 控制台会打印工具执行记录。
再试一个:
http://localhost:8081/ai/chat?message=9 的平方是多少这次应该命中square工具。如果两次都命中了正确的工具,说明 SSE 通道、工具注册、模型调用三段链路全通了。
想更直观地看 SSE 事件流,可以用 curl 手动建连:
curl -N http://localhost:8080/api/v1/sse你会看到event: endpoint和data: /api/v1/mcp?sessionId=xxx这样的推送,sessionId 就是后续 POST 要带的。这一步能帮你确认 Server 端 SSE 是活的。
6. 本篇常见错误排查
连接建立失败,Client 启动报Connection refused:九成是 Server 没起或者端口不对。先单独 curl 一下 Server 的 sse-endpoint,确认能挂住再起 Client。
工具没被调用,模型直接心算:检查 Server 的capabilities.tool是否为 true,以及 Client 的defaultToolCallbacks(mcpTools)有没有漏。另外 system prompt 里明确写"需要计算时调用工具",模型偷懒心算的情况会少很多。
两个端点填反了:sse-endpoint是 GET 长连接,sse-message-endpoint是 POST 消息入口。Client 配置里只填sse-endpoint,填成 message-endpoint 会连不上。
版本冲突:Spring AI 各 starter 必须走同一个 BOM,混用版本会出现NoSuchMethodError。检查dependencyManagement里 BOM 有没有生效。
Client 用 webflux starter 但项目是 webmvc:MCP Client 的 SSE 实现依赖 WebClient,用 webflux starter 更稳。如果坚持 webmvc,注意别把 webflux 的自动配置排除掉。
模型返回 401:Key 或 base-url 不对。base-url 用https://taotoken.net/api,别多加路径后缀。
7. 继续深入的方向
跑通这条链路后,你可以把 Server 换成真实业务工具,比如查订单、算库存,Client 侧挂多个 MCP Server 做工具聚合。需要管理多个 Key 或切换模型时,TaoToken 的模型对话入口可以快速验证工具描述是否被模型正确理解:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
如果打算把 MCP 用到长期编码或 Agent 场景,Coding Plan 提供了更集中的额度管理:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
Key 的创建和管理在控制台:https://taotoken.net/console?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=
实际调试时,我习惯先 curl 通 SSE 再起 Client,这样能把传输层问题和模型层问题分开。工具描述尽量写具体,模型选错工具大多是因为 description 太模糊。