通过Spring AI框架搭建mcp服务端说明
简介
MCP(Model Context Protocol,模型上下文协议)是由 Anthropic 提出的开源协议,旨在标准化大语言模型(LLM)与外部数据源、工具的集成方式,实现安全高效的双向连接。
项目结构
代码示例
这里是以查询城市天气的mcp server做的示例,框架用的是spring ai
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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>mcp-server-demo</artifactId>
<properties>
<java.version>17</java.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-SNAPSHOT</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>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.51</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.32</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
application.properties
# spring.main.web-application-type=none
# NOTE: You must disable the banner and the console logging
# to allow the STDIO transport to work !!!
spring.main.banner-mode=off
# logging.pattern.console=
spring.ai.mcp.server.name=my-mcp-server
spring.ai.mcp.server.version=0.0.1
spring.ai.mcp.server.sse-message-endpoint=/message
ServerApplication(启动类)
package org.springframework.ai.mcp.sample.server;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import io.modelcontextprotocol.server.McpServerFeatures.SyncToolSpecification;
import io.modelcontextprotocol.server.McpSyncServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.mcp.McpToolUtils;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class ServerApplication {
private static final Logger logger = LoggerFactory.getLogger(ServerApplication.class);
CountDownLatch latch = new CountDownLatch(1);
public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
}
@GetMapping("/updateTools")
public String greeting() {
latch.countDown();
return "Update signal received!";
}
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}
@Bean
public CommandLineRunner commandRunner(McpSyncServer mcpSyncServer) {
return args -> {
logger.info("Server: " + mcpSyncServer.getServerInfo());
latch.await();
List<SyncToolSpecification> newTools = McpToolUtils
.toSyncToolSpecifications(ToolCallbacks.from(new MathTools()));
for (SyncToolSpecification newTool : newTools) {
logger.info("Add new tool: " + newTool);
mcpSyncServer.addTool(newTool);
}
logger.info("Tools updated: ");
};
}
}
WeatherService(查询天气服务)
/*
* Copyright 2025 - 2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.ai.mcp.sample.server;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;
@Service
public class WeatherService {
private final RestClient restClient;
public WeatherService() {
this.restClient = RestClient.builder()
.defaultHeader("Accept", "application/geo+json")
.defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
.build();
}
@JsonIgnoreProperties(ignoreUnknown = true)
public record WeatherResponse(@JsonProperty("current") Current current) { // @formatter:off
public record Current(
@JsonProperty("time") LocalDateTime time,
@JsonProperty("interval") int interval,
@JsonProperty("temperature_2m") double temperature_2m) {
}
} // @formatter:on
@Tool(description = "Get the temperature (in celsius) for a specific location") // @formatter:off
public WeatherResponse weatherForecast(
@ToolParam(description = "The location latitude") double latitude,
@ToolParam(description = "The location longitude") double longitude,
ToolContext toolContext) { // @formatter:on
WeatherResponse weatherResponse = restClient
.get()
.uri("https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m",
latitude, longitude)
.retrieve()
.body(WeatherResponse.class);
return weatherResponse;
}
}
启动服务
调试验证
这里直接使用idea的扩展插件通义灵码做的测试,大家也可以用cursor或trae进行测试
这里就可以看到自己写好的mcp服务了