快速掌握MCP——Spring AI MCP包教包会_spring.ai.mcp.server.stdio
最近几个月AI的发展非常快,各种大模型、智能体、AI名词和技术和框架层出不穷,作为一个业余小红书博主的我最近总刷到MCP这个关键字,看着有点高级我也来学习一下。
1.SpringAI与functionCall简单回顾
前几个月我曾写过两篇关于SpringAI的基础入门文章:
- 《使用SpringAI快速实现离线/本地大模型应用》
- 《使用Spring AI中的RAG技术,实现私有业务领域的大模型系统》
在这两篇文章中,对于私有领域数据的处理使用的是RAG和FunctionCall技术来实现的。
而如今再打开SpringAI的官网,Function Calling部分已被标记为Deprecated(已废弃),当前已被更具有范式的Tool Calling取代。
快速回顾可参考开源的playground-flight-booking(航班预定)项目:https://github.com/tzolov/playground-flight-bookin
项目截图:
2.MCP简介
MCP的全称为:Model Context Protocol,是一个开源的协议,可以让大模型应用更方便的集成各种数据源和工具,经典举例为:使用Type-C接口适配各种电器,使用统一的接口为大模型应用提供各种工具。
MCP官网中将MCP主要分为:MCP Hosts、MCP Clients、MCP Servers、Local Data Sources、Remote Services这几个部分。详细定义:MCP General architecture
同时,MCP官网还提供了多种语言的SDK,python、java、c#、typescript等都有。
在本文中所使用到的框架为spring-ai,其中的mcp部分也是基于MCP官网的mcp-java-sdk开发的。
在SpringAI中,它将MCP包装成了2个starter,MCP Client和MCP Server,单看概念有些空洞,本文用几个例子快速体验一把。
3.MCP Client
MCP Client是MCP Server的调用者,常与AI智能体结合在一起。
在mcp官网的介绍中mcp client和mcp server是1:1连接关系,在spring-ai中的MCP client可以理解为mcp host和mcp client的结合体,简单理解就是一个客户端,调用MCP SERVER用的。
3.1 第三方MCP服务
MCP SERVER可以是自己开发的,也可以是网上能访问到的公有MCP SERVER。
为了快速体验到MCP,这里直接使用网上的一个MCP Server调用一下。
网上的MCP服务可以从MCP Server仓库https://mcp.so/servers 搜索,截图如下:
3.2 单MCP体验(filesystem)
先体验一个简单的mcp服务:filesystem
单独启动此MCP服务的命令如下(后面跟的是允许访问的目录):
npx -y @modelcontextprotocol/server-filesystem E:\\tmp\\github\\springai-demo\\target
如无npx命令,就用node.js安装一下npx:npm install -g npx
据说在命令行中使用json-rpc文本可以与stdio的mcp服务交互,但我实测没有反应暂时不管了。
我们把它改造为spring-ai的mcp-client方式调用此mcp,主要代码为:
@SpringBootApplicationpublic class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}@Beanpublic CommandLineRunner predefinedQuestions(ChatClient.Builder chatClientBuilder,McpSyncClient mcpClient, ConfigurableApplicationContext context) {return args -> {var chatClient = chatClientBuilder.defaultTools(new SyncMcpToolCallbackProvider(mcpClient)).build();System.out.println(\"Running predefined questions with AI model responses:\\n\");// Question 1String question1 = \"列出当前可以操作的所有文件\";System.out.println(\"QUESTION: \" + question1);System.out.println(\"ASSISTANT: \" + chatClient.prompt(question1).call().content());// Question 2String question2 = \"请总结 target/spring-ai-mcp-overview.txt 文件的内容,并将总结的内容以一个Markdown格式的新文件保存为 target/summary.md \";System.out.println(\"\\nQUESTION: \" + question2);System.out.println(\"ASSISTANT: \" +chatClient.prompt(question2).call().content());context.close();};}@Bean(destroyMethod = \"close\")public McpSyncClient mcpClient() {// windows时使用npx.cmd,linux时使用npxvar stdioParams = ServerParameters.builder(\"npx.cmd\").args(\"-y\", \"@modelcontextprotocol/server-filesystem\", getDbPath()).build();var mcpClient = McpClient.sync(new StdioClientTransport(stdioParams)).requestTimeout(Duration.ofSeconds(10)).build();var init = mcpClient.initialize();System.out.println(\"MCP Initialized: \" + init);return mcpClient;}private static String getDbPath() {return Paths.get(System.getProperty(\"user.dir\"), \"target\").toString();}}
使用的主要依赖为:
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-client</artifactId></dependency>
运行结果截图:
如截图所示,它成功读取到了系统中的指定文件,并用LLM进行了分析总结,还向系统的磁盘中写入了summary.md文件。
完整代码参考:model-context-protocol/filesystem
3.3 多MCP体验(mcp-servers-config.json)
上面的代码中创建mcp-client的代码不是很优雅,且每次要使用一个mcp-server就要写一段那样的代码,接下来我们看下如何简化。
首先创建一个mcp-servers-config.json配置文件,在里面定义要使用到的mcp-server
{ \"mcpServers\": { \"server-filesystem\": { \"command\": \"npx.cmd\", \"args\": [ \"-y\", \"@modelcontextprotocol/server-filesystem\", \"E:\\tmp\\github\\spring-ai-examples\\target\" ], \"env\": { } }, \"amap-maps\": { \"command\": \"npx.cmd\", \"args\": [ \"-y\", \"@amap/amap-maps-mcp-server\" ], \"env\": { \"AMAP_MAPS_API_KEY\": \"REPLACE_YOUR_KEY\" } } }}
其中的@amap/amap-maps-mcp-server
为高德地图的mcp-server,AMAP_MAPS_API_KEY的内容为我们在高德地图所申请的api_key。
之后编写应用代码:
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public CommandLineRunner predefinedQuestions(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools, ConfigurableApplicationContext context) { return args -> { var chatClient = chatClientBuilder .defaultTools(tools) .build(); System.out.println(\"Running predefined questions with AI model responses:\\n\"); String question2 = \"请为我计划一次成都三岔美食一日游。尽量给出更舒适的出行安排,当然,也要注意天气状况,并将最终的并将内容以一个Markdown格式的新文件保存为 target/TripPlan.md\\n\"; System.out.println(\"\\nQUESTION: \" + question2); System.out.println(\"ASSISTANT: \" + chatClient.prompt(question2).call().content()); context.close(); }; }}
对应的配置文件application.properties内容为:
spring.application.name=mcpspring.main.web-application-type=nonespring.ai.openai.base-url=https://api.deepseek.com/spring.ai.openai.api-key=REPLACE_YOUR_KEYspring.ai.openai.chat.options.model=deepseek-chatspring.ai.mcp.client.toolcallback.enabled=truespring.ai.mcp.client.stdio.servers-configuration: classpath:mcp-servers-config.jsonlogging.level.io.modelcontextprotocol.client=DEBUGlogging.level.io.modelcontextprotocol.spec=DEBUG
为了便于观察分析,建议初学时将日志的级别设置为DEBUG,可以看到详细的输入输出
运行示例截图:
代码中,我们提交的问题为:请为我计划一次成都三岔美食一日游。尽量给出更舒适的出行安排,当然,也要注意天气状况,并将最终的并将内容以一个Markdown格式的新文件保存为 target/TripPlan.md
输出的响应为:
ASSISTANT: 已成功为您计划了一次成都三岔美食一日游,并将行程安排保存为 `target/TripPlan.md` 文件。以下是行程的简要概述:### 天气情况- **2025-04-03(周四)**:多云转阴,白天温度21°C,夜间温度11°C,北风1-3级。- **2025-04-06(周日)**:多云,白天温度20°C,夜间温度12°C,北风1-3级。### 美食推荐包括羊肉汤、川菜、火锅等,如:- 汪老八羊肉汤- 苏帮主三样菜- 江北老灶火锅### 行程安排- 上午:抵达后品尝羊肉汤。- 中午:享用地道川菜。- 下午:游览三岔湖并品尝鲜鱼料理。- 晚上:体验麻辣火锅后返程。请查看 `target/TripPlan.md` 获取完整详情!祝您旅途愉快!
从这里可以看出:程序先调用高德地图-MCP生成了旅游计划,然后又调用了filesystem-mcp将详细的旅游计划以文件的方式保存到了系统中。
短短几行代码,一个简单的智能体应用就完成了。
对于需要调用到多个mcp-server的场景,也更推荐使用servers-configuration配置文件的方式配置。详细可参考:https://docs.spring.io/spring-ai/reference/api/mcp/mcp-client-boot-starter-docs.html
3.4 Playwright自动化
在有了上面的两个示例铺垫后,接下来我们再实现一个AI产品的常见功能:提取某个网页中的指定数据
使用的MCP服务为:playwright。其mcp服务代码仓库为:https://github.com/microsoft/playwright-mcp
使用playwright前我们需要在系统中先安装它,命令为:
pip install playwrightplaywright install # 自动安装浏览器驱动
之后编写spring-ai代码,先编写配置文件mcp-servers-config.json
内容为:
{ \"mcpServers\": { \"playwright\": { \"command\": \"npx.cmd\", \"args\": [ \"@playwright/mcp@latest\" ] } }}
编写JAVA代码:
@SpringBootApplicationpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public CommandLineRunner predefinedQuestions(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools, ConfigurableApplicationContext context) { return args -> { var chatClient = chatClientBuilder .defaultTools(tools) .build(); System.out.println(\"Running predefined questions with AI model responses:\\n\"); String question2 = \"访问这个链接 https://mp.weixin.qq.com/s/7oQG35ECQeJiX6J3Uz3FIQ ,提取出里面的所有旅游景点名称\\n\"; System.out.println(\"\\nQUESTION: \" + question2); System.out.println(\"ASSISTANT: \" + chatClient.prompt(question2).call().content()); context.close(); }; }}
其中我们的需求为:访问这个链接 https://mp.weixin.qq.com/s/7oQG35ECQeJiX6J3Uz3FIQ ,提取出里面的所有旅游景点名称
运行截图:
可以看到它自动调用了我们的浏览器,并按照花的分类提取总结出了旅游景点,非常完美。
程序输出内容如下:
ASSISTANT: 文章中提到的旅游景点(赏花打卡点)如下:1. **樱桃花**: - 贾家街道百年樱桃园 - 菠萝乌龟坡 - 贾家桂花村 - 丹景街道张家沟村 - 武庙镇竹园村马道子 - 武庙社区 - 芦葭镇仁里村优果缘农庄2. **杏花**: - 贾家街道百年樱桃园 - 武庙镇垮龙山3. **油菜花**: - 养马街道尹家祠村1组 - 贾家街道天宫社区党群服务中心外 - 丹景街道藕埝村4. **桃花**: - 养马街道金渔桃缘景区 - 贾家街道东来桃源景区 - 快乐村桃示范基地5. **杏梅花**: - 武庙镇团堡村这些景点分布在成都东部新区,适合春季赏花游玩。
简简单单,一个具备AI网页爬虫分析能力的应用就做好啦。
与Playwright-mcp类似的还有browser-use,但使用下来感觉这个Playwright-mcp更简单。
4.MCP服务调试(MCP Inspector)
为了便于调试mcp服务,mcp官方提供了MCP Inspector工具,支持STDIO和SSE两种类型的MCP服务,可查看和调试MCP服务的tool等信息,非常有用。
运行mcp-inspector需要使用到npx,为避免环境问题推荐使用nvm。
nvm list available
nvm use 22.14.0
nvm ls
以运行mcp-inspector的0.7.0版本为例(使用最新版本去掉@0.7.0),命令如下:
npx @modelcontextprotocol/inspector@0.7.0
如启动失败,可尝试用npm cache clean --force
解决
使用时在浏览器中访问命令行输出的url,如这里的:http://localhost:5173
。
以调试高德地图的mcp服务为例:在Command中输入npx
,在Arguments中输入@amap/amap-maps-mcp-server
,之后再点连接(Connect)。
(高德MCP需要API_KEY,连接前还需要配置对应的变量,否则会报错:AMAP_MAPS_API_KEY environment variable is not set
)
调试一下高德地图的mcp提供的关键词搜索tool:成都爬山的旅游景点
输出内容为:
{\"suggestion\":{\"keywords\":[],\"ciytes\":[]},\"pois\":[{\"id\":\"B001C06PA9\",\"name\":\"都江堰景区\",\"address\":\"公园路\",\"typecode\":\"110202\"},{\"id\":\"B001C06ESL\",\"name\":\"青城山景区\",\"address\":\"青城山路168号\",\"typecode\":\"110201\"},{\"id\":\"B0FFGNLFOI\",\"name\":\"天然阁\",\"address\":\"青城山镇青城山路168号青城山景区(东南角)\",\"typecode\":\"110000\"},{\"id\":\"B0FFINNKVC\",\"name\":\"龙泉山城市森林公园\",\"address\":\"茶店街道\",\"typecode\":\"110101\"},{\"id\":\"B0FFGQ3K69\",\"name\":\"五凤溪古镇\",\"address\":\"五凤镇五凤溪景区\",\"typecode\":\"110202\"},{\"id\":\"B0FFLDEJ9E\",\"name\":\"成都龙泉山丹景台旅游景区\",\"address\":\"老马埂附近\",\"typecode\":\"110200\"},{\"id\":\"B001C7WE5S\",\"name\":\"成都大熊猫繁育研究基地\",\"address\":\"熊猫大道1375号\",\"typecode\":\"110202\"},{\"id\":\"B001C7X8QA\",\"name\":\"人民公园\",\"address\":\"小南街8号(人民公园地铁站B口步行240米)\",\"typecode\":\"110202\"},{\"id\":\"B0FFF06JQQ\",\"name\":\"熊猫谷\",\"address\":\"环山旅游路玉堂段408号\",\"typecode\":\"110102\"},{\"id\":\"B001C05SU4\",\"name\":\"成都市植物园\",\"address\":\"蓉都大道天回路1116号\",\"typecode\":\"110103\"},{\"id\":\"B001C7X564\",\"name\":\"青城后山\",\"address\":\"泰安古镇驿道街112号\",\"typecode\":\"110200\"},{\"id\":\"B0FFGQ1S50\",\"name\":\"灌县古城\",\"address\":\"灌口街道灌县古城西街32号\",\"typecode\":\"110000\"},{\"id\":\"B001C7XDSY\",\"name\":\"平乐古镇\",\"address\":\"兴新街139号\",\"typecode\":\"110202\"},{\"id\":\"B0FFKQ2N8L\",\"name\":\"邛州园\",\"address\":\"川西民俗文化大观园(平乐古镇西)\",\"typecode\":\"110200\"},{\"id\":\"B034000BXN\",\"name\":\"三岔湖景区\",\"address\":[],\"typecode\":\"110200\"},{\"id\":\"B0FFK2YUL9\",\"name\":\"龙泉山风景区\",\"address\":\"桃花故里旅游路东100米\",\"typecode\":\"110200\"},{\"id\":\"B001C802NZ\",\"name\":\"花舞人间景区\",\"address\":\"新蒲路梨花溪1号\",\"typecode\":\"110200\"},{\"id\":\"B001C0531A\",\"name\":\"老君山\",\"address\":\"永商镇\",\"typecode\":\"110200\"},{\"id\":\"B001C0547B\",\"name\":\"丹景山\",\"address\":\"丹景山街道丹景村\",\"typecode\":\"110200\"},{\"id\":\"B0FFIS0UIR\",\"name\":\"天府芙蓉园\",\"address\":\"簇马路一段69号\",\"typecode\":\"110200\"}]}
内容比较靠谱,nice~
5.MCP-Server开发
前面的章节我们体验到了第三方的MCP Server使用,接下来看一下如何开发一个自己的MCP Server。
在JAVA项目中开发MCP服务一般有两种方式:
- 基于MCP官方提供的MCP-SDK实现
- 基于Spring AI框架实现(对MCP-SDK进行了封装)
追求简单快捷,推荐使用SpringAI方式开发MCP Server,在本文中也是使用的Spring AI框架。
5.1 mcp-server开发(STDIO接口)
MCP-SERVER服务有两种交互方式,分别为:
- STDIO(Standard Input/Output)
- SSE(Server-Sent Events)
这里以开发一个天气预报的MCP服务为例,天气预报数据来源使用Open-meteo平台的接口
先看下接口为STDIO类型的MCP Server,开发具体的过程大致如下:
涉及到的pom依赖:
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-mcp-server</artifactId> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> </dependency>
定义方法并实现:
@Servicepublic class WeatherService {private static final Logger logger = org.slf4j.LoggerFactory.getLogger(WeatherService.class);private final RestClient restClient;public WeatherService() {this.restClient = RestClient.create();}/** * The response format from the Open-Meteo API */public record WeatherResponse(Current current) {public record Current(LocalDateTime time, int interval, double temperature_2m) {}}@Tool(description = \"Get the temperature (in celsius) for a specific location\")public String getTemperature(@ToolParam(description = \"The location latitude\") double latitude,@ToolParam(description = \"The location longitude\") double longitude,ToolContext toolContext) {WeatherResponse weatherResponse = restClient.get().uri(\"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m\",latitude, longitude).retrieve().body(WeatherResponse.class);return responseWithPoems;}}
注意上面的 @Tool 和 @ToolParam 注解,它们定义了这个mcp服务一个tool的行为和参数,这是一个MCP-Tool的关键。
最后,再将上面的service注册到Spring容器中,这便完成了MCP Tool到MCP服务的注册:
@Beanpublic ToolCallbackProvider weatherTools(WeatherService weatherService) {return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();}
在打包完成后,再使用MCP Inspector进行调试,传输类型选STDIO,Command中输入:java
,Arguments中输入:-jar E:\\\\tmp\\\\github\\\\spring-ai-examples\\\\model-context-protocol\\\\weather\\\\starter-stdio-server\\\\target\\\\mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar
详细路径根据生成的jar包目录调整,运行截图如下:
输入经纬度后成功返回对应的坐标的温度信息,Tools栏也可以查看此MCP服务对应的tool。
5.2 mcp-server开发(SSE接口)
SpringAI中提供了两个starter可以让我们开发SSE类型的MCP接口:
- 传统的webmvc
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webmvc</artifactId></dependency>
- 异步IO请求的webflux
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-mcp-server-webflux</artifactId></dependency>
将上面STDIO类型的服务改造成SSE类型的接口,仅替换掉依赖就可以。详细源码参考:mcp-weather-webmvc-server
在MCP Inspector中调试SSE的MCP服务时,传输类型选择SSE并填入URL即可。
以MCP服务在本机运行为例,填入:
http://127.0.0.1:8080/sse
运行截图: