使用spring-ai创建1个MCP Server很容易,下面演示MCP(stdio模式)的写法:
一、添加依赖项
1 <dependency>
2 <groupId>org.springframework.ai</groupId>
3 <artifactId>spring-ai-starter-mcp-server</artifactId>
4 <version>1.0.0</version>
5 </dependency>
二、创建1个MCP Server服务
1 import org.springframework.ai.tool.annotation.Tool;
2 import org.springframework.ai.tool.annotation.ToolParam;
3 import org.springframework.stereotype.Service;
4
5 /**
6 * @author 菩提树下的杨过
7 */
8 @Service
9 public class OrderService {
10
11
12 @Tool(name = "queryOrderStatus",
13 description = "根据订单号查询订单状态")
14 public String queryOrderStatus(@ToolParam(required = true, description = "订单号,格式为8位数字,比如:25070601") String orderNo) {
15 return switch (orderNo) {
16 case "25070601" -> "订单号:" + orderNo + ",订单状态:已发货";
17 case "25070602" -> "订单号:" + orderNo + ",订单状态:已完成";
18 case "25070603" -> "订单号:" + orderNo + ",订单状态:已取消";
19 default -> "订单号:" + orderNo + ",订单状态:未知";
20 };
21 }
22 }
可以看到,几乎跟常规的后端Service开发没区别,只是多了@Tool、@ToolParam注解,是给MCP Host和大模型使用的。
三、暴露MCP服务
MCP Server必须对外暴露出来,才能被调用方发现。
1 import com.cnblogs.yjmyzz.mcp.server.OrderService;
2 import org.springframework.ai.tool.ToolCallbackProvider;
3 import org.springframework.ai.tool.method.MethodToolCallbackProvider;
4 import org.springframework.boot.SpringApplication;
5 import org.springframework.boot.autoconfigure.SpringBootApplication;
6 import org.springframework.context.annotation.Bean;
7
8 @SpringBootApplication
9 public class SpringAiApplication {
10
11 public static void main(String[] args) {
12 SpringApplication.run(SpringAiApplication.class, args);
13 }
14
15 @Bean
16 public ToolCallbackProvider orderTools(OrderService orderService) {
17 return MethodToolCallbackProvider.builder().toolObjects(orderService).build();
18 }
19
20 }
编译一下,会在本地生成jar包,类似D:\code\spring-ai-sample\target\spring-ai-0.0.1-SNAPSHOT.jar
四、调整yaml
spring:
main:
banner-mode: off
logging:
pattern:
console:
这一步非常重要,MCP的stdio模式,client与server通过标准输入输出(Standard Input/Output )进行通讯(通俗来说,就是控制台的输入/输出)。如果不关闭启动banner以及控制台的输出,会影响 MCP Client调用时的结果解析。
五、编写MCP Client测试
MCP Server写好了,如何验证功能正常呢,写个Client来调用一下
package com.cnblogs.yjmyzz.mcp.client;
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.Test;
import java.util.Map;
/**
* @author 菩提树下的杨过
*/
public class McpClientSample {
@Test
public void testMcpClientSample() {
ServerParameters stdioParams = ServerParameters.builder("java")
.args("-jar", "D:\\code\\spring-ai-sample\\target\\spring-ai-0.0.1-SNAPSHOT.jar")
.build();
StdioClientTransport stdioTransport = new StdioClientTransport(stdioParams);
//创建MCP Client
McpSyncClient mcpClient = McpClient.sync(stdioTransport).build();
//这一步初始化执行完后,其实就是把jar中的Spring boot应用启动了(MCP Server就能对外提供服务了)
mcpClient.initialize();
McpSchema.ListToolsResult toolsList = mcpClient.listTools();
//打印工具信息(即:orderService的queryOrderStatus方法信息)
System.out.println(toolsList);
//调用MCP Server
McpSchema.CallToolResult orderStatus = mcpClient.callTool(
//这里我们只有1个工具,所以直接取第一个
new McpSchema.CallToolRequest(toolsList.tools().getFirst().name(),
Map.of("orderNo", "25070601")));
//打印调用结果
System.out.println(orderStatus);
//关闭MCP Client
//这里一定要记得关闭,否则MCP Server的Spring boot应用不会退出(大家可以这里打个断点,然后http://localhost:8080/api/hello 访问一下,应该能访问通)
mcpClient.closeGracefully();
}
}
正常的话,应该会看到以下输出:
18:59:41.289 [pool-1-thread-1] INFO io.modelcontextprotocol.client.McpAsyncClient -- Server response with Protocol: 2024-11-05, Capabilities: ServerCapabilities[completions=CompletionCapabilities[], experimental=null, logging=LoggingCapabilities[], prompts=PromptCapabilities[listChanged=true], resources=ResourceCapabilities[subscribe=false, listChanged=true], tools=ToolCapabilities[listChanged=true]], Info: Implementation[name=mcp-server, version=1.0.0] and Instructions null
ListToolsResult[tools=[Tool[name=queryOrderStatus, description=根据订单号查询订单状态, inputSchema=JsonSchema[type=object, properties={orderNo={type=string, description=订单号,格式为8位数字,比如:25070601}}, required=[orderNo], additionalProperties=false, defs=null, definitions=null]]], nextCursor=null]
CallToolResult[content=[TextContent[audience=null, priority=null, text="订单号:25070601,订单状态:已发货"]], isError=false]
18:59:41.615 [parallel-6] WARN io.modelcontextprotocol.client.transport.StdioClientTransport -- Process terminated with code 1
正好4行:
第1行,表示初始化启动成功
第2行,列出了当前应用暴露的所有工具(即:MCP Server方法)
第3行,是调用工具queryOrderStatus的结果
第4行,表示关闭Spring Boot应用(MCP Server)
如果运行失败,强烈建议先看看任务管理器里,是否有java应用占用了默认端口8080,导致MCP Server的Spring boot应用启动失败
看到这里,可能有朋友会懵,这跟大模型有什么关系 ?这不就是常规的后端开发,跟写个业务接口或SOA服务一样么?其实我刚开始接触时,也是这么认为的。
tips:前面提到了client与server是通过控制台I/O来通讯的,上述的测试过程其实也可以完全在控制台手动输入json来进行
上述测试代码其实就是发出了几条json,类似:
{"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"yjmyzz client","version":"0.0.1"}},"jsonrpc":"2.0","id":0}
{"method":"notifications/initialized","jsonrpc":"2.0"}
{"method":"ping","jsonrpc":"2.0","id":1}
{"method":"tools/list","jsonrpc":"2.0","id":1}
... 后面就不一一列举了
可以先用mvn spring-boot:run,把server启动起来,然后一条条把上面的json输入到终端里,就相当于纯手动测试了
当然,实际应用中,更通用的做法,是找1个现成的MCP Host,来承载MCP client
六、使用MCP Host
6.1 下载MCP Host
建议大家使用免费开源的 Cherry Studio,网址:https://www.cherry-ai.com/download,它能对接市面上的各大主流模型
以DeepSeek为例,我们添加api 密钥
然后发起个对话,看看deepseek是否能正常工作
显然,这个订单号deepSeek是不知道的。
6.2 添加MCP Server
参考下图:
右上角,启动保存时,如果失败,也强烈建议查看任务管理器,是否端口占用(或有其它java进程占用)。启动保存后,本质上就把这个Spring Boot应用给run起来了,跟部署到tomcat其实是类似的。
6.3 会话中启用MCP Server
再回到聊天界面,把刚才这个新加的MCP Server启用
再次来问这个订单的状态
发现大模型似乎变聪明了!
静下来想想,发生了啥?
那么,这2个角色是怎么勾搭上的?显然是cherry studio这个媒婆(mcp host)牵线搭桥了,这个细节后面有机会咱们再分析。