项目结构,本文章只列出了mcp-client的示例代码
添加依赖
<?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 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配置
server: port: 9999 servlet: encoding: charset: UTF-8spring: 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。
{ "mcpServers": { "amap-maps": { "command": "cmd", "args": [ "/c", "npx", "-y", "@amap/amap-maps-mcp-server" ], "env": {} } }}
配置
rag配置:
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 */@Configurationpublic 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(); }}
聊天会话客户端配置:
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 */@Configurationpublic 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也会报错
向量数据库配置
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 */@Configurationpublic 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()); }}
通过接口流式回答:
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(); }}