当前位置: 首页 > news >正文

#8、SpringAI MCP 服务端开发实战(图片搜索)

SpringAI MCP 服务端开发实战(图片搜索服务)

本章定位:承接上一章《Spring AI 使用 MCP 客户端(调用高德 MCP)》。上一章我们从"消费方"的角度使用别人提供的 MCP 服务;本章反过来,从"提供方"的角度,基于 Spring AI 从 0 到 1 开发一个图片搜索 MCP 服务端,并用客户端分别通过stdioSSE两种传输方式完成调用。

技术栈:Spring AI 1.0.0-M6 · Spring Boot 3.4.x · JDK 17+ · Maven · Hutool · Pexels API


一、MCP 架构回顾:客户端与服务端

1.1 MCP 客户端

MCP Client 是 MCP 架构中的关键组件,主要负责和 MCP 服务器建立连接并进行通信。它能自动匹配服务器的协议版本、确认可用功能、负责数据传输和 JSON-RPC 交互。此外,它还能发现和使用各种工具、管理资源、和提示词系统进行交互。

除了这些核心功能,MCP 客户端还支持一些额外特性,比如根管理(Roots)、采样控制(Sampling),以及同步或异步操作。为了适应不同场景,它提供了多种数据传输方式,包括:

  • Stdio 标准输入 / 输出:客户端启动一个子进程,通过标准输入输出流与其通信,适用于本地调用;
  • SSE 传输(基于 Java HttpClient / WebFlux):通过 HTTP + Server-Sent Events 与远程服务通信,适用于远程调用。

客户端可以通过不同传输方式调用不同的 MCP 服务,可以是本地的、也可以是远程的。

上一章节已经实现 MCP 客户端调用高德 MCP 服务,本章在此基础上新增对自研图片搜索服务的调用。

1.2 MCP 服务端

MCP Server 也是整个 MCP 架构的关键组件,主要用来为客户端提供各种工具、资源和功能支持。

它负责处理客户端的请求,包括解析协议、提供工具、管理资源以及处理各种交互信息。同时,它还能记录日志、发送通知,并且支持多个客户端同时连接,保证高效的通信和协作。

和客户端一样,它也可以通过多种方式进行数据传输,比如 Stdio 标准输入 / 输出、基于 Servlet / WebFlux / WebMVC 的 SSE 传输,满足不同应用场景。这种设计使得客户端和服务端完全解耦,任何语言开发的客户端都可以调用我们开发的 MCP 服务。


二、MCP 服务端开发:图片搜索服务

服务端开发主要基于 Spring AI MCP Server Boot Starter,它能够自动配置 MCP 服务端组件,使开发者能够轻松创建 MCP 服务,向 AI 客户端提供工具、资源和提示词模板,从而扩展 AI 模型的能力范围。

图片搜索服务使用 Pexels 图片资源网站的 API 构建。

2.1 前置准备

开始之前,请确认环境满足以下要求:

项目要求
JDK17 及以上
Maven3.6+
Spring Boot3.4.x(与 Spring AI 1.0.0-M6 匹配)
Spring AI1.0.0-M6(示例基于该里程碑版本)
Pexels一个可用的 API Key(见 2.2)

版本提示(重要):Spring AI 迭代极快,本文示例基于1.0.0-M6,对应 Spring Boot 3.4.x。升级到 Spring AI 1.0.0 GA 后,MCP 相关 starter 名称发生过调整(见 2.3 与 3.1 的提示),注解也有变化(@ToolParam@ToolParameter),实操时请以官方文档和 Maven 仓库中的实际坐标为准,重点关注"思路和方法"而非死记版本。

2.2 申请 Pexels API Key

进入 Pexels 官网,注册登录后,在 API 管理页面创建一个 Key 即可。免费额度足够本地开发调试使用。

2.3 新建 Maven module:imgSearch_mcpServer

在项目根目录下新建一个 Maven module,名称为imgSearch_mcpServer(示例名,可按自己的规范命名)。

