【Spring AI快速上手 (六)】MCP工具调用新范式_java spring-ai mcp-server服务启动了 mcp-server有提供接口供服务调
一、前言
二、MCP简介
2.1 协议定位与核心价值
2.2 核心组件与交互模型
2.3 通信模式演进
2.4 Spring AI与MCP深度集成范式
2.5 MCP协议演进与行业实践
2.6 未来方向:构建智能体互联网的基石
三、MCP工具调用新范式
Spring AI与MCP的Stdio模式集成实战
基于Stdio协议的MCP服务声明与调用指南
SSE协议下的MCP实时通信实现
一、前言
Spring AI详解:【Spring AI详解】开启Java生态的智能应用开发新时代(附不同功能的Spring AI实战项目)-CSDN博客
二、MCP简介
2.1 协议定位与核心价值
模型上下文协议(Model Context Protocol, MCP) 是由Anthropic推动的开放标准,旨在为大型语言模型(LLM)应用提供标准化接口,实现与外部数据源、工具的安全高效交互。
其核心价值体现在:
解耦AI与数据源:通过统一协议屏蔽底层数据源差异,开发者无需为每个工具编写定制化接口。
构建工具生态系统:持续扩展的预集成工具库(如数据库、API、地图服务),支持动态发现和调用。
安全上下文传递:在企业基础设施内安全处理敏感数据,通过OAuth等机制保障数据隐私。
2.2 核心组件与交互模型
MCP采用客户端-服务器架构,包含五大角色:
数据流向:Host → Client → Server → 数据源/API → 返回结果至LLM
2.3 通信模式演进
MCP支持三类通信模式,适应不同场景需求:
Stdio模式:
通过标准输入输出流通信,适用于本地轻量化工具(如文件操作)。
优势:无网络依赖、低延迟;缺陷:仅限单机部署
SSE模式:
基于HTTP的Server-Sent Events,支持远程服务实时流式响应。
典型场景:企业级天气预报服务、金融数据流
Streamable HTTP模式(2025年新增):
融合普通HTTP与SSE流式传输优势,支持断线恢复与无状态服务。
解决传统SSE长连接压力问题,成为当前推荐方案
2.4 Spring AI与MCP深度集成范式
分层架构设计
Spring AI通过抽象层屏蔽协议细节,实现业务代码与协议解耦:
声明层:@Tool注解定义工具能力,@ToolParam描述参数语义(如航班退票接口)
协议层:ToolCallbackProvider处理MCP协议通信,支持同步/异步调用
执行层:ChatClient构建对话流程,注入系统参数(如当前日期)增强上下文感知
两种调用范式详解
典型场景选择建议:
高频工具(如实时导航)选SSE模式保障响应速度
敏感操作(如数据库更新)用Stdio本地化部署
复杂业务链组合多个MCP微服务实现能力复用
协议无关性设计
Spring AI通过统一编程模型屏蔽底层协议差异:
同一套@Tool接口可同时发布为Stdio服务与SSE端点
McpFunctionCallback将MCP工具转换为Spring AI函数,供ChatClient直接调用。
动态工具注册:SSE模式下@Tool方法自动转换为API端点,无需额外配置
2.5 MCP协议演进与行业实践
关键技术里程碑
Streamable HTTP替代SSE成为默认传输协议,解决长连接不可恢复问题 。
安全四原则强化:用户同意控制、数据最小化、工具沙箱隔离、LLM采样授权
Claude Messages API原生支持MCP,通过mcp_servers参数直连远程服务
动态客户端注册(DCR)机制简化OAuth集成,降低认证开发成本
安全架构升级
MCP面临的七类核心风险与应对策略:
行业应用场景
2.6 未来方向:构建智能体互联网的基石
技术演进趋势
去中心化身份认证:集成W3C DID标准,实现跨域身份互通
分层智能体系统:MCP Gateway协调多智能体协作,解决复杂工作流
协议标准化:推进ISO/IEC国际标准认证,增强跨平台兼容性
开发者行动建议
新项目架构:优先采用Streamable HTTP协议,平衡性能与可靠性
存量系统改造:通过McpFunctionCallback渐进式替换传统插件
生产部署规范:启用logging.level.io.modelcontextprotocol=DEBUG监控协议交互、敏感操作强制OAuth Scope授权
终极愿景: MCP不仅是技术协议,更是智能体生态的“连接器哲学”——当每个数据源都拥有标准化接口,AI应用将真正实现“即插即用”的革命性体验,推动从封闭模型向开放智能体网络的范式迁移
三、MCP工具调用新范式
pom.xml
spring-boot-starter-web提供Web基础支持。
spring-ai-alibaba-starter-dashscope集成阿里云Dashscope服务。
spring-ai-starter-mcp-client-webflux支持SSE和Stdio两种通信协议
com.alibaba.cloud.ai spring-ai-alibaba-bom 1.0.0.2 pom import org.springframework.ai spring-ai-bom 1.0.0 pom import org.springframework.boot spring-boot-dependencies 3.2.5 pom import org.springframework.boot spring-boot-starter-web com.alibaba.cloud.ai spring-ai-alibaba-starter-dashscope org.springframework.ai spring-ai-starter-mcp-client-webflux
application.propertise
# 阿里大模型spring.ai.dashscope.api-key=${TONGYI_AI_KEY}spring.ai.dashscope.chat.options.model= qwen-pluslogging.level.org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor= debug
Spring AI与MCP的Stdio模式集成实战
application.yml
MCP客户端配置:
超时时间设为60秒(生产环境需按业务调整)。
支持两种Stdio模式配置:
外部JSON文件:通过classpath:/mcp/mcp-servers-config.json加载服务器配置(推荐模块化管理)。
内联配置:直接定义命令、参数和环境变量(适合简单场景,但复杂命令建议用JSON)
日志配置:启用MCP客户端和协议规范的DEBUG日志,便于调试
server: port: 8080 # 应用服务端口 servlet: encoding: charset: UTF-8 # 强制使用UTF-8编码 enabled: true # 启用编码过滤器 force: true # 强制响应使用配置的字符集spring: ai: mcp: client: request-timeout: 60000 # MCP请求超时时间(毫秒),建议生产环境根据业务调整 stdio: # 1. 推荐将服务器配置分离到独立JSON文件(模块化管理) # 文件格式需符合Claude Desktop规范,支持多服务器配置 servers-configuration: classpath:/mcp/mcp-servers-config.json # 2. 替代方案:直接内联配置(适合简单场景) # 注意:复杂命令建议使用外部JSON文件 # connections: # baidu-map: # command: cmd # args: # - /c # - npx # - -y # - @baidumap/mcp-server-baidu-map # env: # BAIDU_MAP_API_KEY: xxx# 调试日志配置logging: level: io: modelcontextprotocol: client: DEBUG # 启用MCP客户端详细日志 spec: DEBUG # 启用协议规范级日志(含请求/响应详情)
resources.map.mcp-servers-config.json(百度地图MCP)
定义百度地图MCP服务的启动配置:
使用cmdcmd
执行命令,参数包括npx和@baidumap/mcp-server-baidu-map。
环境变量BAIDU_MAP_API_KEY需替换为实际值
{ \"mcpServers\": { \"baidu-map\": { \"command\": \"cmd\", \"args\": [ \"-c\", \"npx\", \"-y\", \"@baidumap/mcp-server-baidu-map\" ], \"env\": { \"BAIDU_MAP_API_KEY\": \"xxx\" } } }}
Tool 工具类
声明工具接口供AI模型调用
import org.springframework.ai.tool.annotation.Tool;import org.springframework.ai.tool.annotation.ToolParam;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class ToolsService { /** * 退票 * @param ticketNumber 预定号 * @param name 真实人名 * @return 退票成功 */ @Tool(description = \"退票/取消预定,调用之前先查询航班\") public String cancel( // @ToolParam告诉大模型参数的描述 @ToolParam(description = \"预定号,可以是纯数字\") String ticketNumber, @ToolParam(description = \"真实人名(必填,必须为人的真实姓名,严禁用其他信息代替;如缺失请传null)\") String name ) { return \"退票成功\"; } @Tool(description = \"获取航班信息\") public FlightBookingService.BookingDetails getBookingDetails( @ToolParam(description = \"预定号,可以是纯数字\") String bookingNumber, @ToolParam(description = \"真实人名(必填,必须为人的真实姓名,严禁用其他信息代替;如缺失请传null)\") String name) { return \"查询成功\"; }}
对话调用控制器类
通过ChatClient.Builder构建客户端,注册工具服务和回调处理器
import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;import org.springframework.ai.tool.ToolCallbackProvider;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.CrossOrigin;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import reactor.core.publisher.Flux;import java.time.LocalDate;/** * 航空公司智能客服控制器 * 核心功能: * 1. 集成百度地图服务(通过MCP协议调用) * 2. 航班查询与预订服务 * 3. 流式对话接口(SSE) * * 关于百度地图MCP调用: * - 实际调用通过ToolCallbackProvider实现 * - ToolsService仅声明工具接口,不直接处理调用 * - MCP服务配置在application.yaml中定义 */@RestController@CrossOriginpublic class OpenAiController { private final ChatClient chatClient; /** * 构造方法(关键依赖注入) * @param chatClientBuilder ChatClient构建器 * @param toolsService 工具服务(声明百度地图/航班等工具) * @param toolCallbackProvider MCP调用处理器(实际触发百度地图服务调用) */ public OpenAiController(ChatClient.Builder chatClientBuilder, ToolsService toolsService, ToolCallbackProvider toolCallbackProvider) { this.chatClient = chatClientBuilder // 系统角色定义(包含百度地图服务说明) .defaultSystem(\"\"\" ## 角色 您是一个航空公司的智能客服助手,集成了百度地图服务功能。 ## 服务能力 1. 可以查询地点、路线规划、周边搜索等地图相关服务 2. 提供航班信息查询、预订等航空服务 3. 支持多轮对话,保持上下文理解 ## 交互要求 1. 使用简体中文进行交流 2. 涉及地图服务时,明确说明使用的是百度地图数据 3. 对于需要确认的操作(如预订、修改等),必须获得用户明确确认后再执行 4. 提供准确的地理位置信息时,需注明数据来源和更新时间 ## 当前信息 今天的日期是 {current_date} 地图数据来源: 百度地图API \"\"\") // 注册工具声明(工具实际调用通过toolCallbackProvider) .defaultTools(toolsService) // 关键配置:设置MCP回调处理器(实际触发百度地图调用) .defaultToolCallbacks(toolCallbackProvider) .build(); } /** * 流式对话接口 * 当用户请求涉及百度地图服务时: * 1. AI生成工具调用请求 * 2. ToolCallbackProvider通过MCP协议调用百度地图 * 3. 返回结果后生成响应 * * @param message 用户消息(示例:\"查询北京西站到首都机场的驾车路线\") * @return 流式响应(包含地图数据时会标注来源) */ @CrossOrigin @GetMapping(value = \"/ai/generateStreamAsString\", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux generateStreamAsString( @RequestParam(value = \"message\", defaultValue = \"查询北京到上海的自驾路线\") String message) { return chatClient.prompt() .user(message) .system(p -> p.param(\"current_date\", LocalDate.now())) .stream() .content(); }}
这样就成功用Spring AI调用了百度地图的MCP
基于Stdio协议的MCP服务声明与调用指南
pom.xml
依赖spring-ai-starter-mcp-server提供Stdio模式支持(含协议核心、工具管理)。
可选spring-web依赖(HTTP调用外部工具时需添加)
org.springframework.ai spring-ai-bom 1.0.0 pom import org.springframework.boot spring-boot-dependencies 3.2.5 pom import org.springframework.ai spring-ai-starter-mcp-server org.springframework spring-web org.springframework.boot spring-boot-maven-plugin repackage
application.yml
强制非Web环境(web-application-type: none),禁用Banner避免干扰Stdio通信
spring: main: # 禁用Web应用类型(强制非Web环境) # 重要:MCP STDIO模式需要非Web环境 web-application-type: none # 禁用Spring Boot横幅(必需配置) # 原因:控制台输出会干扰STDIO协议通信 banner-mode: off ai: mcp: server: # 服务器标识(需全局唯一) name: my-weather-server # 服务版本号(遵循语义化版本规范) version: 0.0.1
调用外部OpenMeteo接口获取指定经纬度的天气预报
调用OpenMeteo免费API获取天气预报,支持:
当前天气(温度、湿度、降水等)。
7天预报(最高/低温、风速、天气代码转换)。
import java.util.List;import java.time.LocalDate;import java.time.format.DateTimeFormatter;import com.fasterxml.jackson.annotation.JsonIgnoreProperties;import com.fasterxml.jackson.annotation.JsonProperty;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;import org.springframework.web.client.RestClientException;/** * 利用OpenMeteo的免费天气API提供天气服务 * 该API无需API密钥,可以直接使用 */@Servicepublic class OpenMeteoService { // OpenMeteo免费天气API基础URL private static final String BASE_URL = \"https://api.open-meteo.com/v1\"; private final 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 time, @JsonProperty(\"temperature_2m_max\") List tempMax, @JsonProperty(\"temperature_2m_min\") List tempMin, @JsonProperty(\"precipitation_sum\") List precipitationSum, @JsonProperty(\"weather_code\") List weatherCode, @JsonProperty(\"wind_speed_10m_max\") List windSpeedMax, @JsonProperty(\"wind_direction_10m_dominant\") List windDirection) { } } /** * 获取天气代码对应的描述 */ private String getWeatherDescription(int code) { return switch (code) { case 0 -> \"晴朗\"; case 1, 2, 3 -> \"多云\"; case 45, 48 -> \"雾\"; case 51, 53, 55 -> \"毛毛雨\"; case 56, 57 -> \"冻雨\"; case 61, 63, 65 -> \"雨\"; case 66, 67 -> \"冻雨\"; case 71, 73, 75 -> \"雪\"; case 77 -> \"雪粒\"; case 80, 81, 82 -> \"阵雨\"; case 85, 86 -> \"阵雪\"; case 95 -> \"雷暴\"; case 96, 99 -> \"雷暴伴有冰雹\"; default -> \"未知天气\"; }; } /** * 获取风向描述 */ private String getWindDirection(int degrees) { if (degrees >= 337.5 || degrees = 22.5 && degrees = 67.5 && degrees = 112.5 && degrees = 157.5 && degrees = 202.5 && 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(); }}
McpServerApplication启动类
注册OpenMeteoService为MCP工具,通过MethodToolCallbackProvider暴露@Tool方法。
import org.springframework.ai.tool.ToolCallbackProvider;import org.springframework.ai.tool.method.MethodToolCallbackProvider;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;@SpringBootApplicationpublic class McpServerApplication { /** * MCP服务器主启动类 * 核心功能: * 1. 注册MCP工具服务(如天气查询) * 2. 初始化MCP协议通信端点 * 3. 提供工具回调处理器 * * 工作流程: * 1. AI模型生成工具调用请求 → * 2. ToolCallbackProvider通过MCP协议转发 → * 3. OpenMeteoService执行实际业务逻辑 → * 4. 结果返回AI模型生成响应 */@SpringBootApplicationpublic class McpServerApplication { /** * 应用主入口 * @param args 命令行参数 * * 注意:根据传输协议不同需要特殊配置: * - STDIO模式:需设置spring.main.web-application-type=none * - SSE模式:需配置server.port和MCP端点 */ public static void main(String[] args) { SpringApplication.run(McpServerApplication.class, args); } /** * 注册MCP工具回调处理器 * @param openMeteoService 天气查询服务(需包含@Tool注解方法) * @return ToolCallbackProvider实例 * * 关键作用: * 1. 将OpenMeteoService中的@Tool方法暴露为MCP可调用接口 * 2. 处理AI模型发起的工具调用请求 * 3. 管理工具执行结果返回流程 */ @Bean public ToolCallbackProvider weatherTools(OpenMeteoService openMeteoService) { return MethodToolCallbackProvider.builder() .toolObjects(openMeteoService) // 注册工具类实例 .build(); }}
生成JAR后,客户端通过JSON配置JAR路径和启动命令
接下来就是把这个项目进行 package 打成一个 jar 包:mcp-stdio-server-0.0.1.jar
然后就是配置到客户端(这里直接用前面 Spring AI接入MCP 的项目中配置):
resources.map.mcp-servers-config.json
去除百度MCP防止天气查询冲突,配置jar包路径及启动命令
{ \"mcpServers\": { \"mcp-server-weather\": { \"command\": \"java\", \"args\": [ \"-Dspring.ai.mcp.server.stdio=true\", \"-Dlogging.pattern.console=\", \"-jar\", \"D:\\\\ideaworkspace\\\\git_pull\\\\spring-ai-parent\\\\mcp-stdio-server\\\\target\\\\mcp-stdio-server-0.0.1.jar\" ] } }}
修改之前的路线查询消息,改为天气查询消息
import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;import org.springframework.ai.tool.ToolCallbackProvider;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.CrossOrigin;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import reactor.core.publisher.Flux;import java.time.LocalDate;/** * 航空公司智能客服控制器 * 核心功能: * 1. 集成百度地图、OpenMeteo服务(通过MCP协议调用) * 2. 航班查询与预订服务 * 3. 流式对话接口(SSE) * * 关于OpenMeteo MCP调用: * - 实际调用通过ToolCallbackProvider实现 * - ToolsService仅声明工具接口,不直接处理调用 * - MCP服务配置在application.yaml中定义 */@RestController@CrossOriginpublic class OpenAiController { private final ChatClient chatClient; /** * 构造方法(关键依赖注入) * @param chatClientBuilder ChatClient构建器 * @param toolsService 工具服务(声明百度地图/OpenMeteo/航班等工具) * @param toolCallbackProvider MCP调用处理器 */ public OpenAiController(ChatClient.Builder chatClientBuilder, ToolsService toolsService, ToolCallbackProvider toolCallbackProvider) { this.chatClient = chatClientBuilder // 系统角色定义(包含百度地图服务说明) .defaultSystem(\"\"\" ## 角色 您是一个航空公司的智能客服助手,集成了百度地图、OpenMeteo查询天气服务功能。 ## 服务能力 1. 可以查询地点、路线规划、周边搜索等地图相关服务 2. 可查询指定地点的天气预报、天气情况 3. 提供航班信息查询、预订等航空服务 4. 支持多轮对话,保持上下文理解 ## 交互要求 1. 使用简体中文进行交流 2. 涉及地图服务时,明确说明使用的是百度地图数据 3. 对于需要确认的操作(如预订、修改等),必须获得用户明确确认后再执行 4. 提供准确的地理位置信息时,需注明数据来源和更新时间 5. 指定地点时自动生成对应的经纬度再调用根据进行查询 ## 当前信息 今天的日期是 {current_date} 天气数据来源: OpenMeteo \"\"\") // 注册工具声明(工具实际调用通过toolCallbackProvider) .defaultTools(toolsService) // 关键配置:设置MCP回调处理器 .defaultToolCallbacks(toolCallbackProvider) .build(); } /** * 流式对话接口 */ @CrossOrigin @GetMapping(value = \"/ai/generateStreamAsString\", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux generateStreamAsString( @RequestParam(value = \"message\", defaultValue = \"查询北京的天气\") String message) { return chatClient.prompt() .user(message) .system(p -> p.param(\"current_date\", LocalDate.now())) .stream() .content(); }}
这样就可以实现自定义MCP打成jar包让外部访问
SSE协议下的MCP实时通信实现
目前MCP协议已弃用,但是streamable模式Spring AI没有适配,不过这两种都是通过部署成Web服务进行传输数据的,因此可以暂时用SSE进行实现
pom.xml
依赖spring-ai-starter-mcp-server-webflux提供WebFlux实现的SSE支持
org.springframework.ai spring-ai-bom 1.0.0 pom import org.springframework.boot spring-boot-dependencies 3.2.5 pom import org.springframework.ai spring-ai-starter-mcp-server-webflux org.springframework spring-web org.springframework.boot spring-boot-maven-plugin repackage
application.yml
配置服务端口(8088)和MCP服务标识。
spring: ai: mcp: server: name: my-weather-server # 服务标识符 version: 0.0.1 # 服务版本号server: port: 8088 # 服务监听端口
Tool 示例工具类
import java.util.List;import java.time.LocalDate;import java.time.format.DateTimeFormatter;import java.util.Map;import com.fasterxml.jackson.annotation.JsonIgnoreProperties;import com.fasterxml.jackson.annotation.JsonProperty;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;import org.springframework.web.client.RestClientException;@Servicepublic class UserToolService { Map userScore = Map.of( \"wangwu\",99.0, \"zhangsan\",2.0, \"lisi\",3.0); @Tool(description = \"获取用户分数\") public String getScore(String username) { if(userScore.containsKey(username)){ return userScore.get(username).toString(); } return \"未检索到当前用户\"; }}
McpSSEApplication 启动类
import org.springframework.ai.tool.ToolCallbackProvider;import org.springframework.ai.tool.method.MethodToolCallbackProvider;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;@SpringBootApplicationpublic class McpSSEApplication { public static void main(String[] args) { SpringApplication.run(McpSSEApplication.class, args); } @Bean public ToolCallbackProvider userTools(UserToolService userToolService) { return MethodToolCallbackProvider.builder().toolObjects(userToolService).build(); }}
然后启动该服务,本机IP地址为: localhost:8088
这时候就修改客户端的yml文件(这里直接用前面 Spring AI接入MCP 的项目中配置)
配置 url 为sse服务端启动的IP地址
application.yml
修改application.yml,替换Stdio配置为SSE:
指定服务端URL(如http://localhost:8088)
server: port: 8080 # 应用服务端口 servlet: encoding: charset: UTF-8 # 强制使用UTF-8编码 enabled: true # 启用编码过滤器 force: true # 强制响应使用配置的字符集spring: ai: mcp: client: request-timeout: 60000 # MCP请求超时时间(毫秒),建议生产环境根据业务调整 sse: connections: UserSSEInfo: # 连接名称(可自定义) url: http://localhost:8088 # SSE服务基础URL # sse-endpoint: /custom-sse # 可选的SSE端点路径(默认/sse)# 调试日志配置logging: level: io: modelcontextprotocol: client: DEBUG # 启用MCP客户端详细日志 spec: DEBUG # 启用协议规范级日志(含请求/响应详情)
这时候进行会话接口调用即可,信息为:查询zhangsan的分数
import org.springframework.ai.chat.client.ChatClient;import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;import org.springframework.ai.tool.ToolCallbackProvider;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.CrossOrigin;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import reactor.core.publisher.Flux;import java.time.LocalDate;/** * 航空公司智能客服控制器 * 核心功能: * 1. 集成百度地图、OpenMeteo服务(通过MCP协议调用) * 2. 航班查询与预订服务 * 3. 流式对话接口(SSE) * * 关于OpenMeteo MCP调用: * - 实际调用通过ToolCallbackProvider实现 * - ToolsService仅声明工具接口,不直接处理调用 * - MCP服务配置在application.yaml中定义 */@RestController@CrossOriginpublic class OpenAiController { private final ChatClient chatClient; /** * 构造方法(关键依赖注入) * @param chatClientBuilder ChatClient构建器 * @param toolsService 工具服务(声明百度地图/OpenMeteo/航班等工具) * @param toolCallbackProvider MCP调用处理器 */ public OpenAiController(ChatClient.Builder chatClientBuilder, ToolsService toolsService, ToolCallbackProvider toolCallbackProvider) { this.chatClient = chatClientBuilder // 系统角色定义(包含百度地图服务说明) .defaultSystem(\"\"\" ## 角色 您是一个航空公司的智能客服助手,集成了百度地图、OpenMeteo查询天气服务功能。 ## 服务能力 1. 可以查询地点、路线规划、周边搜索等地图相关服务 2. 可查询指定地点的天气预报、天气情况 3. 提供航班信息查询、预订等航空服务 4. 支持多轮对话,保持上下文理解 5. 根据用户名查询用户分数 ## 交互要求 1. 使用简体中文进行交流 2. 涉及地图服务时,明确说明使用的是百度地图数据 3. 对于需要确认的操作(如预订、修改等),必须获得用户明确确认后再执行 4. 提供准确的地理位置信息时,需注明数据来源和更新时间 5. 指定地点时自动生成对应的经纬度再调用根据进行查询 ## 当前信息 今天的日期是 {current_date} 天气数据来源: OpenMeteo \"\"\") // 注册工具声明(工具实际调用通过toolCallbackProvider) .defaultTools(toolsService) // 关键配置:设置MCP回调处理器 .defaultToolCallbacks(toolCallbackProvider) .build(); } /** * 流式对话接口 */ @CrossOrigin @GetMapping(value = \"/ai/generateStreamAsString\", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux generateStreamAsString( @RequestParam(value = \"message\", defaultValue = \"查询zhangsan的分数\") String message) { return chatClient.prompt() .user(message) .system(p -> p.param(\"current_date\", LocalDate.now())) .stream() .content(); }}
MCP(Model Context Protocol)是一种标准化的工具调用协议,旨在简化AI模型与外部服务(如API、数据库、地图服务等)的交互。它通过统一的接口定义和通信机制,使开发者能够轻松集成多种工具,而无需关心底层实现细节。MCP的核心优势在于协议无关性,支持多种通信模式(如Stdio、SSE、HTTP Streamable),并提供了安全、高效的上下文传递机制。
在Spring AI生态中,MCP扮演着关键角色,使开发者可以通过简单的注解(如@Tool、@ToolParam)声明工具功能,并由框架自动处理协议通信、数据转换和结果返回。无论是本地轻量级工具(如文件操作)还是远程企业级服务(如百度地图API),MCP都能提供一致的调用体验,极大提升了AI应用的开发效率。
未来,随着MCP协议的持续演进(如Streamable HTTP的普及、安全机制的强化),它将成为构建智能体互联网(Agent Internet)的核心基石,推动AI应用向更开放、更互联的方向发展。
GitHub:3323223659/Java-AI: SpringAI与LangChain4j学习dome
上一篇:【Spring AI快速上手 (五)】Agent复杂任务智能体初探-CSDN博客
有任何问题或建议欢迎评论区留言讨论!