spring ai mcp
本文主要解析了 Spring AI 中的 MCP(Model Context Protocol) 的使用方式与核心机制,通过一个完整的 Demo 详细展示了 MCP 如何帮助开发者在统一的上下文模型协议下,高效地组织和管理大语言模型(LLM)的调用流程。文章首先介绍了 MCP 的设计初衷与在 Spring AI 架构中的定位,强调其作为模型调用的上下文协议层,负责封装模型请求、响应、提示词(Prompt)、参数等上下文信息。
spring ai chat
spring ai chat的chatClient的配置类是ChatClientAutoConfiguration,代码如下:
@Bean
@ConditionalOnMissingBean
ChatClientBuilderConfigurer chatClientBuilderConfigurer(ObjectProvider<ChatClientCustomizer> customizerProvider) {
ChatClientBuilderConfigurer configurer = new ChatClientBuilderConfigurer();
configurer.setChatClientCustomizers(customizerProvider.orderedStream().toList());
return configurer;
}
@Bean
@Scope("prototype")
@ConditionalOnMissingBean
ChatClient.Builder chatClientBuilder(ChatClientBuilderConfigurer chatClientBuilderConfigurer, ChatModel chatModel,
ObjectProvider<ObservationRegistry> observationRegistry,
ObjectProvider<ChatClientObservationConvention> observationConvention) {
ChatClient.Builder builder = ChatClient.builder(chatModel,
observationRegistry.getIfUnique(() -> ObservationRegistry.NOOP),
observationConvention.getIfUnique(() -> null));
return chatClientBuilderConfigurer.configure(builder);
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = ChatClientBuilderProperties.CONFIG_PREFIX + ".observations", name = "include-input",
havingValue = "true")
ChatClientInputContentObservationFilter chatClientInputContentObservationFilter() {
logger.warn(
"You have enabled the inclusion of the input content in the observations, with the risk of exposing sensitive or private information. Please, be careful!");
return new ChatClientInputContentObservationFilter();
}
ChatClient.Builder这个是构建ChatClient的类,在方法自动注入ChatModel,这个是不同平台差异性在具体ChatModel实现类完成,chatClient公共接口类,不同平台自己实现ChatModel,默认ChatClient的实现类DefaultChatClient。ChatModel类继承关系如下:
spring ai mcp依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
<version>1.0.0-M6</version>
</dependency>
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp</artifactId>
<version>0.8.1</version>
</dependency>
spring mcp demo
代码如下:
@SpringBootApplication
@ApplicationRun
public class BigModelApplication {
public static void main(String[] args) {
SpringApplication.run(BigModelApplication.class, args);
}
@Bean
public CommandLineRunner chatbot(@Qualifier("openAiAdaptationChatModel") ChatModel aliChatModel,ChatClient.Builder chatClientBuilder, List<McpSyncClient> mcpSyncClients) {
return args -> {
var chatClient = ChatClient.builder(aliChatModel)
.defaultOptions(OpenAiChatOptions.builder()
.temperature(0.1d).maxTokens(2000)
.build())
.defaultSystem("You are useful assistant and can perform web searches Brave's search API to reply to your questions.")
.defaultTools(new SyncMcpToolCallbackProvider(mcpSyncClients))
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
.build();
// Start the chat loop
System.out.println("\nI am your AI assistant.\n");
try (Scanner scanner = new Scanner(System.in)) {
while (true) {
System.out.print("\nUSER: ");
System.out.println("\nASSISTANT: " +
chatClient.prompt(scanner.nextLine())
// Get the user input
.call()
.content());
}
}
};
}
}
demo是根据spring官网提供demo mcp client brave chatbot,官网代码:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner chatbot(ChatClient.Builder chatClientBuilder, List<McpSyncClient> mcpSyncClients) {
return args -> {
var chatClient = chatClientBuilder
.defaultSystem("You are useful assistant and can perform web searches Brave's search API to reply to your questions.")
.defaultTools(new SyncMcpToolCallbackProvider(mcpSyncClients))
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
.build();
// Start the chat loop
System.out.println("\nI am your AI assistant.\n");
try (Scanner scanner = new Scanner(System.in)) {
while (true) {
System.out.print("\nUSER: ");
System.out.println("\nASSISTANT: " +
chatClient.prompt(scanner.nextLine()) // Get the user input
.call()
.content());
}
}
};
}
}
跟官网不一样的是在构建chatClient时,官网使用的是ChatClient.Builder chatClientBuilder 自动注入构建chatClient,对应的spring自动配置类是ChatClientAutoConfiguration,博主采用的是上文spring ai chat摘要中的自定义chatModel,文章可以博主另外一篇文章基于Spring AI实现DeepSeek-R1推理模型的推理过程内容获取。
对代码分析:
var chatClient = chatClientBuilder
.defaultSystem("You are useful assistant and can perform web searches Brave's search API to reply to your questions.")
//指定工具,这里指定默认的工具是spring ai基于mcp封装类,mcp相关的工具通过这个方法 都会放到chatClient中
.defaultTools(new SyncMcpToolCallbackProvider(mcpSyncClients))
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
.build();
对应的spring ai mcp相关的配置:
spring:
ai:
openai:
api-key: ********************
base-url:
chat:
options:
model: qwen-72b
completions-path: /v1/chat/completions
api-key: sk-ddddddddddddddddddd
mcp:
client:
enabled: true
stdio:
servers-configuration: classpath:mcp-servers.json
demo中采用是跟claude desk使用stdio协议,通信架构图:
由于我们是采用的stdio协议,需要安装node.js环境,使用一下命令:
npm install -g npx
debug时,openAiApi这个类的方法chatCompletionEntity:
Assert.notNull(chatRequest, "The request body can not be null.");
Assert.isTrue(!chatRequest.stream(), "Request must set the stream property to false.");
Assert.notNull(additionalHttpHeader, "The additional HTTP headers can not be null.");
return this.restClient.post()
.uri(this.completionsPath)
.headers(headers -> headers.addAll(additionalHttpHeader))
.body(chatRequest)
.retrieve()
.toEntity(ChatCompletion.class);
获取到chatRequest,使用:
ObjectMapper ob =new ObjectMapper();
ob.writeValueAsString(chatRequest);
打印出来可以看到请求大模型的参数:
{
"messages": [
{
"content": "You are useful assistant and can perform web searches Brave's search API to reply to your questions.",
"role": "system"
},
{
"content": "搜索中美最近关于加税的新闻",
"role": "user"
}
],
"model": "uranmm-40B",
"max_tokens": 2000,
"stream": false,
"temperature": 0.1,
"tools": [
{
"type": "function",
"function": {
"description": "Performs a web search using the Brave Search API, ideal for general queries, news, articles, and online content. Use this for broad information gathering, recent events, or when you need diverse web sources. Supports pagination, content filtering, and freshness controls. Maximum 20 results per request, with offset for pagination. ",
"name": "brave_web_search",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query (max 400 chars, 50 words)"
},
"count": {
"type": "number",
"description": "Number of results (1-20, default 10)",
"default": 10
},
"offset": {
"type": "number",
"description": "Pagination offset (max 9, default 0)",
"default": 0
}
},
"required": [
"query"
]
}
}
},
{
"type": "function",
"function": {
"description": "Searches for local businesses and places using Brave's Local Search API. Best for queries related to physical locations, businesses, restaurants, services, etc. Returns detailed information including:\n- Business names and addresses\n- Ratings and review counts\n- Phone numbers and opening hours\nUse this when the query implies 'near me' or mentions specific locations. Automatically falls back to web search if no local results are found.",
"name": "brave_local_search",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Local search query (e.g. 'pizza near Central Park')"
},
"count": {
"type": "number",
"description": "Number of results (1-20, default 5)",
"default": 5
}
},
"required": [
"query"
]
}
}
}
]
}
所以需要模型支持 function call功能才行。
·配置好以后就可以运行服务了,