跳到主要内容

通过SpringBoot构建你的MCP Server

1. 背景

在传统的人机交互中,AI 获取信息的来源主要依赖于预先训练时所投喂的大规模语料库,或是在有限权限下的联网支持。因此,如果你希望 AI 分析你自己的网站内容, 或者结合你在知乎上的所有回答进行深入分析,传统 AI 是无法完成的

MCP Server 正好解决了这个问题,它的本质是为 AI 接入更多信息源,相当于为 AI 扩展了「感知范围」。通过 MCP Server,你可以让 AI 实时访问你提供的数据,比如网页、数据库、文档接口等,从而进行更具上下文的分析与响应。

当然,这种能力的代价是:你需要自己整合并维护这些希望让 AI 访问的数据源

那么怎么做?下面的内容会用Java SpringBoot 实现一个最简单的案例:搭建一个MCP 天气服务器,让Claude Desktop能够知道实时的天气。

2. 通过Spring AI构建Spring AI MCP Server

对于Java开发者来说,SpringBoot是再熟悉不过的框架,所以这里我们直接下载已经构建的Spring AI MCP Weather STDIO Server项目进行讲解。

代码位置:starter-stdio-server, 另外你可以找到更多的案例在代码仓库:spring-ai-examples

2.1 项目解释:pom.xml

从pom.xml中我们能很看出,是直接使用的spring-ai-starter-mcp-server,spring-web两个依赖,没有其他的附加依赖。

  • spring-ai-starter-mcp-server 是 Spring AI 官方提供的一个 用于构建自定义 MCP Server 的依赖。
  • 而spring-web 则更熟悉的,是 Spring Framework 中的一个核心模块,它提供了构建 Web 应用程序所需的基础功能。
pom.xml
    <dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
</dependencies>

2.2 项目解释:application.properties

application.properties
spring.main.web-application-type=none

# 注意:您必须禁用横幅和控制台日志记录以便让 STDIO 传输系统正常工作!!!

spring.main.banner-mode=off
logging.pattern.console=

spring.ai.mcp.server.name=my-weather-server
spring.ai.mcp.server.version=0.0.1

logging.file.name=./model-context-protocol/weather/starter-stdio-server/target/mcp-weather-stdio-server.log


  • spring.main.web-application-type=none:关闭 Spring Boot 的 Web 功能(不启动 Servlet 或 WebFlux 容器)。
  • spring.main.banner-mode=off :关闭 Spring Boot 启动时的 ASCII 艺术字横幅,因为横幅会写到 stdout,可能干扰 STDIO 通信协议(AI 工具依赖 stdout 的 JSON 格式)。
  • logging.pattern.console= : 清空控制台日志格式,防止出现前缀、时间戳、级别等多余内容。确保标准输出中只输出结构化 JSON 响应或插件数据,不被干扰。
  • logging.file.name=./model-context-protocol/weather/starter-stdio-server/target/mcp-weather-stdio-server.log :将日志输出写入指定的文件路径。避免将日志打印到控制台(stdout),干扰 MCP 的 stdio 通信;同时方便后期排查问题。

2.3 具体实现

从项目结构中可以看到,整个项目只有 McpServerApplication.javaWeatherService.java 两个类

├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── org
│   │   │   └── springframework
│   │   │   └── ai
│   │   │   └── mcp
│   │   │   └── sample
│   │   │   └── server
│   │   │   ├── McpServerApplication.java
│   │   │   └── WeatherService.java
│   │   └── resources
│   │   └── application.properties
32 directories, 21 files

McpServerApplication.java 很简单,就是项目启动类,然后当中注入了一个bean。

而注入的这个 bean ToolCallbackProvider 是 Spring AI 中用于 管理和调度 AI 调用工具(Tool)时的回调处理逻辑 的接口。 Tool 是你注册给 AI 模型可以调用的函数(类似于 OpenAI 的 Function Calling 或 Tools 功能)。当 AI 触发某个 Tool 的调用时, ToolCallbackProvider 就负责:查找并调用你注册的具体处理逻辑(回调函数)。

.toolObjects(weatherService) 则是把另一个bean WeatherService 给加进去,就意味着把 weatherService 以 tool 的形式提供给AI

@SpringBootApplication
public class McpServerApplication {

public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}

@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}

}

那我们来看真正发挥作用的 WeatherService.java, 这里我调整了一下代码位置方便我们一眼就能注意到有两个方法被 @Tool注解修饰。 这个注解的主要作用就是 在Spring AI中将一个方法标记为工具

而方法入参数被注解 @ToolParam 修饰则是给方法参数添加描述信息,便于 AI 理解参数含义

而这个Service做的作用就是,当AI调用的时候,请求 api.weather.gov 接口获取天气信息,再输出给用户 • 使用 RestClient 调用 api.weather.gov 获取天气数据 • 暴露了两个 AI 可调用的工具(方法),可接入 GPT/Claude 等支持工具调用的模型 • 结合 @Tool和 MCP 协议,可以通过 ChatGPT 插件或 Spring AI Assistant 无缝接入这些后端服务

