spring-ai 学习系列(3)-MCP(stdio)
使用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启用

再次来问这个订单的状态

发现大模型似乎变聪明了!
静下来想想,发生了啥?
- 模型还是那个模型,deepseek并没有改变。
- 本地的mcp server springboot应用,也并不知道deepseek是何方大神
那么,这2个角色是怎么勾搭上的?显然是cherry studio这个媒婆(mcp host)牵线搭桥了,这个细节后面有机会咱们再分析。
文中代码:https://github.com/yjmyzz/spring-ai-sample/tree/day03
spring-ai 学习系列(3)-MCP(stdio)的更多相关文章
- Spring Boot 学习系列(10)—SpringBoot+JSP的使
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 解决问题 随着spring boot 框架的逐步使用,我们期望对于一些已有的系统进行改造,做成通用的脚手架, ...
- Spring Boot 学习系列(03)—jar or war,做出你的选择
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 两种打包方式 采用Spring Boot框架来构建项目,我们对项目的打包有两种方式可供选择,一种仍保持原有的 ...
- Spring Boot 学习系列(序)—Spring Boot
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. Spring Boot? Spring Boot 是由pivotal团队提供的一个基于Spring的全新框架 ...
- Spring Boot 学习系列(06)—采用log4j2记录日志
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 为什么选择log4j2 log4j2相比于log4j1.x和logback来说,具有更快的执行速度.同时也支 ...
- Spring Boot 学习系列(05)—自定义视图解析规则
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 自定义视图解析 在默认情况下Spring Boot 的MVC框架使用的视图解析ViewResolver类是C ...
- Spring Boot 学习系列(09)—自定义Bean的顺序加载
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. Bean 的顺序加载 有些场景中,我们希望编写的Bean能够按照指定的顺序进行加载.比如,有UserServ ...
- Spring Boot 学习系列(08)—自定义servlet、filter及listener
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 传统的filter及listener配置 在传统的Java web项目中,servlet.filter和li ...
- Spring Boot 学习系列(07)—properties文件读取
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 传统的properties读取方式 一般的,我们都可以自定义一个xxx.properties文件,然后在工程 ...
- Spring Boot 学习系列(04)—分而治之,多module打包
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 明确功能,各司其职 在一个结构清晰的项目中,一个没有module划分的结构显然不是最佳实践.有人会说可以在同 ...
- Spring Boot 学习系列(01)—从0到1,只需两分钟
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 快速构建 如果我们想基于spring mvc 提供一个简单的API查询服务,传统的方式,首先需要我们引入sp ...
随机推荐
- python初学之random()模块
##python小脚本 random()是不能直接访问的,需要导入 random 模块,然后通过 random 静态对象调用该方法. random.random()用于生成 一个指定范围内的随机符点数 ...
- ctf.show刷题记录_web(1-10)
ctf平台(ctfshow) `https://ctf.show/ 1.签到 解题:base64解码 ctfshow{0da357d0-359b-47e1-80dc-5c02212725e5} 2.w ...
- 关于μkeil v5.40(keil5) 如何使用STM32(ARM)虚拟下载器进行Proteus联调
最近我心血来潮,想用Proteus+keil5进行联调,但仔细在网上一找,全是某SDN扒下来的陈年老黑X,都快转出数字包浆了还在用,完完全全跟不上时代,也全是51单片机的版本,STM32(ARM)根本 ...
- Web前端杂乱知识复习
OSI OSI是Open System Interconnect的缩写,意为开放式系统互联.其各个层次的划分遵循下列原则: (1)同一层中的各网络节点都有相同的层次结构,具有同样的功能. (2 ...
- heapdump敏感信息提取工具-JDumpSpider(一) ,附下载链接
介绍 HeapDump敏感信息提取工具 在日常得渗透测试工作中,经常遇到spring actuator未授权漏洞,而且在实际过程中也常常会下载到heapdump这个文件.了解过这个文件的人知道,H ...
- js技术之获取当前元素的上一个/下一个兄弟级元素等元素的方法(获取上一个/下一个input)
一.说明 jQuery获取: jQuery.parent(expr),找父亲节点,可以传入expr进行过滤,比如$("span").parent()或者$("span&q ...
- JS 上下文 this 指向总结
这个 js 语言中的 this 和其他面向对象的语言有本质的不同, 也更复杂, 它更多取决于函数在不同场景下的调用方式, 要理解它并总结出它的规律的话, 优先要从上下文 这个概念认知说起. 理解上下文 ...
- vue3 基础-插件 plugin
前几篇我们介绍了 mixin 混入的的方式能实现对代码的复用, 而本篇将要介绍的 plugin 将会更加适合这种通用性功能的代码的复用和扩展. 最常用的场景, 比如轮播图就非常实用 plugin 来实 ...
- 5 easybr指纹浏览器内存修改教程
目的 navigator.deviceMemory可以暴露设备的物理内存和运行状态,被用于设备唯一性识别或判断设备等级. 通过伪造这类信息,可以增强防关联.防追踪能力. easybr指纹浏览器提供演示 ...
- React Native开发鸿蒙Next---图片浏览与保存的问题交流
React Native开发鸿蒙Next---图片浏览与保存的问题交流 之前介绍过利用鸿蒙三方RN组件@react-native-camera-roll/camera-roll保存图片到相册. Rea ...