文章目录
一、前言 二、MCP Client 核心概念 三、完整实战项目 四、一次工具调用的完整链路分析 五、端到端测试 六、部署方案 七、总结
一、前言
各位好,上一篇我们聊了 MCP Server 端的架构设计和实战代码,今天来聊聊 MCP Client 端。在实际项目中,MCP Client 才是真正面向用户的 AI 应用核心,它负责连接各种 MCP Server,自动发现并调用工具,让大模型具备"动手"的能力。
本文与上一篇的关系:
上一篇:MCP Server 端,提供天气查询、订单查询、审批通知等工具服务(端口 8080) 这一篇:MCP Client 端,连接 Server 端的工具,让 MiniMax 大模型能够调用这些工具
为什么选 MiniMax 作为示例模型?因为国内大模型里,MiniMax 的 API 设计非常规范,而且 Spring AI 1.1.5 版本已经原生支持 MiniMax,配合 MCP 协议,简直是天作之合。
本文特色:一个 Demo 项目搞定核心功能!我们将构建一个轻量的 MCP Client 项目,包含:
连接多个 MCP Server(天气、订单、文件系统等) 本地工具与 MCP 工具混合使用 动态工具加载(基于角色配置) 同步和流式对话 对话历史管理 完整的端到端测试
废话不多说,直接上干货!
二、MCP Client 核心概念
2.1 MCP Client 是什么?
MCP Client 是 AI 应用与 MCP Server 之间的桥梁。它负责:
- 协议协商
:与 Server 协商 MCP 协议版本和能力 - 工具发现
:自动获取 Server 暴露的工具列表和 JSON Schema - 工具调用
:将 LLM 的工具调用请求转发给 Server - 结果回传
:将 Server 的执行结果返回给 LLM
2.2 核心术语解释
| MCP (Model Context Protocol) | ||
| MCP Client | ||
| MCP Server | ||
| ToolCallback | ||
| ChatClient | ||
| ChatModel | ||
| Streamable HTTP | ||
| JSON-RPC |
2.3 MCP Client 架构全景

