基于上篇文章的MCP Server Tools的实现,支持redis,elasticsearch业务数据查询,封装tools
pom
<?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>org.my.mcp.tools</groupId>
<artifactId>mcp_tools</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.7</version>
<relativePath />
</parent>
<properties>
<java.version>21</java.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>2.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
<exclusions>
<exclusion>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Netty DNS resolver for macOS -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-resolver-dns-native-macos</artifactId>
<classifier>osx-aarch_64</classifier>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.54</version>
</dependency>
<!-- Redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.38.1</version>
</dependency>
<!-- test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 强制使用兼容的 JUnit Platform 版本 -->
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.10.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-engine</artifactId>
<version>1.10.3</version>
<scope>test</scope>
</dependency>
<!-- Elasticsearch REST Client -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.13.4</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<id>central-portal-snapshots</id>
<name>Central Portal Snapshots</name>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>
application.properties
spring.main.banner-mode=off
# ===== MCP Server é
ç½® =====
spring.ai.mcp.server.name=my-tools-server
spring.ai.mcp.server.version=1.0.0
spring.ai.mcp.server.protocol=STREAMABLE
# å¯ç¨ @McpTool æ³¨è§£æ«æ
spring.ai.mcp.server.tool-callback-providers=annotation
# ===== log =====
logging.file.name=./logs/tools-mcp-server.log
# ===== redis =====
spring.redis.host=ip
spring.redis.port=6379
spring.redis.password=password
spring.redis.database=0
# ===== ES =====
elasticsearch.user=elastic
elasticsearch.password=elastic
elasticsearch.cluster-nodes=ip:9200
# ES index
elasticsearch.user-index=user_index
elasticsearch.bus-index=bus_index
EsConfig
package org.my.mcp.tools.config;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class EsConfig {
@Bean
public ElasticsearchClient elasticsearchClient(EsProperties properties) {
String[] nodes = properties.getClusterNodes().split(",");
HttpHost[] hosts = new HttpHost[nodes.length];
for (int i = 0; i < nodes.length; i++) {
String[] parts = nodes[i].trim().split(":");
hosts[i] = new HttpHost(parts[0], Integer.parseInt(parts[1]), "http");
}
RestClientBuilder builder = RestClient.builder(hosts);
// 设置认证
if (properties.getUser() != null && !properties.getUser().isBlank()) {
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
AuthScope.ANY,
new UsernamePasswordCredentials(properties.getUser(), properties.getPassword())
);
builder.setHttpClientConfigCallback(httpClientBuilder ->
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
);
}
RestClient restClient = builder.build();
ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
return new ElasticsearchClient(transport);
}
}
EsProperties
package org.my.mcp.tools.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
public class EsProperties {
private String user;
private String password;
private String clusterNodes;
private String userIndex = "user";
public String getUserIndex() {
return userIndex;
}
public void setUserIndex(String userIndex ) {
this.userIndex = userIndex ;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getClusterNodes() {
return clusterNodes;
}
public void setClusterNodes(String clusterNodes) {
this.clusterNodes = clusterNodes;
}
}
RedissonConfig
package org.my.mcp.tools.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host:localhost}")
private String host;
@Value("${spring.redis.port:6379}")
private int port;
@Value("${spring.redis.password:}")
private String password;
@Value("${spring.redis.database:0}")
private int database;
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
String address = "redis://" + host + ":" + port;
config.useSingleServer()
.setAddress(address)
.setDatabase(database)
.setConnectionPoolSize(10)
.setConnectionMinimumIdleSize(2);
if (password != null && !password.isBlank()) {
config.useSingleServer().setPassword(password);
}
// 使用 StringCodec,以纯字符串方式读写 Redis
config.setCodec(new StringCodec());
return Redisson.create(config);
}
}
RedisService
package org.my.mcp.tools.service;
import com.alibaba.fastjson2.JSONObject;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.mcp.annotation.McpTool;
import org.springframework.stereotype.Service;
@Service
public class RedisService {
private static final Logger log = LoggerFactory.getLogger(RedisService.class);
private final RedissonClient redissonClient;
public RedisService(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
@McpTool(description = "获取指定key值的用户相关信息")
public String redisHgetall(String key) {
log.info("redisHgetall: key={}", key);
try {
key = key.substring(key.indexOf(":")+1, key.length());
RMap<String, String> map = redissonClient.getMap(key);
java.util.Map<String, String> entries = map.readAllMap();
JSONObject result = new JSONObject();
result.put("key", key);
result.put("count", entries.size());
if (!entries.isEmpty()) {
result.put("fields", entries);
}
return result.toJSONString();
} catch (Exception e) {
log.error("redisHgetall 失败", e);
return buildError(e.getMessage());
}
}
private String buildError(String message) {
JSONObject error = new JSONObject();
error.put("error", message);
return error.toJSONString();
}
}
EsMcpService
package org.my.mcp.tools.service;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.my.mcp.tools.config.EsProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.mcp.annotation.McpTool;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class EsMcpService {
private static final Logger log = LoggerFactory.getLogger(EsMcpService.class);
private final ElasticsearchClient esClient;
private final String userIndex;
public EsMcpService(ElasticsearchClient esClient, EsProperties esProperties) {
this.esClient = esClient;
this.userIndex= esProperties.getUserIndex();
}
@McpTool(description = "根据编号(code)查询用户信息")
public String esQueryUserByCode(String code) {
log.info("esQueryUserByCode: code={}", code);
try {
String[] list = code.split(",");
List<String> terms = java.util.Arrays.stream(list )
.map(String::trim)
.filter(v -> !v.isEmpty())
.toList();
SearchResponse<JSONObject> response = esClient.search(s -> s
.index(userIndex)
.size(terms.size())
.query(q -> q.terms(t -> t.field("code").terms(tf -> tf.value(terms.stream().map(FieldValue::of).toList())))),
JSONObject.class);
return buildUserResult(response);
} catch (Exception e) {
log.error("esQueryUserByCode失败", e);
return buildError(e.getMessage());
}
}
/**
* 构建统一返回结果
*/
private String buildUserResult(SearchResponse<JSONObject> response) {
JSONObject result = new JSONObject();
long total = response.hits().total() != null
? response.hits().total().value()
: response.hits().hits().size();
result.put("total", total);
JSONArray hitsArray = new JSONArray();
for (Hit<JSONObject> hit : response.hits().hits()) {
JSONObject user= hit.source();
if (vehicle != null) {
user.put("_id", hit.id());
user.put("_score", hit.score());
hitsArray.add(user);
}
}
result.put("hits", hitsArray);
log.info("查询成功, 命中 {} 条", total);
return result.toJSONString();
}
private String buildError(String message) {
JSONObject error = new JSONObject();
error.put("error", message);
return error.toJSONString();
}
}
ServerStart 启动类
package org.my.mcp.tools;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class ServerStart {
public static void main(String[] args) {
SpringApplication.run(ServerStart.class, args);
}
}
&spm=1001.2101.3001.5002&articleId=162925403&d=1&t=3&u=fee8214396bf471ab1dd6e11b5508065)

被折叠的 条评论
为什么被折叠?



