2025-03-21 23:03:45
本文快速介绍一下springboot新出的框架内容mcp,通过springboot+mcp+junit,来快速构建一个模板项目。
先挂一下几个链接,首先是我的模板仓库地址:
gitee: https://gitee.com/dreamcenter/springboot-mcpserver-junit
github: https://github.com/dreamcenter/springboot-MCPserver-JUnit
接着是我的B站视频解说:
接着是参考文档地址:
首先引入spring-ai-mcp的依赖项
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-server-spring-boot-starter</artifactId>
<version>${mcp.verison}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
接着,在application.properties中,一定要做如下配置,否则后续启动会失败:
spring.main.banner-mode=off
logging.pattern.console=
这样基础的环境就已经搭建完毕了。我们需要创建一个服务,服务加上@Tool注解,这样才能被识别为mcp函数,对于函数的参数也可以加上@ToolParam注解,表明是mcp函数的参数。
package top.dreamcenter.mcp.service;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Service;
@Service
public class WeatherService {
@Tool(description = "通过城市名字获取温度[伪]")
public String getWeather(String cityName) {
return cityName + "今天的温度是" + cityName.length() * 10;
}
}
接着需要注册provider到容器中,我这里放在confiuration中集中注册了:
@Configuration
public class ToolCallbackProviderRegister {
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}
}
这时候其实就已经完整的搭建起来了,我们只需要 clean + install 即可获得一个含有mcp协议服务的jar包。
打开一个支持mcp的客户端,如我这里使用cherrystudio,找到并且配置上我们刚刚写的jar包,确认如果没有出错,那么就成功了:
接着我们找到一个带小扳手的模型(即支持mcp的大模型)。
工具中启用mcp的demo服务,询问温度。
然后,就得到利用mcp函数的结果啦!
当然,实际开发中肯定不是用cherrystudio来测试,我们需要引入junit,调用McpClient来测试该函数是否有效。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
引入依赖后,即可写测试代码,首先构建McpClient客户端,接着通过该客户端来调用函数得到结果。
@Test
public void myTest() {
ServerParameters parameters = ServerParameters.builder("java")
.args("-jar", "D:\\CODE\\codings\\springboot\\demo\\target\\demo-0.0.1-SNAPSHOT.jar").build();
McpSyncClient mcpClient = McpClient.sync(new StdioClientTransport(parameters)).build();
McpSchema.CallToolResult callToolResult = mcpClient.callTool(new McpSchema.CallToolRequest("getWeather",
Map.of("city", "南京")));
List<McpSchema.Content> content = callToolResult.content();
Assertions.assertNotNull(content);
Assertions.assertEquals(content.size(), 1);
McpSchema.TextContent result = (McpSchema.TextContent)content.get(0);
Assertions.assertEquals(result.text(), "\"南京的温度是2\"");
mcpClient.closeGracefully();
}
当然,上面的只是一个简单的单元测试,如果对每一个接口都CV上面的这一大串代码肯定不方便,而且jar包随着版本迭代,文件名称可能也会变动,不适合单元测试,所以我封装了一个测试模板。首先进入单元任务的时候,BeforeAll查找jar包文件路径,接着在每个测试单元开始前创建一个MCPClient,并且在单元结束后自动closeGracefully进行关闭。
package top.dreamcenter.mcp;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.McpSyncClient;
import io.modelcontextprotocol.client.transport.ServerParameters;
import io.modelcontextprotocol.client.transport.StdioClientTransport;
import io.modelcontextprotocol.spec.McpSchema;
import org.junit.jupiter.api.*;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
public class CommonTest {
private static String jarFilePath;
private McpSyncClient mcpClient;
@BeforeAll
public static void init() throws URISyntaxException {
// find jar file path
URL resource = CommonTest.class.getClassLoader().getResource("locate.txt");
Assertions.assertNotNull(resource, "load junit classLoader context fail");
Path targetPath = Paths.get(resource.toURI()).getParent().getParent();
File files = targetPath.toFile();
File[] files1 = files.listFiles(file -> (file.getName().endsWith(".jar")));
Assertions.assertNotNull(files1, "Can not find the generated jar file under [target] directory");
Assertions.assertEquals(files1.length, 1, "Find more then one jar file under [target] directory");
jarFilePath = files1[0].getAbsolutePath();
}
@BeforeEach
public void initClient() {
ServerParameters serverParameters = ServerParameters.builder("java")
.args("-jar", jarFilePath).build();
mcpClient = McpClient.sync(new StdioClientTransport(serverParameters)).build();
}
@AfterEach
public void close() {
if (mcpClient != null) {
mcpClient.closeGracefully();
}
}
@Test
public void testWeatherMCP() {
var weather = quickCall("getWeather", Map.of("cityName", "南京"));
List<McpSchema.Content> contentList = weather.content();
String res = easyTextContent(contentList);
Assertions.assertEquals("\"南京今天的温度是20\"", res);
}
@Test
public void testNumMCP() {
var num = quickCall("judgeIfOdd", Map.of("num", "1"));
List<McpSchema.Content> contentList = num.content();
String res = easyTextContent(contentList);
Assertions.assertEquals("\"1不是双数\"", res);
}
private McpSchema.CallToolResult quickCall(String name, Map<String, Object> arguments) {
return mcpClient.callTool(new McpSchema.CallToolRequest(name,arguments));
}
private String easyTextContent(List<McpSchema.Content> contentList) {
assert contentList != null && contentList.size() == 1;
McpSchema.TextContent textContent = (McpSchema.TextContent) contentList.get(0);
return textContent.text();
}
}
ok,以上就是本次的博客日记啦!嘻嘻QAQ