项目结构,本文章只列出了mcp-client的示例代码

添加依赖
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kujie.ai</groupId>
<artifactId>mcp-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>spring-ai-mcp-server-demo</module>
<module>spring-ai-mcp-client-demo</module>
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.boot.version>3.5.0</spring.boot.version>
<spring.ai.version>1.1.0-SNAPSHOT</spring.ai.version>
<spring-ai-open-ai.version>1.1.0-SNAPSHOT</spring-ai-open-ai.version>
<spring-ai-milvus-store.version>1.0.0-SNAPSHOT</spring-ai-milvus-store.version>
<mysql.version>8.0.32</mysql.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- 版本与spring-boot-starter-web保持一致 -->
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring.ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-milvus-store</artifactId>
<version>${spring-ai-milvus-store.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.kujie.ai</groupId>
<artifactId>mcp-demo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>spring-ai-mcp-client-demo</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-ollama</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-chat-memory-repository-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-milvus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-rag</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
yml配置
yaml
server:
port: 9999
servlet:
encoding:
charset: UTF-8
spring:
servlet:
multipart:
max-file-size: 10MB # 单个文件最大大小
max-request-size: 50MB # 整个请求最大大小
datasource:
url: jdbc:mysql://localhost:3306/ck_test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&allowMultiQueries=true&tinyInt1isBit=false&allowLoadLocalInfile=true&allowLocalInfile=true&allowUrl
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
application:
name: spring-ai-mcp-client-demo
ai:
vectorstore:
milvus:
client:
host: 你的milvus服务器ip
port: 19530
username:
password:
connectTimeoutMs: 30000 # 30 秒
keepAliveTimeMs: 60000 # 60 秒
databaseName: "default"
collectionName: "default"
embeddingDimension: 1024
indexType: IVF_FLAT
metricType: COSINE
ollama:
base-url: http://你的ollama服务器ip:11434
chat:
options:
model: qwen3:8b
f16-k-v: true
temperature: 0.7
# embedding模型dimension需要与向量数据库配置的dimension一致不然会报错。
# 如果报错则看下面的EmbeddingClientConfig代码
embedding:
options:
model: mxbai-embed-large:latest
f16-k-v: true
temperature: 0.7
mcp:
client:
type: ASYNC
sse:
connections:
server1:
url: http://你的mcp服务ip:9998
sse-endpoint: /sse
stdio:
# 第三方提供的mcp服务比如:高德地图等等
servers-configuration: classpath:mcp-servers-config.json
chat:
memory:
repository:
jdbc:
initialize-schema: always
# spring ai会话记忆自动创建数据库表,详细的可以参照源码JdbcChatMemoryRepositoryRuntimeHints类
schema: classpath:org/springframework/ai/chat/memory/repository/jdbc/schema-mariadb.sql
聊天会话记录参考下面两张图:

mcp-servers-config.json参考下面:
使用时请注意,我这里实例是高德的mcp服务,调用之前需要在环境变量中配置AMAP_MAPS_API_KEY,这个值可以去高德开放平台申请。还需要安装npx。

perl
{
"mcpServers": {
"amap-maps": {
"command": "cmd",
"args": [
"/c",
"npx",
"-y",
"@amap/amap-maps-mcp-server"
],
"env": {}
}
}
}
配置
rag配置:
kotlin
package com.kujie.config;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* rag配置
*
* @author check
* @date 2025-07-26
*/
@Configuration
public class AdvisorConfig {
@Bean
public Advisor advisorAdvisor(MilvusVectorStore milvusVectorStore) {
return RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.50)
.vectorStore(milvusVectorStore)
.build())
.queryAugmenter(ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build())
.build();
}
}
聊天会话客户端配置:
java
package com.kujie.config;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.ChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.memory.repository.jdbc.JdbcChatMemoryRepository;
import org.springframework.ai.chat.memory.repository.jdbc.MysqlChatMemoryRepositoryDialect;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.JdbcTransactionManager;
import javax.sql.DataSource;
import java.util.List;
/**
* 客户端 配置
*
* @author check
* @date 2025-07-24
*/
@Configuration
public class ChatClientConfig {
@Autowired
private DataSource dataSource;
@Autowired
private ChatMemoryRepository chatMemoryRepository;
@Bean
public ChatMemory mysqlChatMemoryRepository() {
// 会话记忆数据库配置,会话记忆有两种一种是jdbc会话记忆一种直接存在内存中
// 我的配置是jdbc
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
ChatMemoryRepository chatMemoryRepository = JdbcChatMemoryRepository.builder()
.jdbcTemplate(jdbcTemplate)
.dialect(new MysqlChatMemoryRepositoryDialect())
.transactionManager(new JdbcTransactionManager(dataSource))
.build();
return MessageWindowChatMemory.builder()
.chatMemoryRepository(chatMemoryRepository)
.maxMessages(10)
.build();
}
@Bean
public ChatClient buildCharClient(ChatClient.Builder ollamaChatModel,
ToolCallbackProvider tools,
ChatMemory chatMemory,
MilvusVectorStore milvusVectorStore,
Advisor advisor) {
return ollamaChatModel
.defaultToolCallbacks(tools.getToolCallbacks())
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build(), new QuestionAnswerAdvisor(milvusVectorStore), advisor)
.build();
}
}
milvus向量数据库在连接时会做一层缓存,参考源码AbstractMilvusGrpcClient类