提供两个 AI 可调用的接口:

  1. getWeatherForecastByLocation(double latitude, double longitude) — 获取某地天气预报
  2. getAlerts(String state) — 获取某个美国州的天气警报
@Service
public class WeatherService {

private static final String BASE_URL = "https://api.weather.gov";
private final RestClient restClient;

public WeatherService() {
this.restClient = RestClient.builder()
.baseUrl(BASE_URL)
.defaultHeader("Accept", "application/geo+json")
.defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
.build();
}

/**
* 获取特定纬度/经度的天气预报
* @param latitude Latitude
* @param longitude Longitude
* @return The forecast for the given location
* @throws RestClientException if the request fails
*/
@Tool(description = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(double latitude, double longitude) {

var points = restClient.get()
.uri("/points/{latitude},{longitude}", latitude, longitude)
.retrieve()
.body(Points.class);

var forecast = restClient.get().uri(points.properties().forecast()).retrieve().body(Forecast.class);

String forecastText = forecast.properties().periods().stream().map(p -> {
return String.format("""
%s:
Temperature: %s %s
Wind: %s %s
Forecast: %s
""", p.name(), p.temperature(), p.temperatureUnit(), p.windSpeed(), p.windDirection(),
p.detailedForecast());
}).collect(Collectors.joining());

return forecastText;
}

/**
* 获取特定区域的警报
* @param 州区号。两个字母的美国州代码(如CA, NY)
* @return 人类可读的警报信息
* @throws RestClientException if the request fails
*/
@Tool(description = "Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY)")
public String getAlerts(@ToolParam( description = "Two-letter US state code (e.g. CA, NY") String state) {
Alert alert = restClient.get().uri("/alerts/active/area/{state}", state).retrieve().body(Alert.class);

return alert.features()
.stream()
.map(f -> String.format("""
Event: %s
Area: %s
Severity: %s
Description: %s
Instructions: %s
""", f.properties().event(), f.properties.areaDesc(), f.properties.severity(),
f.properties.description(), f.properties.instruction()))
.collect(Collectors.joining("\n"));
}

@JsonIgnoreProperties(ignoreUnknown = true)
public record Points(@JsonProperty("properties") Props properties) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record Props(@JsonProperty("forecast") String forecast) {
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
public record Forecast(@JsonProperty("properties") Props properties) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record Props(@JsonProperty("periods") List<Period> periods) {
}

@JsonIgnoreProperties(ignoreUnknown = true)
public record Period(@JsonProperty("number") Integer number, @JsonProperty("name") String name,
@JsonProperty("startTime") String startTime, @JsonProperty("endTime") String endTime,
@JsonProperty("isDaytime") Boolean isDayTime, @JsonProperty("temperature") Integer temperature,
@JsonProperty("temperatureUnit") String temperatureUnit,
@JsonProperty("temperatureTrend") String temperatureTrend,
@JsonProperty("probabilityOfPrecipitation") Map probabilityOfPrecipitation,
@JsonProperty("windSpeed") String windSpeed, @JsonProperty("windDirection") String windDirection,
@JsonProperty("icon") String icon, @JsonProperty("shortForecast") String shortForecast,
@JsonProperty("detailedForecast") String detailedForecast) {
}
}

@JsonIgnoreProperties(ignoreUnknown = true)
public record Alert(@JsonProperty("features") List<Feature> features) {

@JsonIgnoreProperties(ignoreUnknown = true)
public record Feature(@JsonProperty("properties") Properties properties) {
}

@JsonIgnoreProperties(ignoreUnknown = true)
public record Properties(@JsonProperty("event") String event, @JsonProperty("areaDesc") String areaDesc,
@JsonProperty("severity") String severity, @JsonProperty("description") String description,
@JsonProperty("instruction") String instruction) {
}
}
}

2.3 使用项目

当你想要启动这个项目的时候,需要Java 17以上版本,Maven 3.6以上以上版本,此时你肯定很顺滑直接启动了。

但是实际上使用的方式需要先打成一个Jar包,在项目根目录执行:mvn package, 就能在target目录下看到 mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar

接着需要你本地已经安装了Claude for Desktop, 没有的话可以在这里下载:https://claude.ai/download

安装后,使用文本编辑器打开:

~/Library/Application Support/Claude/claude_desktop_config.json

没有的话就对这个文件进行创建。

接着写入下面的内容,记得修改 /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar 为你打出jar包的绝对路径

{
"mcpServers": {
"spring-ai-mcp-weather": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-jar",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
]
}
}
}

最后一步,重启你的 Claude,然后你甚至能直接问AI: 你接入了几个MCP server

接着AI会告诉你的。

然后你再问他,美国洛杉矶天气如何,就会实际调用你的这个jar包的

到此大功告成!快去扩展你的MCP Server吧。

3. 信息来源

Model Context Protocol|面向服务器开发者