Spring AI 系列之八 - MCP Server
之前做个几个大模型的应用,都是使用Python语言,后来有一个项目使用了Java,并使用了Spring AI框架。随着Spring AI不断地完善,最近它发布了1.0正式版,意味着它已经能很好的作为企业级生产环境的使用。对于Java开发者来说真是一个福音,其功能已经能满足基于大模型开发企业级应用。借着这次机会,给大家分享一下Spring AI框架。
注意:由于框架不同版本改造会有些使用的不同,因此本次系列中使用基本框架是 Spring AI-1.0.0,JDK版本使用的是19。
代码参考: https://github.com/forever1986/springai-study
目录
- 1 MCP-Server
- 2 示例演示
- 2.1 MCP-Server(Stdio模式)
- 2.1.1 stdio-server子模块
- 2.1.2 stdio-client子模块
- 2.1.3 演示效果
- 2.2 MCP-Server(SSE模式)
- 2.2.1 sse-server子模块
- 2.2.2 sse-client子模块
- 2.2.3 演示效果
上一章通过Spring AI的MCP-Client实现访问MCP服务,如果用户需要自定义自己的MCP服务,也就是MCP-Server,在Spring AI也提供非常方便的集成,这一章将通过Stido和SSE两种模式的示例来讲解。
1 MCP-Server
在Java MCP SDK中建立MCP-Server提供两种通讯方式,分别是Stdio和SSE。而Spring AI 也集成了这两种方式,并且基于Spring Boot,提供了三种方式的建立MCP-Server:
插件 | 支持通讯方式 | 同步或异步 |
---|---|---|
spring-ai-starter-mcp-server | Stdio | 同步或异步 |
spring-ai-starter-mcp-server-webmvc | Stdio或SSE | 同步或异步 |
spring-ai-starter-mcp-server-webflux | Stdio或SSE | 同步或异步 |
下面通过spring-ai-starter-mcp-server和spring-ai-starter-mcp-server-webmvc分别演示Stdio模式和SSE模式。
2 示例演示
下面两个示例参考lesson10子模块
示例说明:建立自己的MCP-Server服务,该服务提供一个访问天气情况的工具,这里使用高德的公共API访问
2.1 MCP-Server(Stdio模式)
代码参考lesson10子模块下面的,stdio-server和stdio-client两个子模块
2.1.1 stdio-server子模块
1)在lesson10子模块下,新建stdio-server子模块,其pom引入如下:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 引入的是mcp-server --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server</artifactId></dependency>
</dependencies><!-- 使用springboot的打包插件-->
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><!--增加此行,可以解决打包时,找不到jar包的问题--><addResources>true</addResources><includeSystemScope>true</includeSystemScope><mainClass>com.demo.lesson10.stdio.server.Lesson10MCPServerStdioApplication</mainClass></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins>
</build>
注意:由于采用本地运行,因此使用spring-boot-maven-plugin打包工具,指定启动类Lesson10MCPServerStdioApplication
2)配置application.properties文件
# 非web模式运行(必须设置)
spring.main.web-application-type=none
# 关闭打印banner(必须设置)
spring.main.banner-mode=off
# 不在控制台输出日志(如果设置在log文件输出也可以,但是不能在控制台输出,因为这个会传递到client的解析里面)(必须设置)
logging.pattern.console=
# 服务名称
spring.ai.mcp.server.name=stdio-weather-server
# 服务版本
spring.ai.mcp.server.version=0.0.1
# 定义同步模式(必须设置)
spring.ai.mcp.server.type=SYNC
# 定义通讯方式stdio
spring.ai.mcp.server.stdio=true
说明:这里有几个重点配置需要说明
1)使用非web运行模式,因为stdio采用控制台内容输出来通讯
2)关闭banner和日志打印,因为stdio采用控制台内容输出来通讯,开启会导致客户端解析错误
3)新建天气预报服务WeatherService:
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;@Service
public class WeatherService {@Autowiredprivate RestTemplate restTemplate;private static final String adcode = "adcode";@Tool(description = "获取中国城市的天气情况")public String getWeatherForecastByCity(String city) {// 获取城市的adcodeString result = restTemplate.getForObject("https://restapi.amap.com/v3/geocode/geo?address=西安&key=你的高德APIKEY", String.class);// 这里为了方便简单处理一下字符串获取adcode,正式的话需要解析json格式int startIndex = result.indexOf(adcode);String code = result.substring(startIndex+adcode.length()+3,result.indexOf(",",startIndex)-1);// 通过城市的adcode,进行获取天气预报result = restTemplate.getForObject("https://restapi.amap.com/v3/weather/weatherInfo?extensions=all&key=你的高德APIKEY&city="+code, String.class);return result;}
}
4)由于使用RestTemplate访问API,因此配置RestTemplateConfig:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@Configuration
public class RestTemplateConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}
5)配置启动类Lesson10MCPServerStdioApplication :
import com.demo.lesson10.stdio.server.service.WeatherService;
import org.springframework.ai.support.ToolCallbacks;
import org.springframework.ai.tool.ToolCallback;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;import java.util.Arrays;
import java.util.List;@SpringBootApplication
public class Lesson10MCPServerStdioApplication {@Beanpublic List<ToolCallback> weatherTools(WeatherService weatherService) {return Arrays.stream(ToolCallbacks.from(weatherService)).toList();}public static void main(String[] args) {SpringApplication.run(Lesson10MCPServerStdioApplication.class, args);}
}
2.1.2 stdio-client子模块
1)在lesson10子模块下,新建stdio-client子模块,其pom引入如下:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-zhipuai</artifactId></dependency><!-- 引入的是mcp-client --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client</artifactId></dependency>
</dependencies>
2)配置application.properties文件
# 聊天模型
spring.ai.zhipuai.api-key=你的智谱模型的API KEY
spring.ai.zhipuai.chat.options.model=GLM-4-Flash-250414
spring.ai.zhipuai.chat.options.temperature=0.7# 指定mcp-servers的文件
spring.ai.mcp.client.stdio.servers-configuration=classpath:/mcp-servers-config.json
3)配置MCP-Server文件mcp-servers-config.json
{"mcpServers": {"spring-ai-mcp-weather": {"command": "java","args": ["-Dspring.ai.mcp.server.stdio=true","-jar","E:/workspace/iip-factory/springai-study/lesson10/stdio-server/target/stdio-server-1.0-SNAPSHOT.jar"]}}
}
注意:其中jar包的路径由自己实际路径为主,这里其实就是使用java命令启动MCP-Server服务
4)新建MCPClientStdioController类进行访问
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class MCPClientStdioController {private ChatClient chatClient;public MCPClientStdioController(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools) {this.chatClient = chatClientBuilder.defaultToolCallbacks(tools).build();}/*** @param message 问题*/@GetMapping("/ai/stdio/client")public String stdioClient(@RequestParam(value = "message", required = true) String message) {return this.chatClient.prompt().user(message).call().content();}
}
5)新建Lesson10MCPClientStdioApplication启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Lesson10MCPClientStdioApplication {public static void main(String[] args) {SpringApplication.run(Lesson10MCPClientStdioApplication.class, args);}}
2.1.3 演示效果
1)对stdio-server子模块进行打包,会在target目录下得到jar包
2)启动stdio-client子模块
上图中可以看到有一个子线程访问工具连接
3)访问地址
http://localhost:8080/ai/stdio/client?message=广州市的天气预报
说明:可以看到,其访问结果是正确的。
2.2 MCP-Server(SSE模式)
代码参考lesson10子模块下面的,sse-server和sse-client两个子模块
2.2.1 sse-server子模块
1)在lesson10子模块下,新建sse-server子模块,其pom引入如下:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 引入的是mcp-server --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webmvc</artifactId></dependency>
</dependencies>
2)配置application.properties文件
# 为了防止与客户端端口冲突,重新配置端口
server.port=8081
# 服务名称
spring.ai.mcp.server.name=sse-weather-server
# 服务版本
spring.ai.mcp.server.version=0.0.1
# 定义同步模式(必须设置)
spring.ai.mcp.server.type=SYNC
# 是否关闭stdio模式,因为webmvc支持stdio和sse,这里演示sse,关闭stdio
spring.ai.mcp.server.stdio=false
# 用于客户端发送消息的自定义 SSE 消息端点路径,以供网络传输使用
spring.ai.mcp.server.sse-message-endpoint=/mcp/message
# 用于网络传输的自定义 SSE 端点路径
spring.ai.mcp.server.sse-endpoint=/sse
注意:这里有几个点需要注意
1)spring.ai.mcp.server.stdio=false,关闭stdio模式,实际上webmvc也支持stdio模式,这里只是为了演示sse模式
2)这里没有把banner和控制台输出屏蔽,是因为sse模式不是通过控制台输出,因此没必要屏蔽
3)新建天气预报服务WeatherService:
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;@Service
public class WeatherService {@Autowiredprivate RestTemplate restTemplate;private static final String adcode = "adcode";@Tool(description = "获取中国城市的天气情况")public String getWeatherForecastByCity(String city) {// 获取城市的adcodeString result = restTemplate.getForObject("https://restapi.amap.com/v3/geocode/geo?address=西安&key=你的高德APIKEY", String.class);// 这里为了方便简单处理一下字符串获取adcode,正式的话需要解析json格式int startIndex = result.indexOf(adcode);String code = result.substring(startIndex+adcode.length()+3,result.indexOf(",",startIndex)-1);// 通过城市的adcode,进行获取天气预报result = restTemplate.getForObject("https://restapi.amap.com/v3/weather/weatherInfo?extensions=all&key=你的高德APIKEY&city="+code, String.class);return result;}}
4)由于使用RestTemplate访问API,因此配置RestTemplateConfig:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;@Configuration
public class RestTemplateConfig {@Beanpublic RestTemplate restTemplate() {return new RestTemplate();}
}
5)配置启动类Lesson10MCPServerSSEApplication :
import com.demo.lesson10.sse.server.service.WeatherService;
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;@SpringBootApplication
public class Lesson10MCPServerSSEApplication {@Beanpublic ToolCallbackProvider weatherTools(WeatherService weatherService) {return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();}public static void main(String[] args) {SpringApplication.run(Lesson10MCPServerSSEApplication.class, args);}
}
2.2.2 sse-client子模块
1)在lesson10子模块下,新建sse-client子模块,其pom引入如下:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-zhipuai</artifactId></dependency><!-- 引入的是mcp-client --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client</artifactId></dependency>
</dependencies>
2)配置application.properties文件
# 聊天模型
spring.ai.zhipuai.api-key=你的智谱模型的API KEY
spring.ai.zhipuai.chat.options.model=GLM-4-Flash-250414
spring.ai.zhipuai.chat.options.temperature=0.7# 指定mcp-servers的URL
spring.ai.mcp.client.type=SYNC
spring.ai.mcp.client.sse.connections.server1.url=http://localhost:8081
说明:由于sse-server没有特意配置路径,因此无需配置sse-endpoint
3)新建MCPClientSSEController 类进行访问
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
public class MCPClientSSEController {private ChatClient chatClient;public MCPClientSSEController(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools) {this.chatClient = chatClientBuilder.defaultToolCallbacks(tools).build();}/*** @param message 问题*/@GetMapping("/ai/sse/client")public String amapMaps(@RequestParam(value = "message", required = true) String message) {return this.chatClient.prompt().user(message).call().content();}}
4)新建Lesson10MCPClientSSEApplication 启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Lesson10MCPClientSSEApplication {public static void main(String[] args) {SpringApplication.run(Lesson10MCPClientSSEApplication.class, args);}}
2.2.3 演示效果
1)启动sse-server子模块
2)启动sse-client子模块
3)访问地址:
http://localhost:8080/ai/sse/client?message=广州市的天气预报
可以去看sse-server控制台的日志,有调用的日志
结语:本章演示通过2种方式建立了MCP-Server服务,对于MCP的内容还比较多,这里只是一个入门。如果对这一块感兴趣的朋友,需要先研究一下MCP协议,里面涉及的配置也可以参考官方网站。下一章将讲更为复杂的RAG框架。