ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AI 超级智能体全栈项目阶段七:Spring AI 集成 MCP 全攻略:从客户端配置到服务端开发实战(含图片搜索服务案例)

AI 超级智能体全栈项目阶段七:Spring AI 集成 MCP 全攻略:从客户端配置到服务端开发实战(含图片搜索服务案例) 1. 从一次「工具调不通」说起Spring AI 集成 MCP 到底难在哪如果你正在做 AI 超级智能体全栈项目大概率已经过了「能聊天」的阶段开始琢磨怎么让模型真正去调用外部能力查地图、搜图片、读数据库、跑脚本。这时候 MCPModel Context Protocol就会进入视野。它本质上是一套开放协议把「应用怎么把上下文和工具交给大模型」这件事标准化了。你可以把它理解成 AI 世界的 USB 接口以前每个模型、每个工具各写各的适配层现在大家按同一套插头规范来插上就能用。但真到 Spring AI 里落地问题就来了。客户端这边spring-ai-mcp-client-spring-boot-starter的版本、JSON 配置路径、Windows 下npx要不要加.cmd每一步都可能卡住服务端这边Stdio 和 SSE 两种模式配置项不一样工具类怎么被扫描成ToolCallbackProvider打包后java -jar启动参数怎么写全是细节。更现实的是模型调用本身需要一个稳定的 API 通道如果 Key 管理散落在各处调试 MCP 的时候你根本分不清是工具没注册上还是模型请求根本没发出去。这篇就按「客户端配置 → 服务端开发 → 联调验证 → 排错」的完整闭环来写服务端案例用图片搜索Pexels API客户端统一走 TaoToken 的 Key/API 通道避免多套凭证互相干扰。目标很明确你照着敲完能跑通一个「用户说想找计算机图片模型自动调用 MCP 工具返回图片链接」的链路。2. 前置准备TaoToken 统一 Key 与 API 通道接入在写 MCP 代码之前先把模型调用这条链路固定下来。MCP 负责的是「工具」但工具被调用的决策还是模型做的所以模型请求必须通。我习惯把模型通道统一到一个入口这样后面排查问题时变量少。TaoToken 在这里的角色就是提供统一的 API 通道和 Key 管理。你不需要在代码里到处塞不同厂商的 Key客户端配置里指向同一个 base-url 和 api-key 即可。具体操作先到控制台创建 API Key地址是https://taotoken.net/console创建完复制出来形如sk-xxxx。然后在 Spring AI 的模型配置里把 base-url 指向https://taotoken.net/api注意这个地址不带任何查询参数是纯 API 端点。spring: ai: openai: base-url: https://taotoken.net/api api-key: ${TAOTOKEN_API_KEY} chat: options: model: gpt-4o-mini这里用环境变量TAOTOKEN_API_KEY注入别硬编码。如果你还没拿到 Key先去https://taotoken.net/api-keys创建。模型选择上调试 MCP 阶段建议用响应快、支持 function calling 的模型别一上来就上最重的不然工具调用日志刷得慢排查效率低。注意MCP 客户端和服务端的配置是两套东西。模型通道TaoToken解决「谁来决策调用工具」MCP 配置解决「工具从哪来、怎么连」。两者不要混在一个配置文件里否则出问题很难定位。3. 可复制配置Spring AI MCP 客户端骨架客户端的目标是启动时读取一份mcp-servers.json把里面声明的 MCP 服务全部连上并暴露成ToolCallbackProvider供ChatClient调用。3.1 依赖与版本Spring AI 的 MCP starter 在 1.0.0-M6 这个里程碑版本已经比较稳定直接引入dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-client-spring-boot-starter/artifactId version1.0.0-M6/version /dependency如果你用的是 Gradle对应写法是implementation org.springframework.ai:spring-ai-mcp-client-spring-boot-starter:1.0.0-M6。版本号别乱跳M6 和后续 RC 的包结构有差异混用会报ClassNotFoundException。3.2 mcp-servers.json 配置在src/main/resources下新建mcp-servers.json。先放一个高德地图的示例后面再加我们自己的图片搜索服务{ mcpServers: { amap-maps: { command: npx, args: [ -y, amap/amap-maps-mcp-server ], env: { AMAP_MAPS_API_KEY: 你的高德Key } } } }Windows 环境下这里有个坑command要写成npx.cmd否则 Spring AI 启动时会报「找不到命令」。Mac/Linux 保持npx即可。这个差异来自进程创建方式不是配置写错了。3.3 加载配置与调用代码在application.yml里告诉 Spring AI 去哪读这份 JSONspring: ai: mcp: client: stdio: servers-configuration: classpath:mcp-servers.json然后写调用方法。核心是把ToolCallbackProvider注入进来在prompt()链上挂.tools()Resource private ToolCallbackProvider toolCallbackProvider; public String getMessageWithMCPClient(String content, String chatId) { ChatResponse chatResponse this.chatClient.prompt() .user(content) .tools(toolCallbackProvider) .advisors(advisor - advisor .param(chat_memory_conversation_id, chatId) .param(chat_memory_response_size, 10)) .call() .chatResponse(); String text chatResponse.getResult().getOutput().getText(); log.info(MCP 调用返回: {}, text); return text; }这段代码里.tools(toolCallbackProvider)是关键它把 MCP 服务端暴露的所有工具注册给当前对话。advisors那段是会话记忆和 MCP 无关但智能体项目一般都需要顺手带上。4. 服务端开发实战图片搜索 MCPStdio 版本客户端骨架有了现在自己写一个 MCP 服务端。选图片搜索是因为它足够直观输入一个类型词返回一组图片链接验证时一眼能看出工具有没有被调用。4.1 新建模块与依赖新建 Maven 模块varin-image-search-mcp父工程用 Spring Boot 3.4.10Java 21。核心依赖两个MCP 服务端 starter 和 Hutool发 HTTP 请求、解析 JSON 省事。dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-mcp-server-webmvc-spring-boot-starter/artifactId version1.0.0-M6/version /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.38/version /dependency4.2 Stdio 模式配置Stdio 模式下服务端不启动 Web 容器靠标准输入输出和客户端通信。配置文件这样写spring: application: name: varin-image-search-mcp profiles: active: stdio ai: mcp: server: name: varin-image-search-mcp-server version: 0.0.1 type: SYNC stdio: true main: web-application-type: none banner-mode: off imageSearch: api_key: 你的PexelsKeyweb-application-type: none和banner-mode: off很重要。前者防止启动 Tomcat后者避免 banner 输出污染 stdio 通道——MCP 协议对标准输出的纯净度有要求混入无关字符会导致客户端解析失败。4.3 响应实体与工具类Pexels 的搜索接口返回结构比较深先定义三个实体ImageSource各尺寸链接、PexelsImage单张图、PexelsResponse整体响应。字段名和 JSON 对齐用 Lombok 的Data省 getter/setter。工具类是重点用Tool和ToolParam注解把方法暴露给模型Component public class PexelsImageSearchTool { Value(${imageSearch.api_key}) private String apiKey; private static final String PEXELS_SEARCH_URL https://api.pexels.com/v1/search; private static final int DEFAULT_PER_PAGE 5; Tool(description 根据图片类型查询到对应类型的图片) public String searchImages(ToolParam(description 图片的类型) String type) { MapString, String headers new HashMap(); headers.put(Authorization, apiKey); MapString, Object params new HashMap(); params.put(query, type); params.put(per_page, DEFAULT_PER_PAGE); String result HttpUtil.createGet(PEXELS_SEARCH_URL) .addHeaders(headers) .form(params) .execute() .body(); PexelsResponse response JSONUtil.toBean(result, PexelsResponse.class); StringBuffer sb new StringBuffer(); response.getPhotos().forEach(img - sb.append(img.getSrc().getOriginal()).append(\n)); return sb.toString(); } }Tool的description会被模型看到用来判断什么时候调用这个工具所以写清楚「根据图片类型查询图片」比写「搜索」有效得多。ToolParam同理参数说明越具体模型传参越准。4.4 注册为 ToolCallbackProvider工具类本身只是个 Spring Bean要让它变成 MCP 能识别的工具需要在启动类里包一层Bean public ToolCallbackProvider toolCallbackProvider(PexelsImageSearchTool pexelsImageSearchTool) { return MethodToolCallbackProvider.builder() .toolObjects(pexelsImageSearchTool) .build(); }MethodToolCallbackProvider会扫描传入对象上所有带Tool的方法逐个注册。如果你有多个工具类.toolObjects(a, b, c)一起传进去就行。4.5 打包与客户端挂载mvn clean package打出 jar 后在客户端的mcp-servers.json里追加一段varin-image-search-mcp: { command: java, args: [ -Dspring.ai.mcp.server.stdiotrue, -Dspring.main.web-application-typenone, -Dlogging.pattern.console, -jar, varin-image-search-mcp/target/varin-image-search-mcp-0.0.1-SNAPSHOT.jar ], env: {} }-Dlogging.pattern.console把控制台日志格式清空同样是为了不污染 stdio。路径按你本地实际位置调整相对路径是相对于客户端启动目录的。5. 验证请求与成功结果配置齐了写个测试方法跑一下Test void getMessageWithMCPClient() { String result ialdaApp.getMessageWithMCPClient( 我想找一些关于计算机类型的图片, UUID.randomUUID().toString()); Assertions.assertNotNull(result); }跑之前确认两件事TaoToken 的 Key 已注入环境变量Pexels 的 Key 已填进服务端配置。启动后如果一切正常日志里会看到 MCP 客户端连接了两个服务端amap-maps 和 varin-image-search-mcptoolCallbackProvider里注册的工具数量是 2 个服务端工具之和。模型返回的内容大致是这样以下是一些关于计算机类型的图片链接您可以点击查看或下载 - https://images.pexels.com/photos/1416871/pexels-photo-1416871.jpeg - https://images.pexels.com/photos/33650825/pexels-photo-33650825.jpeg - https://images.pexels.com/photos/32664236/pexels-photo-32664236.jpeg - https://images.pexels.com/photos/32472475/pexels-photo-32472475.jpeg - https://images.pexels.com/photos/27153419/pexels-photo-27153419.jpeg看到这个就说明链路通了模型识别出「找图片」意图 → 调用searchImages工具 → 服务端请求 Pexels → 返回链接 → 模型整理成自然语言。整个过程你可以在日志里看到工具调用的入参和出参这是排查问题的第一手材料。6. 本篇常见错排查6.1 Windows 下 npx 找不到命令报错通常是Cannot run program npx。原因前面提过Windows 需要.cmd后缀。把mcp-servers.json里的command: npx改成command: npx.cmd。如果你用的是 PowerShell 且装了 Node也可以先where npx确认实际路径。6.2 Stdio 服务端启动后客户端连不上先看服务端 jar 能不能单独跑起来java -jar xxx.jar如果报NoClassDefFoundError多半是打包时没把依赖打进去检查spring-boot-maven-plugin是否配置了repackage。如果服务端能跑但客户端连不上检查-Dlogging.pattern.console有没有漏日志混入 stdio 是最隐蔽的坑。6.3 工具注册了但模型不调用两种可能。一是Tool的 description 太模糊模型判断不出该用二是模型本身不支持 function calling。前者改描述后者换模型。另外确认.tools(toolCallbackProvider)确实挂上了如果toolCallbackProvider为 null说明 Bean 没注入成功检查启动类上的Bean方法。6.4 SSE 版本切换后端口冲突SSE 模式需要启动 Web 容器配置改成stdio: false、web-application-type: servlet并指定server.port。客户端那边把stdio.servers-configuration注释掉改用sse.connectionsspring: ai: mcp: client: sse: connections: server1: url: http://localhost:9977如果同时保留 stdio 和 SSE 配置可能出现同名工具重复注册模型调用时行为不确定。调试阶段建议一次只开一种模式。6.5 模型请求本身失败如果日志里根本没有工具调用记录先确认模型通道通不通。用https://taotoken.net/api作为 base-url 时检查 Key 是否有效、模型名是否拼对。可以先用一个不带工具的简单 prompt 测一下排除 MCP 干扰。模型通道和 MCP 通道分开验证是提高排查效率的关键习惯。7. 继续往下走把 MCP 接进你的智能体主链路到这里客户端配置、服务端开发、联调验证、排错都跑了一遍。你会发现 MCP 的价值不在于单个工具而在于「标准化」——今天接图片搜索明天接数据库查询后天接内部 API客户端配置几乎不用改只加一段 JSON、服务端按Tool规范写就行。如果你准备把这套东西放进长期运行的智能体项目建议把模型通道固定到 TaoToken 的 Coding Plan地址是https://taotoken.net/coding-plan这样 Key 和配额集中管理调试 MCP 时不用反复切换凭证。接入文档在https://taotoken.net/doc里面有 base-url、模型列表和常见错误码说明排错时对着查比盲猜快。想先验证模型对工具调用的支持情况可以直接在https://taotoken.net/model-chat里用自然语言试确认模型能正确识别工具意图后再写进代码能省不少来回打包的时间。
返回列表