建议单独打开该模块或在 IDE 中将它作为独立工程操作,不要在原有项目的子文件夹里混着改,避免 Maven 依赖、资源路径等出现不必要的问题。

2.4 引入服务端依赖

引入必要的依赖,包括 Lombok、Hutool 工具库和 Spring AI MCP 服务端依赖。

MCP 服务端依赖有 3 种可选,开发流程完全一致,只是配置不同:

依赖说明
spring-ai-mcp-server-spring-boot-starter仅支持 stdio,无需 Web 容器
spring-ai-mcp-server-webmvc-spring-boot-starter基于 Spring MVC 的 SSE 传输,同时可选用 stdio(本章选择
spring-ai-mcp-server-webflux-spring-boot-starter基于 Spring WebFlux 的响应式 SSE 传输,可选用 stdio

此处我们选择引入WebMVC版本(同时支持 stdio 与 SSE,方便后续切换测试):

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-server-webmvc-spring-boot-starter</artifactId><version>1.0.0-M6</version></dependency>

再补上 Hutool 工具库(发起 HTTP 请求、解析 JSON 用)与 Lombok:

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.25</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>

版本提示1.0.0-M6阶段的服务端 starter 坐标形如spring-ai-mcp-server-webmvc-spring-boot-starter;Spring AI 1.0.0 GA 后更名为spring-ai-starter-mcp-server-webmvc。建议通过spring-ai-bom统一管理 Spring AI 各模块版本。

引入 WebMVC 依赖后,启动时会自动注册 MCP 的SSE 端点消息端点(默认路径为/sse/mcp/message),供客户端连接使用,无需我们自己写 Controller。

2.5 编写配置文件(stdio / SSE 双模式)

src/main/resources目录下编写三份配置,通过 Spring Profile 灵活切换 stdio / SSE 两种传输模式。

① stdio 配置application-stdio.yml(需关闭 Web 支持,因为 stdio 模式不启动 Web 容器):

spring:ai:mcp:server:name:image-search-mcp-serverversion:0.0.1type:SYNC# 同步模式;若需响应式可改为 ASYNCstdio:true# 启用 stdio 传输main:web-application-type:none# 关闭 Web 容器banner-mode:off

② SSE 配置application-sse.yml(需关闭 stdio 模式,由 WebMVC 提供 SSE 端点):

spring:ai:mcp:server:name:image-search-mcp-serverversion:0.0.1type:SYNCstdio:false# 关闭 stdio,走 SSE

③ 主配置application.yml:指定激活哪套配置,并声明服务端口与 Pexels Key:

server:port:9090spring:application:name:imgSearch_mcpServerprofiles:active:sse,local# 切换 stdio 时改为: stdio,local## Pexels 图片搜索 API Key(申请地址: https://www.pexels.com/zh-cn/api/key/)Pexels:api-key:你的api-key

提示:上面激活了local这个 Profile,一般用于存放本地私有配置(比如 API Key、数据库密码等)。可以把Pexels.api-key挪到application-local.yml中,并把该文件加入.gitignore,避免密钥提交到代码库;application.yml只保留公共配置。如果没有这个文件,也可以先把api-key直接写在主配置文件里,或删除local激活项。

2.6 编写图片搜索工具 ImageSearchTool

tools包下新建ImageSearchTool,使用@Tool注解标注方法,作为 MCP 服务对外提供的工具。核心逻辑:调用 Pexels 搜索接口,把返回的多张图片 URL 用逗号拼接后返回给 AI。

packagecom.example.imgsearch.tools;importcn.hutool.core.util.StrUtil;importcn.hutool.http.HttpUtil;importcn.hutool.json.JSONObject;importcn.hutool.json.JSONUtil;importorg.springframework.ai.tool.annotation.Tool;importorg.springframework.ai.tool.annotation.ToolParam;importorg.springframework.beans.factory.annotation.Value;importorg.springframework.stereotype.Component;importjava.util.HashMap;importjava.util.List;importjava.util.Map;importjava.util.stream.Collectors;/** * 图片搜索工具:对外暴露为一个 MCP Tool */@ComponentpublicclassImageSearchTool{// 替换为你的 Pexels API 密钥(从官网申请,建议通过环境变量/配置中心注入,勿硬编码)@Value("${Pexels.api-key}")privateStringapiKey;// Pexels 常规搜索接口(请以官方文档为准)privatestaticfinalStringAPI_URL="https://api.pexels.com/v1/search";@Tool(description="search image from web")publicStringsearchImage(@ToolParam(description="Search query keyword")Stringquery){try{// 多张图片 URL 用逗号分隔,便于客户端/AI 解析returnString.join(",",searchMediumImages(query));}catch(Exceptione){return"Error search image: "+e.getMessage();}}/** * 搜索图片列表 * * @param query 搜索关键词 * @return 图片 URL 列表 */publicList<String>searchMediumImages(Stringquery){// 设置请求头(Pexels 要求将 API Key 直接放在 Authorization 头,无需 Bearer 前缀)Map<String,String>headers=newHashMap<>();headers.put("Authorization",apiKey);// 设置请求参数(query 必填;可按需补充 page、per_page 等)Map<String,Object>params=newHashMap<>();params.put("query",query);params.put("per_page",10);// 发送 GET 请求Stringresponse=HttpUtil.createGet(API_URL).addHeaders(headers).form(params).execute().body();// 解析响应 JSON:photos[] 数组 → 每项的 src 对象 → 图片地址returnJSONUtil.parseObj(response).getJSONArray("photos").stream().map(photoObj->(JSONObject)photoObj).map(photoObj->photoObj.getJSONObject("src")).map(src->src.getStr("original"))// 图片规格:original 为原图,可选 large/medium/small 等.filter(StrUtil::isNotBlank).collect(Collectors.toList());}}

说明

  • @Tool/@ToolParam注解来自 Spring AI 的org.springframework.ai.tool.annotation包,与原生工具调用一致;Spring AI 1.0.0 GA 后@ToolParam更名为@ToolParameter
  • Pexels 的photos[].src对象提供了多种尺寸:original(原图)、large2x/large(宽 ≤ 940px)、medium(宽 ≤ 350px)、small(宽 ≤ 130px)等,按需选择即可。
  • 工具内部做了 try-catch 兜底,即使 Pexels 接口异常也会返回友好的错误文本,避免整个对话流程中断(这也是 MCP 服务端的最佳实践之一)。

2.7 单元测试验证工具

编写对应的单元测试类,先不启动 MCP,直接验证工具类本身是否可用:

packagecom.example.imgsearch.tools;importjakarta.annotation.Resource;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.Test;importorg.springframework.boot.test.context.SpringBootTest;@SpringBootTestclassImageSearchToolTest{@ResourceprivateImageSearchToolimageSearchTool;@TestvoidsearchImage(){Stringresult=imageSearchTool.searchImage("美女图片");Assertions.assertNotNull(result);System.out.println(result);}}

测试通过后,控制台会输出搜索到的多张图片地址。


2.8 在主类中注册工具

在主类中通过定义ToolCallbackProviderBean 来注册工具,Spring AI 会自动把ImageSearchTool@Tool注解的方法转换为 MCP 工具并在启动时发布:

packagecom.example.imgsearch;importcom.example.imgsearch.tools.ImageSearchTool;importorg.springframework.ai.tool.MethodToolCallbackProvider;importorg.springframework.ai.tool.ToolCallbackProvider;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.context.annotation.Bean;@SpringBootApplicationpublicclassImgSearchMcpServerApplication{publicstaticvoidmain(String[]args){SpringApplication.run(ImgSearchMcpServerApplication.class,args);}@BeanpublicToolCallbackProviderimageSearchTools(ImageSearchToolimageSearchTool){returnMethodToolCallbackProvider.builder().toolObjects(imageSearchTool).build();}}

提示:示例将主类名统一为ImgSearchMcpServerApplication,与 module 名保持一致。JAR 包名由 Maven 的artifactId决定(默认artifactId-version.jar),与主类名无关,但主类名必须与文件名一致、且被@SpringBootApplication标注。

2.9 打包可执行 JAR

至此服务端就开发完成了。在imgSearch_mcpServer模块下执行 Maven Package 打包:

mvn clean package-DskipTests

打包成功后,会在target目录下生成可执行 JAR 包,例如imgSearch_mcpServer-0.0.1-SNAPSHOT.jar,等会儿客户端调用时会依赖这个文件。


三、MCP 客户端开发:调用图片搜索服务

接下来直接在根项目中开发客户端,调用刚才创建的图片搜索服务。

前置条件:客户端项目已完成上一章"调用高德 MCP"的全部配置(引入了 MCP 客户端依赖、实现doChatWithMcp方法、具备mcp-servers.json)。本章只需在此基础上新增一个图片搜索 Server 的配置即可。

3.1 引入客户端依赖

如果上一章已引入过,可跳过本步。未引入的话,先加入 MCP 客户端依赖:

<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId><version>1.0.0-M6</version></dependency>

实际开发中,你也可以按需添加 WebFlux 支持,但传输方式要与服务端模式匹配(stdio 客户端用 Java HttpClient,SSE 客户端可选用 WebFlux)。GA 版本后该 starter 更名为spring-ai-starter-mcp-client

3.2 stdio 模式调用

(1)配置mcp-servers.json

在客户端resources目录的mcp-servers.json中,新增我们刚打包好的图片搜索服务。通过java命令执行 JAR 包,-D参数相当于命令行注入 stdio 相关配置:

{"mcpServers":{"amap-maps":{"command":"npx.cmd","args":["-y","@amap/amap-maps-mcp-server"],"env":{"AMAP_MAPS_API_KEY":"你的api-key"}},"image-search-mcp-server":{"command":"java","args":["-Dspring.ai.mcp.server.stdio=true","-Dspring.main.web-application-type=none","-Dlogging.pattern.console=","-jar","imgSearch_mcpServer/target/imgSearch_mcpServer-0.0.1-SNAPSHOT.jar"],"env":{}}}}

要点说明:

  • amap-maps是上一章配置的高德 MCP,保留即可,两者可共存;
  • -Dspring.ai.mcp.server.stdio=true让服务端以 stdio 模式运行;
  • -Dspring.main.web-application-type=none关闭 Web 容器;
  • -Dlogging.pattern.console=置空控制台日志格式,必须配置——因为 stdio 使用标准输入输出流通信,多余的控制台日志会干扰 JSON-RPC 报文;
  • -jar后的路径是相对客户端项目根目录的 JAR 路径,请确保已先打包、且路径与你的实际产物一致。

(2)客户端 Spring 配置

在客户端application.yml中启用 stdio 模式,并指定mcp-servers.json位置:

spring:ai:mcp:client:stdio:servers-configuration:classpath:mcp-servers.json

(3)启动项目,发起对话

沿用上一章的doChatWithMcp方法(内部通过ToolCallbackProvider把 MCP 工具注入ChatClient

(4)运行效果

以 Debug 模式运行,通过断点可以看到functionCallbacks中成功加载了图片搜索工具;最终输出结果包含多个图片地址。

小贴士:stdio 模式下服务端是以"子进程"形式被客户端拉起的,代码上打断点不方便调试。若想调试服务端逻辑,建议使用下文 3.3 的 SSE 模式。

3.3 SSE 模式调用

SSE 模式下,服务端是一个独立启动的 Web 服务,客户端通过网络连接它,调试体验更好(服务端可以单独打断点、看日志)。

(1)服务端切换到 SSE 配置

修改服务端application.yml,激活 SSE 配置:

server:port:9090spring:application:name:imgSearch_mcpServerprofiles:active:sse,local

application-sse.ymlstdio: false,让服务端以 SSE 模式启动。)

(2)以 Debug 模式启动服务端

直接运行ImgSearchMcpServerApplication主类即可,启动成功后服务端监听9090端口,并提供/sse/mcp/message两个端点。

(3)修改客户端配置

修改客户端application.yml,添加 SSE 连接配置,同时注释掉原有的 stdio 配置,避免两种模式冲突、产生端口或进程问题:

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

注意url的端口必须与服务端server.port保持一致(示例均为9090)。如果端口不一致,客户端会连接失败。

(4)运行测试

再次运行doChatWithMcp测试,会发现这次 MCP 服务端的代码被真实执行(可在服务端打断点确认)。

3.4 stdio 与 SSE 对比小结

对比维度stdioSSE
传输方式标准输入 / 输出(子进程)HTTP + Server-Sent Events
运行方式由客户端自动拉起子进程独立部署、独立启动的 Web 服务
适用场景本地调用、小型项目远程调用、多客户端共享
调试难度较高(stdout 被协议占用)低(服务端可打断点、看日志)
安全本地进程,不暴露网络端口需自行处理鉴权 / 网络安全
性能无网络开销,相对更高有网络传输开销
客户端配置mcp-servers.json+ stdio 配置sse.connections.url指向服务端地址

四、常见问题排查

  1. JAR 包找不到 / 报"无法执行":客户端启动前必须先mvn package打包服务端,并核对mcp-servers.json-jar的路径与文件名是否与 target 目录中的实际产物一致。

  2. stdio 模式启动后控制台乱码 / 卡住 / 通信异常:确认已在启动参数中加入-Dlogging.pattern.console=关闭控制台日志;服务端代码里不要System.out.println直接输出业务信息,会污染 stdio 协议报文。

  3. Pexels 返回 401 / 鉴权失败:检查Pexels.api-key是否配置正确、Key 是否已生效。Pexels 要求把 Key 直接放在Authorization请求头中(无需Bearer前缀)。

  4. SSE 连接失败(Connection refused / timeout):服务端是否已启动?客户端url的端口是否与服务端server.port一致?切换模式后是否残留了旧的 stdio 配置?

  5. 切换传输模式后出现端口冲突 / 重复进程:客户端配置里 stdio 与 SSE二选一,把另一份注释掉;服务端 Profile(sse/stdio)同样只激活一份。

  6. 工具未加载(functionCallbacks为空):检查服务端ToolCallbackProviderBean 是否已注册、依赖是否引入完整、JAR 是否已重新打包;再看客户端启动日志中 MCP 连接的tools/list是否正常(参考上一章的经验)。

  7. @ToolParam报"找不到符号":Spring AI 版本差异导致。1.0.0-M6使用@ToolParam,GA 版本后更名为@ToolParameter,请按依赖版本替换。

  8. Windows 下命令找不到:在 Windows 下npx需写成npx.cmd(高德 MCP 场景);服务端用java命令时请确认 JDK 已加入 PATH。


五、最佳实践与部署建议

  1. 慎用 MCP:MCP 不是银弹,本质就是"标准化的工具调用"。如果只是应用内部自用、不需要共享的工具,直接用原生@Tool即可,能省去打包、部署、进程管理的成本。建议"能不用就不用",先用工具调用,确有共享 / 生态诉求时再转 MCP。

  2. 传输模式选型:stdio 适合本地小型项目,安全且性能好;SSE 适合独立服务、多客户端共享的中大型项目。服务端开发时推荐引入 WebMVC 版本,一套代码两种模式随意切换。

  3. 工具描述要清晰@Tool@ToolParamdescription要写清楚用途、参数含义,AI 才能准确判断何时调用、传什么参数。

  4. 注意容错:工具方法内部捕获所有可能异常并返回友好错误信息(本章示例已体现),避免一次失败拖垮整个对话流程。

  5. 性能与超时:服务端耗时的操作可改用 ASYNC 模式,或设置合理超时;客户端也要配置request-timeout,防止 MCP 调用过久阻塞 AI 应用。

  6. 安全与密钥管理:API Key 不要硬编码、不要提交 Git。用环境变量 /application-local.yml(gitignore)/ 配置中心注入。远程部署 SSE 服务时,务必考虑鉴权与网络隔离,避免服务被滥用。

  7. 跨平台兼容:stdio 模式注意 Windows 与 Linux 的命令差异(.cmd后缀)、路径分隔符、进程启动方式。生产环境建议用 Linux / Docker 统一运行时。

  8. 部署方式

    • 本地部署(stdio):把服务端 JAR 放到客户端可访问的机器上,通过mcp-servers.json配置即可,适合小项目;
    • 远程部署(SSE):与部署普通后端 Web 项目一致(JAR + 容器 / systemd / 云主机),适合需要共享的服务;
    • Serverless:阿里云百炼等平台支持将 MCP 服务部署到函数计算。注意目前主要通过npx/uvx方式部署,Java 服务一般需要自行部署或选择兼容方案。

六、小结

  1. MCP 服务端开发 = 用@Tool写工具 + 用ToolCallbackProvider注册工具,再通过 starter 依赖自动发布为 MCP 服务,开发过程与原生工具调用几乎一致。
  2. 服务端支持 stdio / SSE 两种传输模式,通过配置文件即可切换;客户端在mcp-servers.json(stdio)或sse.connections(SSE)中登记服务即可调用。
  3. MCP 的本质是"标准化的工具调用":不是 AI 主动调服务,而是客户端把工具清单告诉 AI,AI 决定调用时由我们的程序执行并回填结果。
http://www.jsqmd.com/news/1344776/

相关文章:

  • AntiGravity 与 TRAE Work:AI 开发工具的两种路径对比
  • 基于Unity与AI姿态估计的实时动作捕捉:低成本驱动3D Avatar
  • 时间序列分析基石:自回归模型原理、实战与避坑指南
  • 图像预处理精度实测:三大二值化方案准确率对比《CAD 光栅 PDF 竣工图 OCR 实战开发》专栏第 3 期
  • Unity微信小游戏性能优化实战:从内存管理到渲染调优
  • C++解释器模式实现与优化技巧
  • 从零搭建水下机器人仿真环境:ROS2、Gazebo与ArduSub集成指南
  • Agent 上下文压缩:从「背不动」到「拎得清」
  • 创业公司ERP生产管理模块实施:职责重塑、核心流程与避坑指南
  • 线性回归实战:从房价预测案例掌握机器学习建模全流程
  • 从IPO模型到动态学习系统:构建持续进化的智能应用架构
  • Hadoop HDFS核心原理与生产环境实战指南
  • AntiGravity 与 TRAE Work:AI Agent 工具对比分析
  • ABAP开发核心:深入理解RANGE与SELECTION-OPTIONS的数据筛选机制
  • AUTOSAR架构下UDS诊断服务的实现、配置与工程实践
  • 2026年竹装饰品牌设计公司行业现状与正规商家选择指南 - myqiye
  • 串口通信全解析:从RS-232/RS-485硬件设计到STM32调试实战
  • CentOS7虚拟机:操作系统最小化安装配置
  • 大模型微调超参数实战指南:从学习率到LoRA的调优策略
  • 基于Spring Boot与Vue的盲盒系统开发实战:从权重算法到前后端实现
  • Claude Code类似的企业Agent推荐:企业级AI编程助手选型指南
  • GeoGuessr 道路标线识别:15 秒决策流程与常见误判
  • 化油器油针调整指南:掌握发动机中速区混合比调校
  • AIOps Agent如何借助RAG技术实现历史故障智能查询与决策辅助
  • Agent记忆系统设计:短期上下文与长期外部记忆的协同实践
  • 定制护墙板vs成品护墙板:技术参数对比与选型分析(2026版) - 汇聚至此
  • PyAutoGUI自动化入门:从环境搭建到实战案例的完整指南
  • 光速不变和光速极限-3
  • 选UV打印机时,怎样分辨源头工厂和经销商?
  • 无线网络安全攻防:从WPA2握手包破解到WPA3与防御策略