☰
MCP开发的示例:用 Spring AI + SSE 打通 stdio 服务
2026/9/28 4:25:33 网站建设 项目流程

1. 从 stdio 到 SSE:MCP 客户端接入到底卡在哪

MCP(Model Context Protocol)是让大模型调用外部工具的一套协议,Spring AI 从 1.0.0-M6 开始把服务端和客户端都做成了 starter,理论上引个依赖、写个@Tool就能跑。但真正动手时,很多人会卡在同一个地方:本地 stdio 模式跑通了,想换成 SSE 远程通道,客户端却连不上,或者连上了工具列表是空的。

这个问题的根源在于 stdio 和 SSE 是两套完全不同的传输机制。stdio 靠标准输入输出流通信,客户端要负责把服务端进程拉起来,所以配置里写的是command和args;SSE 走 HTTP,服务端得先独立跑起来监听端口,客户端只填一个url就行。两种模式的配置文件、启动参数、依赖开关都不一样,混着配就会出问题。

这篇就按「先 stdio 跑通、再切 SSE」的顺序,把 Spring Boot 项目里用 Spring AI 接入 MCP 的完整链路走一遍。你会看到可复制的 pom 依赖、两套 application.yml 骨架、MCP 客户端 Bean 代码,以及启动后怎么调用工具、怎么验证 SSE 连接、stdio 怎么回退。适合已经在写 Spring Boot、想给项目接上 MCP 工具能力的后端同学。

2. 前置准备:依赖、Key 与 TaoToken 接入

在写代码之前,先把两件事准备好:Maven 依赖和模型侧的接入凭证。

服务端这边,核心依赖是spring-ai-mcp-server-webmvc-spring-boot-starter,它引入后会自动注册 SSE 端点,包括消息端点和 SSE 传输端点。默认开启 SSE 传输,要开 stdio 得通过参数显式打开。客户端这边对应的是spring-ai-mcp-client-spring-boot-starter。两个都用1.0.0-M6版本,版本号要对齐,不然会出现 Bean 找不到的情况。

模型侧如果你打算让 MCP 工具真正被大模型调用,需要一个能走 OpenAI 兼容协议的服务。我这边用的是 TaoToken,它的 API 地址是https://taotoken.net/api,兼容 OpenAI 的/v1/chat/completions格式,Spring AI 的 OpenAI starter 直接改 base-url 就能接。先去控制台建一个 API Key,后面配置里会用到。

<!-- 服务端:MCP Server + WebMVC,自动注册 SSE 端点 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId> <version>1.0.0-M6</version> </dependency> <!-- 客户端:MCP Client --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId> <version>1.0.0-M6</version> </dependency> <!-- 工具库,示例里用 hutool 发 HTTP 请求 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.25</version> </dependency>

拿 Key 的入口在控制台的 API Keys 页面,建完之后复制出来,注意别提交到 Git。如果你还没决定用哪个模型,可以先去模型对话页面试一下返回格式,确认接口通不通再写进配置。

3. 可复制配置:两套 yml 与 MCP 客户端 Bean

配置这块是整个接入最容易出错的地方,因为 stdio 和 SSE 的开关是互斥的。我的做法是拆成三个文件:application.yml做主入口和端口,application-stdio.yml和application-sse.yml分别管两种模式,靠spring.profiles.active切换。

先看 stdio 模式。stdio 靠标准输入输出通信,所以必须关掉 Web 容器,否则 Tomcat 一起来就会抢占标准流,导致协议握手失败。

# application-stdio.yml spring: ai: mcp: server: name: image-search-mcp-server version: 0.0.1 type: SYNC stdio: true # 核心开关:开启 stdio 传输 main: web-application-type: none # 关闭 Web 服务,stdio 模式必须 banner-mode: off

再看 SSE 模式。SSE 是 HTTP 长连接,服务端要独立监听端口,所以stdio必须关掉,同时保留 Web 容器。

# application-sse.yml spring: ai: mcp: server: name: image-search-mcp-server version: 0.0.1 type: SYNC stdio: false # 关闭 stdio,启用 SSE server: port: 8127 # SSE 模式下的监听端口

主配置文件负责激活哪套:

# application.yml spring: application: name: image-search-mcp-server profiles: active: stdio # 默认走 stdio,切 SSE 改成 sse

服务端的工具类用@Tool注解标注方法,Spring AI 会自动扫描并注册。这里以图片搜索为例,方法体里发 HTTP 请求、解析 JSON、返回 URL 列表:

@Service public class ImageSearchTool { private static final String API_KEY = System.getenv("IMAGE_API_KEY"); private static final String API_URL = "https://api.pexels.com/v1/search"; @Tool(description = "search image from web") public String searchImage(@ToolParam(description = "Search query keyword") String query) { try { return String.join(",", searchMediumImages(query)); } catch (Exception e) { return "Error search image: " + e.getMessage(); } } public List<String> searchMediumImages(String query) { Map<String, String> headers = new HashMap<>(); headers.put("Authorization", API_KEY); Map<String, Object> params = new HashMap<>(); params.put("query", query); String response = HttpUtil.createGet(API_URL) .addHeaders(headers) .form(params) .execute() .body(); return JSONUtil.parseObj(response) .getJSONArray("photos") .stream() .map(photoObj -> (JSONObject) photoObj) .map(photoObj -> photoObj.getJSONObject("src")) .map(photo -> photo.getStr("medium")) .filter(StrUtil::isNotBlank) .collect(Collectors.toList()); } }