2.4 MCP Client 传输方式对比
| Streamable HTTP | 远程服务、微服务 | 断线重连、会话恢复、统一端点 | 需要 HTTP 基础设施 |
三、完整实战项目
3.1 项目功能清单
我们将构建一个轻量的 MCP Client Demo 项目,包含以下功能:
| 多 MCP Server 连接 | spring.ai.mcp.client.connections | |
| 本地工具 | Function | |
| 混合工具调用 | ChatClient | |
| 动态工具加载 | ||
| 同步对话 | chatClient.prompt().call() | |
| 流式对话 | chatClient.prompt().stream() | |
| 对话历史 | ChatMemory | |
| 工具调用日志 | Advisor |
3.2 项目结构
mcp-client-demo/├── pom.xml # Maven 依赖配置├── src/│ ├── main/│ │ ├── java/│ │ │ └── com/example/mcpclient/│ │ │ ├── McpClientApplication.java # 启动类│ │ │ ├── config/│ │ │ │ ├── McpRequestCustomizerProperties.java # MCP 请求定制器配置属性│ │ │ │ ├── McpClientConfig.java # MCP Client HTTP 请求定制器│ │ │ │ └── ChatConfig.java # ChatClient 配置│ │ │ ├── tool/│ │ │ │ └── LocalWeatherTool.java # 本地天气工具│ │ │ ├── advisor/│ │ │ │ └── ToolCallLoggingAdvisor.java # 工具调用日志拦截器│ │ │ ├── service/│ │ │ │ ├── DynamicToolService.java # 动态工具加载服务│ │ │ │ └── ChatService.java # 聊天服务│ │ │ └── controller/│ │ │ └── ChatController.java # REST API 控制器│ │ └── resources/│ │ └── application.yml # 配置文件│ └── test/│ └── java/│ └── com/example/mcpclient/│ └── McpClientApplicationTests.java└── target/
说明:项目结构按依赖关系排列,底层配置和工具类在前,上层服务和控制器在后。
3.3 依赖配置
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.5.5</version></parent><groupId>com.example</groupId><artifactId>mcp-client-demo</artifactId><version>1.0.0</version><name>MCP Client Demo</name><description>轻量的 MCP Client Demo 项目,包含多 Server 连接、本地工具混合、动态加载等功能</description><properties><java.version>17</java.version><spring-ai.version>1.1.5</spring-ai.version></properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>{MINIMAX_API_KEY:your-minimax-api-key-here}chat:options:model: abab6.5s-chat # MiniMax 模型temperature: 0.7# MCP Client 配置 - Streamable HTTP 模式mcp:client:enabled: truename: mini-mcp-clientversion: 1.0.0type: sync # sync=同步模式,async=异步模式request-timeout: 60s# 多 MCP Server 连接配置(streamable-http 传输方式)streamable-http:connections:server-a:url: http://localhost:8080endpoint: /mcpauth:type: api-keyheader-name: "X-API-Key"header-value: "demo-api-key"# server-b:# url: http://localhost:8080# endpoint: /mcp# auth:# type: bearer# token: "token-for-server-b"# 日志配置(调试时开启)logging:level:io.modelcontextprotocol: debug # MCP 协议调试日志com.example.mcpclient: debug # 项目调试日志
连接配置说明
每个连接支持以下配置项:
url | http://localhost:8080 | |
endpoint | /mcp | |
headers | X-API-Key: "demo-api-key" | |
auth |
认证配置(auth)
支持两种认证方式:
1. API Key 认证
auth:type: api-keyheader-name: "X-API-Key"header-value: "your-api-key"
2. Bearer Token 认证
auth:type: bearertoken: "your-bearer-token"
注意:没有配置
auth的连接将不添加额外认证头,使用默认的headers配置即可。
3.5 启动类
package com.example.mcpclient;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/*** MCP Client 启动类* 一个 Demo 项目搞定核心功能:多 Server 连接、本地工具混合、动态加载等** @author senfel* @date 2023/11/5 14:01*/@SpringBootApplicationpublic class McpClientApplication {public static void main(String[] args) {SpringApplication.run(McpClientApplication.class, args);}}
3.6 本地工具实现
package com.example.mcpclient.tool;import com.fasterxml.jackson.annotation.JsonClassDescription;import com.fasterxml.jackson.annotation.JsonProperty;import com.fasterxml.jackson.annotation.JsonPropertyDescription;import lombok.Data;import org.springframework.ai.tool.annotation.Tool;import org.springframework.stereotype.Component;import java.util.Random;/*** 本地天气查询工具* 与 MCP 工具互补,提供本地模拟数据** 为什么需要本地工具?* - 有些工具不需要远程调用,直接在本地执行更快* - 可以模拟数据用于测试* - 与 MCP 工具混合使用,LLM 会自动选择最合适的工具** Spring AI 1.1.5 变化:* - 使用 @Tool 注解标记工具方法,而不是实现 Function 接口* - MethodToolCallbackProvider 会自动扫描 @Tool 注解的方法** @author senfel* @date 2023/11/5 14:01*/@Component("local_weather_query")public class LocalWeatherTool {private static final String[] CONDITIONS = {"晴朗", "多云", "阴天", "小雨", "大雨", "雷雨"};private final Random random = new Random();/*** 工具请求参数* JSON Schema 会自动生成,用于告诉 LLM 如何调用这个工具*/@Data@JsonClassDescription("查询指定城市的天气信息")public static class Request {@JsonProperty(required = true, value = "city")@JsonPropertyDescription("城市名称,例如:北京、上海、广州")private String city;}/*** 工具响应结果*/@Datapublic static class Response {private String city;private WeatherInfo weatherInfo;@Datapublic static class WeatherInfo {private Integer temperature;private String condition;private Integer humidity;private Integer windSpeed;}}/*** 查询天气的工具方法* 使用 @Tool 注解,Spring AI 会自动将其注册为 ToolCallback*/@Tool(description = "查询指定城市的天气信息")public Response queryWeather(Request request) {Response.WeatherInfo weatherInfo = new Response.WeatherInfo();weatherInfo.setTemperature(random.nextInt(35) - 5); // -5 ~ 30°CweatherInfo.setCondition(CONDITIONS[random.nextInt(CONDITIONS.length)]);weatherInfo.setHumidity(random.nextInt(80) + 20); // 20% ~ 100%weatherInfo.setWindSpeed(random.nextInt(30) + 1); // 1 ~ 30 km/hResponse response = new Response();response.setCity(request.getCity());response.setWeatherInfo(weatherInfo);return response;}}
3.7 工具调用日志拦截器
package com.example.mcpclient.advisor;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClientRequest;import org.springframework.ai.chat.client.ChatClientResponse;import org.springframework.ai.chat.client.advisor.api.CallAdvisor;import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;import org.springframework.stereotype.Component;/*** 工具调用日志记录 Advisor* 记录每次工具调用的详细信息,方便调试和监控** Advisor 是什么?* - 类似于 Spring 的拦截器,可以在工具调用前后执行逻辑* - 用于日志记录、性能监控、权限检查等** Spring AI 1.1.5 变化:* - 使用 CallAdvisor 接口替代 CallAroundAdvisor* - 使用 ChatClientRequest 和 ChatClientResponse 替代 AdvisedRequest 和 AdvisedResponse* - 使用 adviseCall 方法替代 aroundCall 方法** @author senfel* @date 2023/11/5 14:01*/@Component@Slf4jpublic class ToolCallLoggingAdvisor implements CallAdvisor {@Overridepublic String getName() {return "ToolCallLoggingAdvisor";}@Overridepublic int getOrder() {return 0; // 执行顺序,数字越小越先执行}@Overridepublic ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {// 工具调用前log.info("=== 工具调用开始 ===");log.info("请求内容: {}", request.prompt().getContents());long startTime = System.currentTimeMillis();// 执行工具调用ChatClientResponse response = chain.nextCall(request);// 工具调用后long duration = System.currentTimeMillis() - startTime;log.info("响应结果: {}", response);log.info("耗时: {}ms", duration);log.info("=== 工具调用结束 ===");return response;}}
3.8 MCP Client 请求定制器配置
package com.example.mcpclient.config;import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer;import io.modelcontextprotocol.common.McpTransportContext;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.net.http.HttpRequest;/*** MCP Client 配置* 负责定制 HTTP 请求,例如添加认证头、日志记录等** @author senfel* @date 2023/11/5 14:01*/@Configuration@Slf4j@RequiredArgsConstructorpublic class McpClientConfig {private final McpRequestCustomizerProperties customizerProperties;@Beanpublic McpSyncHttpClientRequestCustomizer mcpHttpClientRequestCustomizer() {return (builder, method, endpoint, body, context) -> {String requestUrl = endpoint.toString();// 根据请求 URL 匹配对应的 server 配置customizerProperties.getConnections().values().stream().filter(conn -> requestUrl.startsWith(conn.getUrl())).findFirst().ifPresent(conn -> {McpRequestCustomizerProperties.AuthConfig auth = conn.getAuth();if (auth != null) {applyAuth(builder, auth);log.info("MCP request to {} with auth type={}: method={}",endpoint.getHost(), auth.getType(), method);} else {log.info("MCP request to {} without custom auth: method={}",endpoint.getHost(), method);}});};}private void applyAuth(HttpRequest.Builder builder, McpRequestCustomizerProperties.AuthConfig auth) {switch (auth.getType()) {case "api-key":builder.header(auth.getHeaderName(), auth.getHeaderValue());break;case "bearer":builder.header("Authorization", "Bearer " + auth.getToken());break;default:log.warn("Unknown auth type: {}", auth.getType());}}}
配置属性类
package com.example.mcpclient.config;import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Configuration;import java.util.Map;/*** MCP 请求定制器配置属性* 绑定 spring.ai.mcp.client.streamable-http 配置** @author senfel* @date 2023/11/5 14:01*/@Data@Configuration@ConfigurationProperties(prefix = "spring.ai.mcp.client.streamable-http")public class McpRequestCustomizerProperties {private Map<String, ServerConnection> connections;@Datapublic static class ServerConnection {private String url;private String endpoint;private AuthConfig auth;}@Datapublic static class AuthConfig {private String type;private String headerName;private String headerValue;private String token;}}
3.9 ChatClient 配置
package com.example.mcpclient.config;import com.example.mcpclient.advisor.ToolCallLoggingAdvisor;import com.example.mcpclient.service.DynamicToolService;import com.example.mcpclient.tool.LocalWeatherTool;import io.modelcontextprotocol.client.McpSyncClient;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;import org.springframework.ai.chat.memory.ChatMemory;import org.springframework.ai.chat.model.ChatModel;import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;import org.springframework.ai.tool.method.MethodToolCallbackProvider;import org.springframework.ai.tool.resolution.ToolCallbackResolver;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.List;/*** ChatClient 配置* 一个配置类搞定所有功能:* - 多 MCP Server 工具注入* - 本地工具注入* - 对话历史管理* - 工具调用日志* - 动态工具加载服务(基于角色)** Spring AI 1.1.5 变化:* - ChatMemory 由 Spring Boot 自动配置,无需手动创建 InMemoryChatMemory Bean* - MessageChatMemoryAdvisor 使用 builder 模式构建** @author senfel* @date 2023/11/5 14:01*/@Configurationpublic class ChatConfig {private final LocalWeatherTool localWeatherTool;private final ToolCallLoggingAdvisor toolCallLoggingAdvisor;private final ChatModel chatModel;private final ToolCallbackResolver toolCallbackResolver;public ChatConfig(LocalWeatherTool localWeatherTool,ToolCallLoggingAdvisor toolCallLoggingAdvisor,ChatModel chatModel,ToolCallbackResolver toolCallbackResolver) {this.localWeatherTool = localWeatherTool;this.toolCallLoggingAdvisor = toolCallLoggingAdvisor;this.chatModel = chatModel;this.toolCallbackResolver = toolCallbackResolver;}/*** 默认 ChatClient(包含所有工具)* 用于 /api/chat 接口** 注意:* - McpSyncClient 由 Spring AI 自动创建* - ChatMemory 由 Spring Boot 自动配置(基于 spring-ai-client-chat 依赖)* - 根据 application.yml 中的 spring.ai.mcp.client.connections 配置* 每个 connection 会自动创建一个 McpSyncClient Bean*/@Beanpublic ChatClient chatClient(ChatMemory chatMemory, List<McpSyncClient> mcpClients) {// 1. 收集所有 MCP Server 的工具// Spring AI 已自动创建多个 McpSyncClient,每个对应一个 ServerSyncMcpToolCallbackProvider[] mcpProviders = mcpClients.stream().map(SyncMcpToolCallbackProvider::new).toArray(SyncMcpToolCallbackProvider[]::new);// 2. 本地工具MethodToolCallbackProvider localProvider = MethodToolCallbackProvider.builder().toolObjects(localWeatherTool).build();// 3. 构建 ChatClientreturn ChatClient.builder(chatModel).defaultSystem("你是一个智能助手,可以帮助用户查询天气、订单、文件等信息。\n" +"当用户询问天气、订单或文件时,请使用相应的工具来获取信息。\n" +"回答要简洁明了,直接给出用户需要的信息。")// 注入所有工具(MCP + 本地).defaultToolCallbacks(mcpProviders).defaultToolCallbacks(localProvider)// 注入对话历史(使用 builder 模式).defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())// 注入工具调用日志.defaultAdvisors(toolCallLoggingAdvisor).build();}/*** 动态工具加载服务* 用于 /api/role/chat 接口,根据角色动态加载工具*/@Beanpublic DynamicToolService dynamicToolService(ChatModel chatModel, ChatMemory chatMemory, List<McpSyncClient> mcpClients) {return new DynamicToolService(chatModel, chatMemory, mcpClients, toolCallbackResolver);}}
3.10 动态工具加载服务
package com.example.mcpclient.service;import io.modelcontextprotocol.client.McpSyncClient;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.memory.ChatMemory;import org.springframework.ai.chat.model.ChatModel;import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;import org.springframework.ai.tool.ToolCallback;import org.springframework.ai.tool.resolution.ToolCallbackResolver;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.List;import java.util.Map;/*** 动态工具加载服务* 根据角色配置动态加载本地工具和 MCP 工具** 使用场景:* - 客服助手角色:只能查询天气和订单* - 财务助手角色:只能查询订单和处理支付* - 管理员角色:可以使用所有工具** @author senfel* @date 2023/11/5 14:01*/@Service@RequiredArgsConstructor@Slf4jpublic class DynamicToolService {private final ChatModel chatModel;private final ChatMemory chatMemory;private final List<McpSyncClient> mcpClients;private final ToolCallbackResolver toolCallbackResolver;// Mock 角色配置(实际项目中从数据库读取)private final Map<Long, ChatRole> roleConfig = Map.of(1L, new ChatRole(1L, "客服助手", "你是一个客服助手,可以帮助用户查询天气和订单。",List.of("local_weather_query"), List.of("business-server")),2L, new ChatRole(2L, "财务助手", "你是一个财务助手,可以帮助用户查询订单和处理支付。",List.of(), List.of("business-server", "payment-server")));/*** 根据角色 ID 创建 ChatClient* 自动加载角色关联的工具*/public ChatClient createChatClientByRole(Long roleId) {ChatRole role = roleConfig.get(roleId);if (role == null) {throw new IllegalArgumentException("角色不存在: " + roleId);}List<ToolCallback> toolCallbacks = new ArrayList<>();// 1. 加载本地工具role.toolNames().forEach(toolName -> {ToolCallback toolCallback = toolCallbackResolver.resolve(toolName);if (toolCallback != null) {toolCallbacks.add(toolCallback);}});// 2. 加载 MCP 工具role.mcpClientNames().forEach(mcpClientName -> {// 匹配对应的 McpSyncClientmcpClients.stream().filter(client -> client.getClientInfo().name().contains(mcpClientName)).findFirst().ifPresent(client -> {ToolCallback[] mcpToolCallbacks =new SyncMcpToolCallbackProvider(client).getToolCallbacks();toolCallbacks.addAll(List.of(mcpToolCallbacks));});});// 3. 构建 ChatClientreturn ChatClient.builder(chatModel).defaultSystem(role.systemMessage()).defaultToolCallbacks(toolCallbacks.toArray(new ToolCallback[0])).build();}/*** 角色定义*/public record ChatRole(Long id, String name, String systemMessage,List<String> toolNames, List<String> mcpClientNames) {}}
3.11 聊天服务
package com.example.mcpclient.service;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.chat.client.ChatClient;import org.springframework.stereotype.Service;import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import reactor.core.publisher.Flux;/*** 聊天服务* 封装 ChatClient 调用逻辑** 一个 Service 搞定所有功能:* - 同步对话* - 流式对话* - SSE 流式对话** @author senfel* @date 2023/11/5 14:01*/@Service@RequiredArgsConstructor@Slf4jpublic class ChatService {private final ChatClient chatClient;/*** 同步对话* @param message 用户消息* @return AI 回复*/public String chat(String message) {log.info("用户消息: {}", message);String response = chatClient.prompt().user(message).call().content();log.info("AI 回复: {}", response);return response;}/*** 流式对话* @param message 用户消息* @return AI 回复(流式拼接)*/public String chatStream(String message) {log.info("用户消息(流式): {}", message);StringBuilder result = new StringBuilder();Flux<String> stream = chatClient.prompt().user(message).stream().content();stream.doOnNext(chunk -> {result.append(chunk);System.out.print(chunk);}).then().block();return result.toString();}/*** SSE 流式对话* @param message 用户消息* @return SseEmitter*/public SseEmitter chatSse(String message) {SseEmitter emitter = new SseEmitter(60000L); // 60 秒超时Flux<String> stream = chatClient.prompt().user(message).stream().content();stream.doOnNext(chunk -> {try {emitter.send(SseEmitter.event().data(chunk));} catch (Exception e) {emitter.completeWithError(e);}}).doOnComplete(emitter::complete).doOnError(emitter::completeWithError).subscribe();return emitter;}}
3.12 控制器
package com.example.mcpclient.controller;import com.example.mcpclient.service.ChatService;import com.example.mcpclient.service.DynamicToolService;import lombok.RequiredArgsConstructor;import lombok.extern.slf4j.Slf4j;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.*;import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;/*** 聊天控制器* 提供同步、流式、SSE 三种对话接口* 支持基于角色的动态工具加载** 接口说明:* - POST /api/chat : 同步对话,一次性返回完整结果* - POST /api/chat/stream : 流式对话,实时返回文本片段* - GET /api/chat/sse : SSE 流式对话,浏览器可直接使用* - POST /api/role/chat : 基于角色的对话(动态加载工具)** @author senfel* @date 2023/11/5 14:01*/@RestController@RequestMapping("/api")@RequiredArgsConstructor@Slf4jpublic class ChatController {private final ChatService chatService;private final DynamicToolService dynamicToolService;/*** 同步对话接口* @param message 用户消息* @return AI 回复*/@PostMapping("/chat")public String chat(@RequestParam String message) {log.info("用户消息: {}", message);return chatService.chat(message);}/*** 流式对话接口* @param message 用户消息* @return AI 回复(流式拼接)*/@PostMapping("/chat/stream")public String chatStream(@RequestParam String message) {log.info("用户消息(流式): {}", message);return chatService.chatStream(message);}/*** SSE 流式对话接口* SSE (Server-Sent Events) 是什么?* - 服务器向浏览器推送实时事件的技术* - 基于 HTTP,单向通信(服务器 → 客户端)* - 浏览器可直接使用 EventSource API 接收** @param message 用户消息* @return SSE 流*/@GetMapping(value = "/chat/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public SseEmitter chatSse(@RequestParam String message) {log.info("用户消息(SSE): {}", message);return chatService.chatSse(message);}/*** 基于角色的同步对话* @param roleId 角色 ID(1=客服助手,2=财务助手)* @param message 用户消息* @return AI 回复*/@PostMapping("/role/chat")public String roleChat(@RequestParam Long roleId, @RequestParam String message) {log.info("角色 ID: {}, 用户消息: {}", roleId, message);// 根据角色动态创建 ChatClient(自动加载对应工具)org.springframework.ai.chat.client.ChatClient chatClient =dynamicToolService.createChatClientByRole(roleId);String response = chatClient.prompt().user(message).call().content();log.info("AI 回复: {}", response);return response;}}
四、一次性工具调用完整链路分析
4.1 完整调用流程图
用户 ChatController ChatService ChatClient│ │ │ ││ "北京天气怎么样?" │ │ ││─────────────────────────>│ │ ││ │ chat("北京天气怎么样?") │ ││ │─────────────────────────>│ ││ │ │ prompt().user(msg) ││ │ │───────────────────────>││ │ │ ││ │ │ 构建 Prompt│ │ │ + 附加工具列表│ │ │ ││ │ │ ▼│ │ │ MiniMax ChatModel│ │ │ ││ │ │ 1. 分析用户意图 ││ │ │ 2. 决定调用 getWeather ││ │ │ 3. 生成参数 cityName ││ │ │<───────────────────────││ │ │ ││ │ ▼ ││ │ McpSyncClient ││ │ SyncMcpToolCallbackProvider ││ │ │ ││ │ │ 封装 JSON-RPC 请求 ││ │ │ tools/call ││ │ │ HTTP POST /mcp ││ │ │ ││ │ ▼ ││ │ ┌─────────────────┐ ││ │ │ MCP Server │ ││ │ │ (localhost:8080)│ ││ │ │ WeatherTool │ ││ │ └─────────────────┘ ││ │ │ ││ │ │ 返回天气数据 ││ │ │<───────────────────────││ │ │ ││ │ ▼ ││ │ MiniMax ChatModel ││ │ │ ││ │ │ 基于工具结果生成回复 ││ │ │<───────────────────────││ │ │ ││ │ 返回 AI 回复 │ ││ │<─────────────────────────│ ││ "北京今天天气晴朗..." │ │ ││<─────────────────────────│ │ │
4.2 调用链路详细说明
POST /api/chat?message=北京天气怎么样? | |||
chatClient.prompt().user(message).call() | |||
getWeather 工具,生成参数 {cityName: "北京"} | |||
POST http://localhost:8080/mcp | |||
WeatherTool.getWeather("北京") | |||
{city: "北京", temperature: 22, condition: "晴朗"} | |||
五、端到端测试
5.1 启动服务
# 1. 先启动 MCP Server(端口 8080)# 参考:SpringAI+MCPServer实战-StreamableHTTP协议打造企业级AI工具服务.mdcd ../mcp-servermvn spring-boot:run# 2. 启动 MCP Client(端口 8081)cd mcp-client-demomvn spring-boot:run
5.2 测试用例
# 测试 1:本地工具调用(天气查询)curl -X POST "http://localhost:8081/api/chat" \-d "message=北京今天天气怎么样"# 测试 2:MCP 工具调用(订单查询)curl -X POST "http://localhost:8081/api/chat" \-d "message=帮我查一下订单 ORD-1001 的状态"# 测试 3:多工具调用(本地 + MCP)curl -X POST "http://localhost:8081/api/chat" \-d "message=先查一下北京天气,再帮我看看 ORD-1002 订单发货没"# 测试 4:流式对话curl -X POST "http://localhost:8081/api/chat/stream" \-d "message=今天适合外出吗?"# 测试 5:SSE 流式对话(浏览器可直接访问)curl -X GET "http://localhost:8081/api/chat/sse?message=上海天气怎么样"# 测试 6:基于角色的对话(客服助手角色)curl -X POST "http://localhost:8081/api/role/chat" \-d "roleId=1&message=北京天气怎么样"# 测试 7:基于角色的对话(财务助手角色)curl -X POST "http://localhost:8081/api/role/chat" \-d "roleId=2&message=订单 ORD-1001 发货没"
5.3 预期效果
测试 1:本地工具调用
用户:北京今天天气怎么样AI:北京今天天气晴朗,温度 18°C,湿度 45%,风速 12 km/h。天气不错,适合外出!
测试 2:MCP 工具调用
用户:帮我查一下订单 ORD-1001 的状态AI:订单 ORD-1001 购买的是 iPhone 16 Pro,当前状态为"已发货",预计 2 天后送达,价格 8999.00 元。
测试 3:多工具调用
用户:先查一下北京天气,再帮我看看 ORD-1002 订单发货没AI:北京今天多云,温度 22°C。订单 ORD-1002(MacBook Air M3)当前状态为"处理中",预计 5 天后送达。
5.4 调试日志示例
2025-06-06 10:30:15.123 DEBUG [main] i.m.client.McpSyncClient : Initializing MCP client...2025-06-06 10:30:15.456 DEBUG [main] i.m.client.McpSyncClient : Connected to server: business-server v1.0.02025-06-06 10:30:15.789 DEBUG [main] i.m.client.McpSyncClient : Connected to server: filesystem-server v1.0.02025-06-06 10:30:16.123 DEBUG [main] i.m.client.McpSyncClient : Connected to server: payment-server v1.0.02025-06-06 10:30:16.456 DEBUG [main] i.m.client.McpSyncClient : Discovered tools: [getWeather, getOrderStatus, listFiles, processPayment]2025-06-06 10:30:16.789 INFO [main] c.e.m.config.ChatConfig : Registered local tool: local_weather_query2025-06-06 10:30:20.123 INFO [http-nio-8081-exec-1] c.e.m.service.ChatService : 用户消息: 北京今天天气怎么样2025-06-06 10:30:21.456 INFO [http-nio-8081-exec-1] c.e.m.advisor.ToolCallLoggingAdvisor : === 工具调用开始 ===2025-06-06 10:30:21.456 INFO [http-nio-8081-exec-1] c.e.m.advisor.ToolCallLoggingAdvisor : 工具名称: local_weather_query2025-06-06 10:30:21.456 INFO [http-nio-8081-exec-1] c.e.m.advisor.ToolCallLoggingAdvisor : 工具参数: {city=北京}2025-06-06 10:30:21.789 INFO [http-nio-8081-exec-1] c.e.m.advisor.ToolCallLoggingAdvisor : 工具结果: {city=北京, weatherInfo={temperature=22, condition=晴朗, ...}}2025-06-06 10:30:21.789 INFO [http-nio-8081-exec-1] c.e.m.advisor.ToolCallLoggingAdvisor : 耗时: 333ms2025-06-06 10:30:21.789 INFO [http-nio-8081-exec-1] c.e.m.advisor.ToolCallLoggingAdvisor : === 工具调用结束 ===2025-06-06 10:30:22.123 INFO [http-nio-8081-exec-1] c.e.m.service.ChatService : AI 回复: 北京今天天气晴朗,温度 22°C...
六、部署方案
6.1 Docker 部署
FROM eclipse-temurin:17-jre-alpineWORKDIR /appCOPY target/mcp-client-demo-1.0.0.jar app.jarEXPOSE 8081ENV MINIMAX_API_KEY=your-api-key-hereENTRYPOINT ["java", "-jar", "app.jar"]
# 构建镜像docker build -t mcp-client-demo:1.0.0 .# 运行容器docker run -d -p 8081:8081 \-e MINIMAX_API_KEY=your-real-api-key \--name mcp-client-demo mcp-client-demo:1.0.0
6.2 Kubernetes 部署
apiVersion: apps/v1kind: Deploymentmetadata:name: mcp-client-demospec:replicas: 2selector:matchLabels:app: mcp-client-demotemplate:metadata:labels:app: mcp-client-demospec:containers:- name: mcp-client-demoimage: mcp-client-demo:1.0.0ports:- containerPort: 8081env:- name: MINIMAX_API_KEYvalueFrom:secretKeyRef:name: minimax-secretkey: api-key- name: SPRING_AI_MCP_CLIENT_CONNECTIONS_BUSINESS-SERVER_URLvalue: "http://business-mcp-server/mcp"- name: SPRING_AI_MCP_CLIENT_CONNECTIONS_FILESYSTEM-SERVER_URLvalue: "http://filesystem-mcp-server/mcp"- name: SPRING_AI_MCP_CLIENT_CONNECTIONS_PAYMENT-SERVER_URLvalue: "http://payment-mcp-server/mcp"---apiVersion: v1kind: Servicemetadata:name: mcp-client-demospec:selector:app: mcp-client-demoports:- port: 80targetPort: 8081type: ClusterIP---apiVersion: v1kind: Secretmetadata:name: minimax-secrettype: Opaquedata:api-key: eW91ci1iYXNlNjQtZW5jb2RlZC1hcGkta2V5
七、总结
通过本文的实战,我们完成了一个轻量的 MCP Client Demo 项目,包含:
- 多 MCP Server 连接
:同时连接天气、订单、文件、支付等多个 Server - 本地工具与 MCP 工具混合使用
:LLM 自动选择最合适的工具 - 动态工具加载
:基于角色配置动态加载工具(Mock 数据) - 同步和流式对话
:支持多种对话模式 - 对话历史管理
:支持多轮对话上下文 - 工具调用日志
:记录工具调用详情,方便调试 - 完整的端到端测试
:从用户请求到工具调用再到 AI 回复的全流程
关键要点回顾
适用场景建议
- 企业内部 AI 助手
:连接多个业务系统的 MCP Server,提供统一 AI 入口 - 微服务架构
:MCP Client 作为 API Gateway,后端连接多个 MCP Server 微服务 - 角色权限管理
:基于角色动态加载工具,不同角色使用不同工具集
感谢各位看官的一路陪伴,大家都再接再厉!
上一篇回顾:《Spring AI MCP Server 实战:用 Streamable HTTP 协议打造企业级 AI 工具服务》
往下拉评论更精彩~~~

🔍并发编程系列-基础并发包与各种锁机制源码分析、使用与常规实战案例解析
🔍微服务-微服务架构、数据采集、指标分析、流量控制、熔断降级与隔离集成
🔍数据结构与算法-各种排序、堆、栈、队列、链表、二叉树、Hash算法等
🔍大数据-基础知识、环境搭建与接入项目使用、Hadoop、Spark、Flink等
🔍数据库-中间件及其整合、Mysql、Redis、Mongodb
🔍elasticsearch-ES基础语法使用、各种文档分析与常见问题的处理
🔍docker-容器化相关知识,Docker部署Spring、Springboot、SpringCloud
🔍kubernetes-相关概念、架构、流程概述,架构的搭建与微服务项目各中间件部署
🔍Spring-项目架构、锁机制、问题解析、数据库与中间件整合
🔍开发工具-与开发有关的工具中间件,GIT、IDEA、CANAL、JENKINS、CICD
🔍故障排查-JVM内存泄漏、CPU超高、Canal同步定位、线上监控诊断Arthas等
点击这里关注我,记得星标哦~
夜雨聆风