Java也能玩转MCP!
目前主流 MCP Server 的开发语言多为 Python,但凭借 Java 成熟且强大的生态体系,在实际业务开发场景中,绝大多数业务的后端开发仍以 Java 为主。Spring AI 提供了对 MCP 的支持实现,这极大地方便了原有 Java 应用对 MCP 的接入。
本文直接上干货,来实践使用Java构建mcp server和mcp client。
Part1
什么是MCP
MCP(Model Context Protocol,模型上下文协议)是一种标准化的通信协议,旨在连接 AI 模型与工具链,提供统一的接口以支持动态工具调用、资源管理、对话状态同步等功能。它允许开发者构建灵活的 AI 应用程序,与不同的模型和工具进行交互,同时保持协议的可扩展性和跨语言兼容性。
*感兴趣的伙伴欢迎查看本专栏关于MCP的系列文章:
1.爆火的MCP背后,为何被称作 AI 模型的“万能适配器”?
Part2
Spring AI 支持 MCP 实现
Spring AI MCP 为模型上下文协议提供 Java 和 Spring 框架集成。它使 Spring AI 应用程序能够通过标准化的接口与不同的数据源和工具进行交互,支持同步和异步通信模式。整体架构如下:
Spring AI MCP 采用模块化架构,包括以下组件:
•Spring AI 应用程序:使用 Spring AI 框架构建想要通过 MCP 访问数据的生成式 AI 应用程序
•Spring MCP 客户端:MCP 协议的 Spring AI 实现,与服务器保持 1:1 连接
通过 Spring AI MCP,可以快速搭建 MCP 客户端和服务端程序。
Part3
使用Spring AI构建mcp server
Spring AI 提供了两种机制快速搭建 MCP Server,通过这两种方式开发者可以快速向 AI 应用开放自身的能力,这两种机制如下:
•基于 stdio 的进程间通信传输,以独立的进程运行在 AI 应用本地,适用于比较轻量级的工具。
•基于 SSE(Server-Sent Events) 进行远程服务访问,需要将服务单独部署,客户端通过服务端的 URL 进行远程访问,适用于比较重量级的工具。
环境要求
•JDK17+
•pring Boot 3.0.0+
基于stdio的方式构建
添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
</dependencies>
配置项目yaml
spring:
main:
web-application-type: none
banner-mode: off
ai:
mcp:
server:
name: mcp-weather-server
version: 0.0.1
实现mcp工具
@Service
publicclass OpenMeteoService {
// OpenMeteo免费天气API基础URL
privatestaticfinal String BASE_URL = "https://api.open-meteo.com/v1";
privatefinal RestClient restClient;
public OpenMeteoService() {
this.restClient = RestClient.builder()
.baseUrl(BASE_URL)
.defaultHeader("Accept", "application/json")
.defaultHeader("User-Agent", "OpenMeteoClient/1.0")
.build();
}
// OpenMeteo天气数据模型
@JsonIgnoreProperties(ignoreUnknown = true)
public record WeatherData(
@JsonProperty("latitude") Double latitude,
@JsonProperty("longitude") Double longitude,
@JsonProperty("timezone") String timezone,
@JsonProperty("current") CurrentWeather current,
@JsonProperty("daily") DailyForecast daily,
@JsonProperty("current_units") CurrentUnits currentUnits) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record CurrentWeather(
@JsonProperty("time") String time,
@JsonProperty("temperature_2m") Double temperature,
@JsonProperty("apparent_temperature") Double feelsLike,
@JsonProperty("relative_humidity_2m") Integer humidity,
@JsonProperty("precipitation") Double precipitation,
@JsonProperty("weather_code") Integer weatherCode,
@JsonProperty("wind_speed_10m") Double windSpeed,
@JsonProperty("wind_direction_10m") Integer windDirection) {
}
@JsonIgnoreProperties(ignoreUnknown = true)
public record CurrentUnits(
@JsonProperty("time") String timeUnit,
@JsonProperty("temperature_2m") String temperatureUnit,
@JsonProperty("relative_humidity_2m") String humidityUnit,
@JsonProperty("wind_speed_10m") String windSpeedUnit) {
}
@JsonIgnoreProperties(ignoreUnknown = true)
public record DailyForecast(
@JsonProperty("time") List<String> time,
@JsonProperty("temperature_2m_max") List<Double> tempMax,
@JsonProperty("temperature_2m_min") List<Double> tempMin,
@JsonProperty("precipitation_sum") List<Double> precipitationSum,
@JsonProperty("weather_code") List<Integer> weatherCode,
@JsonProperty("wind_speed_10m_max") List<Double> windSpeedMax,
@JsonProperty("wind_direction_10m_dominant") List<Integer> windDirection) {
}
}
/**
* 获取天气代码对应的描述
*/
private String getWeatherDescription(int code) {
returnswitch (code) {
case0 -> "晴朗";
case1, 2, 3 -> "多云";
case45, 48 -> "雾";
case51, 53, 55 -> "毛毛雨";
case56, 57 -> "冻雨";
case61, 63, 65 -> "雨";
case66, 67 -> "冻雨";
case71, 73, 75 -> "雪";
case77 -> "雪粒";
case80, 81, 82 -> "阵雨";
case85, 86 -> "阵雪";
case95 -> "雷暴";
case96, 99 -> "雷暴伴有冰雹";
default -> "未知天气";
};
}
/**
* 获取风向描述
*/
private String getWindDirection(int degrees) {
if (degrees >= 337.5 || degrees < 22.5)
return"北风";
if (degrees >= 22.5 && degrees < 67.5)
return"东北风";
if (degrees >= 67.5 && degrees < 112.5)
return"东风";
if (degrees >= 112.5 && degrees < 157.5)
return"东南风";
if (degrees >= 157.5 && degrees < 202.5)
return"南风";
if (degrees >= 202.5 && degrees < 247.5)
return"西南风";
if (degrees >= 247.5 && degrees < 292.5)
return"西风";
return"西北风";
}
/**
* 获取指定经纬度的天气预报
*
* @param latitude 纬度
* @param longitude 经度
* @return 指定位置的天气预报
* @throws RestClientException 如果请求失败
*/
@Tool(description = "获取指定经纬度的天气预报")
public String getWeatherForecastByLocation(double latitude, double longitude) {
// 获取天气数据(当前和未来7天)
var weatherData = restClient.get()
.uri("/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m,apparent_temperature,relative_humidity_2m,precipitation,weather_code,wind_speed_10m,wind_direction_10m&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,weather_code,wind_speed_10m_max,wind_direction_10m_dominant&timezone=auto&forecast_days=7",
latitude, longitude)
.retrieve()
.body(WeatherData.class);
// 拼接天气信息
StringBuilder weatherInfo = new StringBuilder();
// 添加当前天气信息
WeatherData.CurrentWeather current = weatherData.current();
String temperatureUnit = weatherData.currentUnits() != null ? weatherData.currentUnits().temperatureUnit()
: "°C";
String windSpeedUnit = weatherData.currentUnits() != null ? weatherData.currentUnits().windSpeedUnit() : "km/h";
String humidityUnit = weatherData.currentUnits() != null ? weatherData.currentUnits().humidityUnit() : "%";
weatherInfo.append(String.format("""
当前天气:
温度: %.1f%s (体感温度: %.1f%s)
天气: %s
风向: %s (%.1f %s)
湿度: %d%s
降水量: %.1f 毫米
""",
current.temperature(),
temperatureUnit,
current.feelsLike(),
temperatureUnit,
getWeatherDescription(current.weatherCode()),
getWindDirection(current.windDirection()),
current.windSpeed(),
windSpeedUnit,
current.humidity(),
humidityUnit,
current.precipitation()));
// 添加未来天气预报
weatherInfo.append("未来天气预报:\n");
WeatherData.DailyForecast daily = weatherData.daily();
for (int i = 0; i < daily.time().size(); i++) {
String date = daily.time().get(i);
double tempMin = daily.tempMin().get(i);
double tempMax = daily.tempMax().get(i);
int weatherCode = daily.weatherCode().get(i);
double windSpeed = daily.windSpeedMax().get(i);
int windDir = daily.windDirection().get(i);
double precip = daily.precipitationSum().get(i);
// 格式化日期
LocalDate localDate = LocalDate.parse(date);
String formattedDate = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd (EEE)"));
weatherInfo.append(String.format("""
%s:
温度: %.1f%s ~ %.1f%s
天气: %s
风向: %s (%.1f %s)
降水量: %.1f 毫米
""",
formattedDate,
tempMin, temperatureUnit,
tempMax, temperatureUnit,
getWeatherDescription(weatherCode),
getWindDirection(windDir),
windSpeed, windSpeedUnit,
precip));
}
return weatherInfo.toString();
}
/**
* 获取指定位置的空气质量信息 (使用备用模拟数据)
* 注意:由于OpenMeteo的空气质量API可能需要额外配置或不可用,这里提供备用数据
*
* @param latitude 纬度
* @param longitude 经度
* @return 空气质量信息
*/
@Tool(description = "获取指定位置的空气质量信息(模拟数据)")
public String getAirQuality(@ToolParam(description = "纬度") double latitude,
@ToolParam(description = "经度") double longitude) {
try {
// 从天气数据中获取基本信息
var weatherData = restClient.get()
.uri("/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m&timezone=auto",
latitude, longitude)
.retrieve()
.body(WeatherData.class);
// 模拟空气质量数据 - 实际情况下应该从真实API获取
// 根据经纬度生成一些随机但相对合理的数据
int europeanAqi = (int) (Math.random() * 100) + 1;
int usAqi = (int) (europeanAqi * 1.5);
double pm10 = Math.random() * 50 + 5;
double pm25 = Math.random() * 25 + 2;
double co = Math.random() * 500 + 100;
double no2 = Math.random() * 40 + 5;
double so2 = Math.random() * 20 + 1;
double o3 = Math.random() * 80 + 20;
// 根据AQI评估空气质量等级
String europeanAqiLevel = getAqiLevel(europeanAqi);
String usAqiLevel = getUsAqiLevel(usAqi);
return String.format("""
空气质量信息(模拟数据):
位置: 纬度 %.4f, 经度 %.4f
欧洲空气质量指数: %d (%s)
美国空气质量指数: %d (%s)
PM10: %.1f μg/m³
PM2.5: %.1f μg/m³
一氧化碳(CO): %.1f μg/m³
二氧化氮(NO2): %.1f μg/m³
二氧化硫(SO2): %.1f μg/m³
臭氧(O3): %.1f μg/m³
数据更新时间: %s
注意: 由于OpenMeteo空气质量API限制,此处显示模拟数据,仅供参考。
""",
latitude, longitude,
europeanAqi, europeanAqiLevel,
usAqi, usAqiLevel,
pm10,
pm25,
co,
no2,
so2,
o3,
weatherData.current().time());
} catch (Exception e) {
// 如果获取基本天气数据失败,返回完全模拟的数据
return String.format("""
空气质量信息(完全模拟数据):
位置: 纬度 %.4f, 经度 %.4f
欧洲空气质量指数: %d (%s)
美国空气质量指数: %d (%s)
PM10: %.1f μg/m³
PM2.5: %.1f μg/m³
一氧化碳(CO): %.1f μg/m³
二氧化氮(NO2): %.1f μg/m³
二氧化硫(SO2): %.1f μg/m³
臭氧(O3): %.1f μg/m³
注意: 由于API限制,此处显示完全模拟数据,仅供参考。
""",
latitude, longitude,
50, getAqiLevel(50),
75, getUsAqiLevel(75),
25.0,
15.0,
300.0,
20.0,
5.0,
40.0);
}
}
/**
* 获取欧洲空气质量指数等级
*/
private String getAqiLevel(Integer aqi) {
if (aqi == null)
return"未知";
if (aqi <= 20)
return"优";
elseif (aqi <= 40)
return"良";
elseif (aqi <= 60)
return"中等";
elseif (aqi <= 80)
return"较差";
elseif (aqi <= 100)
return"差";
else
return"极差";
}
/**
* 获取美国空气质量指数等级
*/
private String getUsAqiLevel(Integer aqi) {
if (aqi == null)
return"未知";
if (aqi <= 50)
return"优";
elseif (aqi <= 100)
return"中等";
elseif (aqi <= 150)
return"对敏感人群不健康";
elseif (aqi <= 200)
return"不健康";
elseif (aqi <= 300)
return"非常不健康";
else
return"危险";
}
publicstatic void main(String[] args) {
OpenMeteoService client = new OpenMeteoService();
// 北京坐标
System.out.println(client.getWeatherForecastByLocation(39.9042, 116.4074));
// 北京空气质量(模拟数据)
System.out.println(client.getAirQuality(39.9042, 116.4074));
}
}
注册mcp工具
@SpringBootApplication
public class McpWeatherServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpWeatherServerApplication.class, args);
}
@Bean
public ToolCallbackProvider weatherTools(OpenMeteoService openMeteoService) {
return MethodToolCallbackProvider.builder().toolObjects(openMeteoService).build();
}
}
打包
mvn clean package -DskipTests
将项目打包后可以供mcp客户端使用。
基于sse方式构建
添加依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-webflux-spring-boot-starter</artifactId>
</dependency>
配置项目yaml
server:
port: 8080 # 服务器端口配置
spring:
ai:
mcp:
server:
name: my-weather-server # MCP服务器名称
version: 0.0.1 # 服务器版本号
实现mcp工具
@Service
publicclass OpenMeteoService {
private final WebClient webClient;
public OpenMeteoService(WebClient.Builder webClientBuilder) {
this.webClient = webClientBuilder
.baseUrl("https://api.open-meteo.com/v1")
.build();
}
@Tool(description = "根据经纬度获取天气预报")
publicString getWeatherForecastByLocation(
@ToolParameter(description = "纬度,例如:39.9042") String latitude,
@ToolParameter(description = "经度,例如:116.4074") String longitude) {
try {
String response = webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/forecast")
.queryParam("latitude", latitude)
.queryParam("longitude", longitude)
.queryParam("current", "temperature_2m,wind_speed_10m")
.queryParam("timezone", "auto")
.build())
.retrieve()
.bodyToMono(String.class)
.block();
// 解析响应并返回格式化的天气信息
return"当前位置(纬度:" + latitude + ",经度:" + longitude + ")的天气信息:\n" + response;
} catch (Exception e) {
return"获取天气信息失败:" + e.getMessage();
}
}
@Tool(description = "根据经纬度获取空气质量信息")
publicString getAirQuality(
@ToolParameter(description = "纬度,例如:39.9042") String latitude,
@ToolParameter(description = "经度,例如:116.4074") String longitude) {
// 模拟数据,实际应用中应调用真实API
return"当前位置(纬度:" + latitude + ",经度:" + longitude + ")的空气质量:\n" +
"- PM2.5: 15 μg/m³ (优)\n" +
"- PM10: 28 μg/m³ (良)\n" +
"- 空气质量指数(AQI): 42 (优)\n" +
"- 主要污染物: 无";
}
}
注册mcp工具
@SpringBootApplication
publicclass McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public ToolCallbackProvider weatherTools(OpenMeteoService openMeteoService) {
return MethodToolCallbackProvider.builder()
.toolObjects(openMeteoService)
.build();
}
@Bean
public WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
}
部署服务
服务端将在 http://localhost:8080 启动。
Part4
如何使用Spring AI构建的mcp client
在上面我们构建好了mcp server,我们如何在客户端使用,或者如何构建mcp client。下面我们来实践应用一下。
Claude Desktop使用以上构建的mcp服务
这里我们演示stdio方式
添加配置到Claude的配置文件
{
"mcpServers": {
"fetch": {
"command": "docker",
"args": ["run", "-i", "--rm", "mcp/fetch"]
},
"mcp-weather-server": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-Dspring.main.web-application-type=none",
"-Dlogging.pattern.console=",
"-jar",
"/Users/username/Desktop/mcp-server/mcp-weather-server-0.0.1-SNAPSHOT.jar"
],
"env": {}
}
}
}
Claude Desktop窗口出现mcp工具
验证是否可以调用成功
基于Spring AI Alibaba以stdio方式集成mcp client
添加依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
</dependency>
<!-- 添加Spring AI MCP starter依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-client-spring-boot-starter</artifactId>
</dependency>
配置yaml文件
spring:
ai:
dashscope:
# 配置通义千问API密钥
api-key: ${DASH_SCOPE_API_KEY}
mcp:
client:
stdio:
# 指定MCP服务器配置文件路径(推荐)
servers-configuration: classpath:/mcp-servers-config.json
# 直接配置示例,和上边的配制二选一
# connections:
# server1:
# command: java
# args:
# - -jar
# - /path/to/your/mcp-server.jar
这个配置文件设置了 MCP 客户端的基本配置,包括 API 密钥和服务器配置文件的位置。你也可以选择直接在配置文件中定义服务器配置,但是还是建议使用json文件管理 mcp 配置。在resources目录下创建mcp-servers-config.json配置文件:
{
"mcpServers": {
// 定义名为"weather"的MCP服务器
"weather": {
// 指定启动命令为java
"command": "java",
// 定义启动参数
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-Dspring.main.web-application-type=none",
"-jar",
"<修改为stdio编译之后的jar包全路径>"
],
// 环境变量配置(可选)
"env": {}
}
}
}
这个 JSON 配置文件定义了 MCP 服务器的详细配置,包括如何启动服务器进程、需要传递的参数以及环境变量设置,还是要注意引用的 jar 包必须是全路径的。
编写启动类测试
@SpringBootApplication
publicclassApplication {
public static void main(String[] args) {
// 启动Spring Boot应用
SpringApplication.run(Application.class, args);
}
@Bean
public CommandLineRunner predefinedQuestions(
ChatClient.Builder chatClientBuilder,
ToolCallbackProvider tools,
ConfigurableApplicationContext context) {
return args -> {
// 构建ChatClient并注入MCP工具
var chatClient = chatClientBuilder
.defaultTools(tools)
.build();
// 定义用户输入
String userInput = "北京的天气如何?";
// 打印问题
System.out.println("\n>>> QUESTION: " + userInput);
// 调用LLM并打印响应
System.out.println("\n>>> ASSISTANT: " +
chatClient.prompt(userInput).call().content());
// 关闭应用上下文
context.close();
};
}
}
这段代码展示了如何在 Spring Boot 应用中使用 MCP 客户端。它创建了一个命令行运行器,构建了 ChatClient 并注入了 MCP 工具,然后使用这个客户端发送查询并获取响应。在 Spring AI Alibaba 中使用 Mcp 工具非常简单,只需要把ToolCallbackProvider放到chatClientBuilder的defaultTools方法中,就可以自动的适配。
Part5
结语
使用Spring AI 能够很方便的接入MCP,能够在我们的Java应用中很方便的引入MCP服务的工具,同时也能够将我们的现有的API包装成MCP的工具提供服务供外部调用。这样对Java强大的生态非常友好,大家可以多做一些尝试。
参考:
1.https://github.com/springaialibaba/spring-ai-alibaba-examples/tree/main/spring-ai-alibaba-mcp-example
作者
肖泉|AI开发工程师
bug不一定真是bug,也有可能是新的思路
“
【延伸探索】🔮
AI 不是黑箱魔法,而是可拆解的工程!
✨ 关注 神州数码云基地
✨ 星标公众号,解锁:
▸ 神州问学使用指南
▸ 企业级 AI 场景落地避坑指南
▸ AI 技术落地实战
▸ AI 行业前沿资讯
- END -
往期精选
了解云基地,就现在!