工具注册靠一个ToolCallbackProviderBean,放在主类里:

@SpringBootApplication public class ImageSearchMcpServerApplication { public static void main(String[] args) { SpringApplication.run(ImageSearchMcpServerApplication.class, args); } @Bean public ToolCallbackProvider imageSearchTools(ImageSearchTool imageSearchTool) { return MethodToolCallbackProvider.builder() .toolObjects(imageSearchTool) .build(); } }

客户端这边,stdio 模式用mcp-servers.json描述怎么拉起服务端进程:

{ "mcpServers": { "image-search-mcp-server": { "command": "java", "args": [ "-Dspring.ai.mcp.server.stdio=true", "-Dspring.main.web-application-type=none", "-Dlogging.pattern.console=", "-jar", "image-search-mcp-server/target/image-search-mcp-server-0.0.1-SNAPSHOT.jar" ], "env": {} } } }

切到 SSE 时,客户端配置换成 URL 形式,同时把 stdio 那段注释掉,避免端口冲突:

spring: ai: mcp: client: sse: connections: server1: url: http://localhost:8127 # stdio: # servers-configuration: classpath:mcp-servers.json

4. 验证请求:调用工具与确认 SSE 连接

配置写完,先验证 stdio。用 Maven 打包服务端,生成可执行 JAR:

mvn clean package -DskipTests

打包完成后,客户端启动时会按mcp-servers.json里的java -jar命令把服务端进程拉起来。写个单元测试确认工具能被发现:

@SpringBootTest class McpClientStdioTest { @Autowired private ChatClient chatClient; @Test void testImageSearchTool() { String result = chatClient.prompt() .user("帮我搜一张关于 mountain 的图片") .call() .content(); System.out.println(result); } }

如果 stdio 通了,控制台会打印出模型调用工具后返回的图片 URL。接下来切 SSE:先把application.yml的active改成sse,单独启动服务端,看到日志里出现 SSE 端点注册信息就说明服务端起来了。

java -jar image-search-mcp-server/target/image-search-mcp-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=sse

服务端启动后,用 curl 直接探一下 SSE 端点是否可达:

curl -N http://localhost:8127/sse

正常的话会看到event: endpoint和一条data:消息,里面带着消息端点的路径。这一步能通,说明 SSE 通道本身没问题。然后启动客户端,同样跑上面的单元测试,工具列表里应该能看到searchImage。

如果你用的是 TaoToken 接模型,客户端里模型配置大概长这样:

spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini

把TAOTOKEN_API_KEY设成环境变量,别写死在 yml 里。

5. 本篇常见错排查

报错一:No tool callbacks found或工具列表为空。九成是ToolCallbackProviderBean 没注册,或者@Tool注解的方法所在类没被 Spring 扫描到。检查主类包路径是否覆盖了 tools 包,Bean 方法名别和已有 Bean 冲突。

报错二:stdio 模式下客户端启动后立刻退出。通常是服务端 JAR 路径写错了,或者web-application-type没设成none。stdio 模式下 Web 容器一起来就会抢标准流,协议握手直接失败。确认mcp-servers.json里的-jar后面是绝对路径或正确的相对路径。

报错三:SSE 连接超时或 404。先确认服务端是不是用sseprofile 启动的,再确认端口没被占用。SSE 模式下stdio必须是false,如果两个都开着,服务端行为会不确定。另外客户端配置里 stdio 那段一定要注释掉,否则客户端会尝试拉起本地进程,和远程 SSE 冲突。

报错四:模型不调用工具,直接编答案。这多半是模型侧的问题,不是 MCP 的问题。确认你用的模型支持 function calling,base-url 和 api-key 正确。可以先用模型对话页面单独测一下模型返回,排除模型本身的问题。

报错五:切换 profile 后配置没生效。Spring Boot 的 profile 激活优先级是命令行 > 环境变量 >application.yml。如果你在命令行传了--spring.profiles.active=sse,application.yml里的active: stdio会被覆盖,这是正常的。排查时用--debug启动,看日志里实际加载了哪个 profile。

6. 下一步:把 MCP 接进你的编码流

stdio 和 SSE 都跑通之后,你会发现 MCP 的价值不在于单个工具,而在于它能被 Agent 反复调用。如果你打算把 MCP 工具接进日常编码流程,比如让模型在写代码时自动查文档、搜图片、调内部接口,可以考虑用 Coding Plan 把模型调用和工具链统一管起来,省得每个项目都单独配一遍 Key 和 base-url。

接入过程中如果遇到工具注册、SSE 握手、模型不调用这类问题,先去接入文档对照配置项,大部分坑都在 profile 切换和 stdio/SSE 开关的互斥上。需要新建 Key 或者管理多个项目的凭证,直接进 API Keys 页面操作就行。

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

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

立即咨询