在该案例中,我们会创建MCP Server 和MCP Client 两个SpringBoot项目,MCP Server项目中会创建一个getWeather工具,该工具通过OpenWeather可以查询某个城市天气情况;MCP Client 项目中创建相应的Controller,根据配置通过WebFlux SSE方式与MCP Server进行通信,实现调用天气工具。
Mcp Server开发
按照如下步骤创建MCP Server对应的SpringBoot项目。**WebFlux SSE传输模式中创建的Mcp Server相比于STDIO传输模式中创建的MCPServer 只是在pom.xml中引入的依赖不同而已。**
1) 创建SpringBoot项目
SpringBoot项目命名为SpringAIMCPStdioServer,设置使用的JDK为17版本。
2) 在项目中加入如下Maven依赖
<?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 https://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.5.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>SpringAIMCPSseServer</artifactId> <version>0.0.1-SNAPSHOT</version> <name>SpringAIMCPSseServer</name> <description>SpringAIMCPSseServer</description> <properties> <java.version>17</java.version> </properties> <!-- 导入 Spring AI BOM,用于统一管理 Spring AI 依赖的版本, 引用每个 Spring AI 模块时不用再写 <version>,只要依赖什么模块 Mavens 自动使用 BOM 推荐的版本 --> <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.boot</groupId>--> <!-- <artifactId>spring-boot-starter-web</artifactId>--> <!-- </dependency>--> <!-- 支持 SSE 传输,使用如下依赖 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webflux</artifactId> </dependency> <!-- 依赖的json 包--> <dependency> <groupId>org.json</groupId> <artifactId>json</artifactId> <version>20210307</version> </dependency> </dependencies> <!-- 打包插件 --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.4.4</version> <configuration> <mainClass>com.example.springaimcpstdioserver.SpringAimcpStdioServerApplication</mainClass> </configuration> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <!-- 声明仓库, 用于获取 Spring AI 以及相关预发布版本--> <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </repository> <repository> <name>Central Portal Snapshots</name> <id>central-portal-snapshots</id> <url>https://central.sonatype.com/repository/maven-snapshots/</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </project>注意:
在该pom.xml中引入了“spring-ai-starter-mcp-server-webflux”MCP 依赖包,该包只支持SSE传输;
一个SpringBoot项目MVC 和 WebFlux 通常二选其一使用,不要引入“spring-boot-starter-web”依赖包,该包会启用 Spring MVC + 嵌入式 Tomcat,相当于是一套HTTP服务器组件,与Spring WebFlux + Reactor Netty 这套HTTP 服务组件冲突了,导致后续客户端HTTP请求报错。
package com.example.springaimcpsseserver.service; import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; /** * 天气服务类,用于获取指定城市的天气信息 * @Service 标记为 Spring 服务层组件 */ @Service public class WeatherService { private static final Logger logger = LoggerFactory.getLogger(WeatherService.class); private static final String BASE_URL = "http://api.openweathermap.org/data/2.5/weather"; @Value("${OPEN_WEATHER_API_KEY}") private String OPEN_WEATHER_API_KEY; /** * 根据城市名称获取天气信息(使用 OpenWeatherMap) * @param city 城市名称,如 "Beijing" * @return 天气信息文本 */ @Tool(description = "获取指定城市的当前天气情况,格式化后的天气报告字符串。") public String getWeather(@ToolParam(description = "城市名称,必须是英文格式,比如 London 或 Beijing") String city) { logger.info("====== 调用了getWeather工具 ======"); try { String charset = "UTF-8"; String query = String.format( "q=%s&appid=%s&units=metric&lang=zh_cn", URLEncoder.encode(city, charset), URLEncoder.encode(OPEN_WEATHER_API_KEY, charset) ); URL url = new URL(BASE_URL + "?" + query); logger.info("====== 访问URL: ======"+url.toString()); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), charset)); StringBuilder response = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { response.append(line); } reader.close(); JSONObject data = new JSONObject(response.toString()); if (data.getInt("cod") == 404) { return "未找到该城市的天气信息。"; } JSONObject main = data.getJSONObject("main"); JSONArray weatherArray = data.getJSONArray("weather"); JSONObject weather = weatherArray.getJSONObject(0); JSONObject wind = data.getJSONObject("wind"); String weatherDescription = weather.optString("description", "无描述"); double temperature = main.optDouble("temp", Double.NaN); double feelsLike = main.optDouble("feels_like", Double.NaN); double tempMin = main.optDouble("temp_min", Double.NaN); double tempMax = main.optDouble("temp_max", Double.NaN); int pressure = main.optInt("pressure", 0); int humidity = main.optInt("humidity", 0); double windSpeed = wind.optDouble("speed", Double.NaN); return String.format(""" 城市: %s 天气描述: %s 当前温度: %.1f°C 体感温度: %.1f°C 最低温度: %.1f°C 最高温度: %.1f°C 气压: %d hPa 湿度: %d%% 风速: %.1f m/s """, data.optString("name", city), weatherDescription, temperature, feelsLike, tempMin, tempMax, pressure, humidity, windSpeed ); } catch (Exception e) { return "获取天气信息时出错: " + e.getMessage(); } } public static void main(String[] args) { //测试方法 WeatherService client = new WeatherService(); String beijing = client.getWeather("Beijing"); System.out.println(beijing); } }Mcp Client 开发
按照如下步骤创建MCP Client的SpringBoot项目。该MCP Client 项目中可以使用不同的LLM模型,只需要在对应配置文件中引入不同的模型对应的apikey及相关依赖即可。
WebFlux SSE传输模式中创建的Mcp Client相比于STDIO传输模式中创建的MCP Client 有两点不同: 在pom.xml中引入的依赖不同 、在application.properties中配置远程Server 。
1) 创建SpringBoot项目
SpringBoot项目命名为SpringAIMCPSseClient,设置使用的JDK为17版本。
2) 在项目中加入如下Maven依赖
<?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 https://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.5.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>SpringAIMCPSseClient</artifactId> <version>0.0.1-SNAPSHOT</version> <name>SpringAIMCPSseClient</name> <description>SpringAIMCPSseClient</description> <properties> <java.version>17</java.version> </properties> <!-- 导入 Spring AI BOM,用于统一管理 Spring AI 依赖的版本, 引用每个 Spring AI 模块时不用再写 <version>,只要依赖什么模块 Mavens 自动使用 BOM 推荐的版本 --> <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.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 基于 WebFlux 的 SSE 传输实现的依赖 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-client-webflux</artifactId> </dependency> <!-- DeepSeek 模型依赖 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-deepseek</artifactId> </dependency> </dependencies> <!-- 声明仓库, 用于获取 Spring AI 以及相关预发布版本--> <repositories> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </repository> <repository> <name>Central Portal Snapshots</name> <id>central-portal-snapshots</id> <url>https://central.sonatype.com/repository/maven-snapshots/</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories> </project>