随着大模型技术的快速发展,Model Coordination Protocol (MCP) 逐渐成为连接本地系统和远程AI服务的重要桥梁。Spring AI 是 Spring 官方推出的 AI 开发框架,支持多种语言模型接口,而 MCP Client 则是其集成远程推理能力的核心组件之一。
本文将详细介绍如何在 Spring Boot 项目中配置和使用 Spring AI 的 MCP Client,包括环境准备、依赖引入、配置方式、代码实现、扩展机制以及常见问题排查等内容。
Model Coordination Protocol (MCP) 是一种用于协调本地应用与远程 AI 模型服务之间交互的协议。它允许你:
Spring AI 提供了对 MCP 协议的支持,通过 spring-ai-mcp-client
模块可以轻松对接基于 MCP 协议的模型服务。
<dependencies>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-coreartifactId>
<version>0.8.1version>
dependency>
<dependency>
<groupId>org.springframework.aigroupId>
<artifactId>spring-ai-mcp-clientartifactId>
<version>0.8.1version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webfluxartifactId>
dependency>
dependencies>
你需要一个支持 MCP 协议的服务端,例如:
MCP 服务通常需要暴露以下接口:
方法 | 路径 | 功能 |
---|---|---|
POST | /v1/models/{model}/generate |
生成文本 |
POST | /v1/models/{model}/chat |
对话模式 |
GET | /v1/models |
获取可用模型列表 |
spring:
ai:
mcp:
client:
base-url: http://localhost:8080 # MCP Server 地址
model-name: qwen-3b # 默认模型名称
timeout: 60s # 请求超时时间
你可以通过自动装配来使用 McpClient
:
import org.springframework.ai.mcp.client.McpClient;
import org.springframework.stereotype.Service;
@Service
public class AIClientService {
private final McpClient mcpClient;
public AIClientService(McpClient mcpClient) {
this.mcpClient = mcpClient;
}
public String generateText(String prompt) {
return mcpClient.generate(prompt);
}
}
如果你希望更灵活地控制请求,也可以直接注入 WebClient
:
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
@Service
public class CustomAIService {
private final WebClient webClient;
public CustomAIService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder.baseUrl("http://localhost:8080/v1").build();
}
public Mono<String> chatWithModel(String model, String prompt) {
var body = Map.of("model", model, "prompt", prompt);
return webClient.post()
.uri("/models/{model}/generate", model)
.bodyValue(body)
.retrieve()
.bodyToMono(Map.class)
.map(response -> response.get("content").toString());
}
}
Spring AI 支持流式响应处理,适用于需要逐步接收模型输出的场景。
Flux<String> stream = mcpClient.stream("请写一首关于夏天的诗");
stream.subscribe(System.out::println); // 输出每一行生成内容
你可以在运行时动态指定不同的模型名称:
String response = mcpClient.withModel("llama-3-8b").generate("讲个笑话");
或使用配置文件设置默认模型,并在代码中覆盖:
spring:
ai:
mcp:
client:
base-url: http://localhost:8080
model-name: mistral-nemo
你可以通过配置类来自定义 WebClient 行为,比如添加请求头、日志拦截等。
@Configuration
public class WebClientConfig {
@Bean
public WebClientCustomizer webClientCustomizer() {
return webClient -> webClient.defaultHeader("Authorization", "Bearer your_token");
}
}
Spring AI 提供了良好的可扩展性设计,允许开发者通过自定义组件来增强或修改 McpClient
的行为。下面我们将详细介绍几个主要的扩展点及其使用方式。
McpRequestInterceptor
—— 请求拦截器这是一个在请求发送前进行处理的接口,你可以用来添加认证头、日志记录、参数修改等操作。
import org.springframework.ai.mcp.client.McpRequest;
import org.springframework.ai.mcp.client.McpRequestInterceptor;
public class AuthRequestInterceptor implements McpRequestInterceptor {
@Override
public void intercept(McpRequest request) {
// 添加认证头
request.headers().set("Authorization", "Bearer your_token_here");
// 打印请求信息
System.out.println("Intercepting request to model: " + request.model());
}
}
@Configuration
public class McpConfig {
@Bean
public McpRequestInterceptor authRequestInterceptor() {
return new AuthRequestInterceptor();
}
}
McpResponsePostProcessor
—— 响应后处理器该接口用于在接收到模型返回结果后对响应进行加工处理,例如日志记录、格式转换、异常封装等。
import org.springframework.ai.mcp.client.McpResponse;
import org.springframework.ai.mcp.client.McpResponsePostProcessor;
public class LoggingResponsePostProcessor implements McpResponsePostProcessor {
@Override
public void process(McpResponse response) {
System.out.println("Received response from model: " + response.model());
System.out.println("Response content length: " + response.content().length());
}
}
@Bean
public McpResponsePostProcessor loggingResponsePostProcessor() {
return new LoggingResponsePostProcessor();
}
McpModelSelector
—— 模型选择器这个扩展点用于根据上下文动态选择使用的模型。比如可以根据用户身份、输入内容类型、负载情况等决定调用哪个模型。
import org.springframework.ai.mcp.client.McpModelSelector;
public class SmartModelSelector implements McpModelSelector {
@Override
public String selectModel(String input) {
if (input.contains("code")) {
return "codellama";
} else if (input.contains("math")) {
return "deepseek-math";
} else {
return "qwen-3b"; // 默认模型
}
}
}
@Bean
public McpModelSelector smartModelSelector() {
return new SmartModelSelector();
}
注意:需要确保你的
McpClient
配置启用了该选择器。
你还可以通过 WebClientCustomizer
来定制底层的 WebClient
实例,实现诸如自动重试、超时控制等功能。
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.stereotype.Component;
@Component
public class RetryWebClientCustomizer implements WebClientCustomizer {
@Override
public void customize(WebClient.Builder webClientBuilder) {
webClientBuilder.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(16 * 1024 * 1024));
webClientBuilder.filters(exchangeFilterFunctions -> {
exchangeFilterFunctions.add((clientRequest, next) ->
next.exchange(clientRequest)
.doOnError(ex -> System.err.println("Error occurred during request: " + ex.getMessage()))
.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))
.filter(ex -> ex instanceof IOException));
});
}
}
应用启动时报错 Connection refused
或 TimeoutException
。
curl http://localhost:8080/v1/models
测试是否能访问模型列表application.yml
中增加超时时间:spring:
ai:
mcp:
client:
timeout: 120s
抛出 JsonProcessingException
或无法提取 content
字段。
content
字段;✅ 确保服务端返回如下格式:
{
"content": "这是模型的回答",
"model": "qwen-3b"
}
在客户端加入日志打印中间响应体:
Flux<String> stream = mcpClient.stream("你好");
stream.doOnNext(System.out::println).subscribe();
使用 exchange()
方法获取原始响应并手动处理:
Mono<ClientResponse> responseMono = webClient.post()
.uri("/models/qwen/generate")
.bodyValue(Map.of("prompt", "Hello"))
.exchangeToMono(response -> {
if (response.statusCode().isError()) {
return response.bodyToMono(String.class)
.flatMap(errorBody -> Mono.error(new RuntimeException("Server error: " + errorBody)));
}
return response.bodyToMono(Map.class);
});
调用 .stream()
方法时没有输出或抛出异常。
text/event-stream
类型的响应;检查服务端是否返回正确的 Content-Type:
Content-Type: text/event-stream
确保服务端每行返回一个 data:
字段:
data: {"content":"这"}
data: {"content":"是"}
data: {"content":"一"}
data: {"content":"句"}
data: {"content":"话"}
在客户端使用 retrieve().bodyToFlux(String.class)
接收流式响应:
Flux<String> stream = webClient.get()
.uri("/models/qwen/chat")
.accept(MediaType.TEXT_EVENT_STREAM)
.retrieve()
.bodyToFlux(String.class);
stream.subscribe(System.out::println);
若使用默认的 McpClient
,请确保其内部 WebClient 支持流式传输。
调用 .withModel("xxx")
后仍然使用默认模型。
{model}
参数;McpModelSelector
逻辑冲突;检查服务端是否支持多个模型,并可通过 /v1/models
获取列表
检查 MCP Client 的 Base URL 是否为 /v1
开头
查看日志中实际请求的 URL 是否包含目标模型名
如果使用 McpModelSelector
,可在其 selectModel
方法中加日志调试
启用 Spring Boot 的 debug 日志级别:
logging:
level:
org.springframework.ai: DEBUG
reactor: DEBUG
在 McpRequestInterceptor
和 McpResponsePostProcessor
中加入详细的日志输出
使用 WireShark 或 Fiddler 抓包查看实际请求内容
通过本节的深入讲解,你应该已经掌握了以下能力:
McpRequestInterceptor
和 McpResponsePostProcessor
对请求和响应进行拦截和处理McpModelSelector
动态选择模型这些扩展能力和排错技巧对于构建稳定、灵活的企业级 AI 应用非常关键。如果你正在开发一个生产级别的系统,建议将这些扩展点作为标准模块进行封装复用。