使用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)的更多相关文章

  1. Spring Boot 学习系列(10)—SpringBoot+JSP的使

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 解决问题 随着spring boot 框架的逐步使用,我们期望对于一些已有的系统进行改造,做成通用的脚手架, ...

  2. Spring Boot 学习系列(03)—jar or war,做出你的选择

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 两种打包方式 采用Spring Boot框架来构建项目,我们对项目的打包有两种方式可供选择,一种仍保持原有的 ...

  3. Spring Boot 学习系列(序)—Spring Boot

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. Spring Boot? Spring Boot 是由pivotal团队提供的一个基于Spring的全新框架 ...

  4. Spring Boot 学习系列(06)—采用log4j2记录日志

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 为什么选择log4j2 log4j2相比于log4j1.x和logback来说,具有更快的执行速度.同时也支 ...

  5. Spring Boot 学习系列(05)—自定义视图解析规则

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 自定义视图解析 在默认情况下Spring Boot 的MVC框架使用的视图解析ViewResolver类是C ...

  6. Spring Boot 学习系列(09)—自定义Bean的顺序加载

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. Bean 的顺序加载 有些场景中,我们希望编写的Bean能够按照指定的顺序进行加载.比如,有UserServ ...

  7. Spring Boot 学习系列(08)—自定义servlet、filter及listener

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 传统的filter及listener配置 在传统的Java web项目中,servlet.filter和li ...

  8. Spring Boot 学习系列(07)—properties文件读取

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 传统的properties读取方式 一般的,我们都可以自定义一个xxx.properties文件,然后在工程 ...

  9. Spring Boot 学习系列(04)—分而治之,多module打包

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 明确功能,各司其职 在一个结构清晰的项目中,一个没有module划分的结构显然不是最佳实践.有人会说可以在同 ...

  10. Spring Boot 学习系列(01)—从0到1,只需两分钟

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 快速构建 如果我们想基于spring mvc 提供一个简单的API查询服务,传统的方式,首先需要我们引入sp ...

随机推荐

  1. python初学之random()模块

    ##python小脚本 random()是不能直接访问的,需要导入 random 模块,然后通过 random 静态对象调用该方法. random.random()用于生成 一个指定范围内的随机符点数 ...

  2. ctf.show刷题记录_web(1-10)

    ctf平台(ctfshow) `https://ctf.show/ 1.签到 解题:base64解码 ctfshow{0da357d0-359b-47e1-80dc-5c02212725e5} 2.w ...

  3. 关于μkeil v5.40(keil5) 如何使用STM32(ARM)虚拟下载器进行Proteus联调

    最近我心血来潮,想用Proteus+keil5进行联调,但仔细在网上一找,全是某SDN扒下来的陈年老黑X,都快转出数字包浆了还在用,完完全全跟不上时代,也全是51单片机的版本,STM32(ARM)根本 ...

  4. Web前端杂乱知识复习

    OSI OSI是Open System Interconnect的缩写,意为开放式系统互联.其各个层次的划分遵循下列原则: ​ (1)同一层中的各网络节点都有相同的层次结构,具有同样的功能. ​ (2 ...

  5. heapdump敏感信息提取工具-JDumpSpider(一) ,附下载链接

    ​ 介绍 HeapDump敏感信息提取工具 在日常得渗透测试工作中,经常遇到spring actuator未授权漏洞,而且在实际过程中也常常会下载到heapdump这个文件.了解过这个文件的人知道,H ...

  6. js技术之获取当前元素的上一个/下一个兄弟级元素等元素的方法(获取上一个/下一个input)

    一.说明 jQuery获取: jQuery.parent(expr),找父亲节点,可以传入expr进行过滤,比如$("span").parent()或者$("span&q ...

  7. JS 上下文 this 指向总结

    这个 js 语言中的 this 和其他面向对象的语言有本质的不同, 也更复杂, 它更多取决于函数在不同场景下的调用方式, 要理解它并总结出它的规律的话, 优先要从上下文 这个概念认知说起. 理解上下文 ...

  8. vue3 基础-插件 plugin

    前几篇我们介绍了 mixin 混入的的方式能实现对代码的复用, 而本篇将要介绍的 plugin 将会更加适合这种通用性功能的代码的复用和扩展. 最常用的场景, 比如轮播图就非常实用 plugin 来实 ...

  9. 5 easybr指纹浏览器内存修改教程

    目的 navigator.deviceMemory可以暴露设备的物理内存和运行状态,被用于设备唯一性识别或判断设备等级. 通过伪造这类信息,可以增强防关联.防追踪能力. easybr指纹浏览器提供演示 ...

  10. React Native开发鸿蒙Next---图片浏览与保存的问题交流

    React Native开发鸿蒙Next---图片浏览与保存的问题交流 之前介绍过利用鸿蒙三方RN组件@react-native-camera-roll/camera-roll保存图片到相册. Rea ...