1、概述
MCP的出现为AI的落地发展摁下了加速键,我们通过MCP可以解耦工具和大模型之间的强耦合关系。使一个工具服务可以供不同厂商的大模型调用。加速了工具服务组件化发展。
具体介绍可以看 模型上下文协议(MCP):AI 代理与真实世界交互的桥梁
这篇文章介绍MCP-server的开发及相关调试及一些注意点。文章最后附有全部代码
2、MCP server开发
MCP server 开发有两种模式,一种是stdio模式,另一种是sse模式。stdio相当于是依赖包加载到本地进行运行,
2.1、依赖相关
开发mcp对环境有要求,jdk要求17+,SpringBoot要求【3.4.0+】
MCP-server开发有两个依赖包,底层的技术一个是依赖webmvc,另一个是依赖webflux。webmvc是基于servlet的阻塞式模型,webflux是一个异步非阻塞式的 Web 框架,它能够充分利用多核 CPU 的硬件资源去处理大量的并发请求。
具体两个依赖如下(目前最新版本【1.0.0-M7】):
<!-- webmvc-mcp --><dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> <version>1.0.0-M7</version></dependency><!-- webflux-mcp --><dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webflux</artifactId> <version>1.0.0-M7</version></dependency>
我们选其中之一【webmvc】进行演示讲解。
2.2、代码开发
大家可以看下目录结构,结构比较简单。主要核心是【OpenMeteoService】和【MCPSpringAiRegisterConfiguration】
服务开发
我们使用open-meteo服务,【open-meteo】是一个开源免费的天气api服务。
这里我们开发两个MCP工具
根据经纬度查询天气
根据经纬度查询空气质量
配置开发
初始化restTemplate
Tools服务注册
application.yml配置
目前使用的都是默认配置,只添加了一个name和version,具体配置可参考官网
目前【sse-endpoint】配置不起作用,原因是依赖初始化时使用默认值,因此如果要使用自定义,需要重写初始化。
解决方案:创建配置,重写WebMvcSseServerTransportProvider初始化
3、接口验证
我们使用开源组件inspector进行验证mcp服务
运行inspector需要安装node、npm、npx环境
执行【npx @modelcontextprotocol/inspector】命令便可安装运行启动服务地址为【http://127.0.0.1:6274】
具体调试信息如下:
添加【sse-endpoint】配置后如下:
4、总结
目前只是运行了一个简单的demo,离服务化还是较远。但提出了工具组件化的概念。
对于MCP,还有好多需要完善,比如鉴权、相关依赖完善、各个服务提供商的数据是否完备等。但是这一技术理念还是值得我们现在去理解去学习。
5、代码
代码结构如下:
BeanConfiguration
package com.young.server.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.client.RestTemplate;/** * @author 刘子洋 * @date 2025年05月13日 下午3:22 * @description:初始化组件bean */@Configurationpublic class BeanConfiguration { @Bean public RestTemplate restTemplate() { return new RestTemplate(); }}
MCPSpringAiRegisterConfiguration
package com.young.server.config;import com.young.server.mcp.springAi.OpenMeteoService;import org.springframework.ai.tool.ToolCallbackProvider;import org.springframework.ai.tool.method.MethodToolCallbackProvider;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * @author 刘子洋 * @date 2025年05月13日 下午3:18 * @description:MCP 服务注册 */@Configurationpublic class MCPSpringAiRegisterConfiguration { /** * 创建并配置一个天气工具回调提供者 * 该方法通过接收一个OpenMeteoService实例来构建一个MethodToolCallbackProvider对象 * 主要用于在调用OpenMeteoService的方法时提供回调功能 * * @param openMete OpenMeteoService的实例,用于获取天气信息 * @return 返回一个配置好的MethodToolCallbackProvider对象 */ @Bean public ToolCallbackProvider weatherTools(OpenMeteoService openMete){ return MethodToolCallbackProvider.builder() .toolObjects(openMete) .build(); }}
MyMcpServerConfig
package com.young.server.config;//package com.young.server.config;import com.fasterxml.jackson.databind.ObjectMapper;import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;import org.springframework.beans.factory.ObjectProvider;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.web.servlet.function.RouterFunction;import org.springframework.web.servlet.function.ServerResponse;/** * @author 刘子洋 * @date 2025年05月14日 上午10:53 * @description:重写 WebMvcSseServerTransportProvider 初始化 */@Configurationpublic class MyMcpServerConfig { @Bean @Primary public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider( ObjectProvider<ObjectMapper> objectMapperProvider, McpServerProperties serverProperties) { ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); return new WebMvcSseServerTransportProvider(objectMapper, serverProperties.getSseMessageEndpoint(), serverProperties.getSseEndpoint()); } @Bean public RouterFunction<ServerResponse> mvcMcpRouterFunction(WebMvcSseServerTransportProvider transportProvider) { return transportProvider.getRouterFunction(); }}
OpenMeteoService
package com.young.server.mcp.springAi;import lombok.extern.slf4j.Slf4j;import org.springframework.ai.tool.annotation.Tool;import org.springframework.ai.tool.annotation.ToolParam;import org.springframework.stereotype.Service;import org.springframework.web.client.RestTemplate;/** * @author 刘子洋 * @date 2025年05月13日 下午2:53 * @description:空气服务类 */@Service@Slf4jpublic class OpenMeteoService { private final RestTemplate restTemplate; private static final String WEATHER_TEMPLATE = "当前位置(纬度:%s,经度:%s)的天气信息:\n %s"; public OpenMeteoService(RestTemplate restTemplate) { this.restTemplate = restTemplate; } /** * 根据给定的经纬度获取天气预报 * 此方法使用RestTemplate调用外部天气API,获取JSON格式的天气信息,并将其格式化为字符串返回 * * @param latitude 纬度,表示地理位置的南北位置 * @param longitude 经度,表示地理位置的东西位置 * @return 格式化后的天气预报信息字符串 */ @Tool(description = "根据给定的经纬度获取天气预报") public String getWeatherForecastByLocation( @ToolParam(description = "经纬度,例如:39.9042") String latitude, @ToolParam(description = "经纬度,例如:116.4074") String longitude ) { String url = "https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&hourly=temperature_2m&timezone=auto"; String response = restTemplate.getForObject(url, String.class, latitude, longitude); log.info("response: {}", response); return String.format(WEATHER_TEMPLATE, latitude, longitude, response); } /** * 根据给定的经纬度获取空气质量信息 * * @param latitude 纬度,表示地理位置的南北位置 * @param longitude 经度,表示地理位置的东西位置 * @return 空气质量信息字符串 */ @Tool(description = "根据经纬度获取空气质量信息") public String getAirQualityByLocation( @ToolParam(description = "经纬度,例如:39.9042") String latitude, @ToolParam(description = "经纬度,例如:116.4074") String longitude ) { // 模拟数据,实际应用中应调用真实API return "当前位置(纬度:" + latitude + ",经度:" + longitude + ")的空气质量:\n" + "- PM2.5: 15 μg/m³ (优)\n" + "- PM10: 28 μg/m³ (良)\n" + "- 空气质量指数(AQI): 42 (优)\n" + "- 主要污染物: 无"; }}
maven(有些组件没有在maven中心仓库,因此自定义加入了一些仓库)
<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 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.young</groupId> <artifactId>my-agi</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>mcp-server</artifactId> <packaging>war</packaging> <name>mcp-server Maven Webapp</name> <url>http://maven.apache.org</url> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId> <version>1.0.0-M7</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>3.4.0</version> </plugin> </plugins> </build> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> <repository> <id>spring-snapshots</id> <name>Spring Snapshots</name> <url>https://repo.spring.io/snapshot</url> <releases> <enabled>false</enabled> </releases> </repository> <repository> <id>central-portal-snapshots</id> <name>Central Portal Snapshots</name> <url>https://central.sonatype.com/repository/maven-snapshots/</url> <releases> <enabled>false</enabled> </releases> <snapshots> <enabled>true</enabled> </snapshots> </repository> </repositories></project>