如果第一次向向量数据库中存储数据时报错,则需要清除对应的collectionName和databaseName的缓存不然dimension就算修改了使用同样的collectionName和databaseName也会报错
向量数据库配置
less
package com.kujie.config;
import io.milvus.client.MilvusServiceClient;
import io.milvus.param.*;
import io.milvus.param.collection.DropCollectionParam;
import org.springframework.ai.embedding.TokenCountBatchingStrategy;
import org.springframework.ai.ollama.OllamaEmbeddingModel;
import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 手动配置向量数据库,在yaml文件中配置了就不需要配置这个了
*
* @author check
* @date 2025-07-25
*/
@Configuration
public class MilvusVectorClientConfig {
@Bean
public MilvusVectorStore vectorStore(@Autowired MilvusServiceClient milvusClient, @Autowired OllamaEmbeddingModel ollamaEmbeddingModel) {
// 清除缓存
R<RpcStatus> rpcStatusR = milvusClient.dropCollection(DropCollectionParam.newBuilder().withCollectionName("default").withDatabaseName("default").build());
return MilvusVectorStore.builder(milvusClient, ollamaEmbeddingModel)
.collectionName("default")
.databaseName("default")
.indexType(IndexType.IVF_FLAT)
.metricType(MetricType.COSINE)
.batchingStrategy(new TokenCountBatchingStrategy())
.initializeSchema(true)
.embeddingDimension(1024)
.build();
}
@Bean
public MilvusServiceClient milvusClient() {
return new MilvusServiceClient(ConnectParam.newBuilder().withHost("你的milvus服务器ip").withPort(19530).build());
}
}
通过接口流式回答:
java
package com.kujie.controller;
import com.kujie.utils.TimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.SystemPromptTemplate;
import org.springframework.ai.ollama.api.OllamaOptions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.time.temporal.WeekFields;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/ai")
@CrossOrigin(origins = "*")
public class ChatController {
@Autowired
private ChatClient chatClient;
@GetMapping(value = "/steamChat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> steamChat(@RequestParam("input") String input) {
String systemPrompt = null;
try {
// 这里读取的是我的提示词模板
ClassPathResource resource = new ClassPathResource("template/system-prompt.txt");
systemPrompt = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
} catch (IOException e) {
log.error("提示词提取失败", e);
}
SystemPromptTemplate systemPromptTemplate = new SystemPromptTemplate(systemPrompt);
String systemMessage = systemPromptTemplate.render(Map.of("test","test");
Prompt prompt = new Prompt(
List.of(
new SystemMessage(systemMessage),
new UserMessage(input)
),
OllamaOptions.builder().build()
);
// 流式回答
return this.chatClient.prompt(prompt)
.stream()
.content();
}
}