引子
在上一篇文章中,我们通过集成 SearXNG,成功让大模型"睁眼看世界",具备了获取互联网实时信息的能力。然而,无论是 RAG(检索增强生成)还是联网搜索,本质上都是让 AI "读" 更多的书,获取更多的信息。但一个真正的智能助手,不仅要能"读",还要能 "写" 和 "做"。
试想这样一个场景:你希望 AI 帮你整理今天的股市数据,并生成一份 Excel 报表保存到桌面;或者你希望 AI 帮你给客户发送一封会议邀请邮件。在目前的架构下,大模型只能告诉你"邮件内容写好了,请你复制粘贴去发送",它就像一个被困在罐子里的"超级大脑",虽然博学,却无法触碰现实世界。

为了打破这个次元壁,我们需要引入 MCP(Model Context Protocol,模型上下文协议) 。关于它的概念不多赘述概念,网上相关的文章已经很多了,有需要了解请看MCP中文文档:docs.mcpcn.org/introductio...
本文将分为两个部分实战 MCP:
- 作为客户端(Client):调用现成的 MCP 服务。
- 作为服务端(Server):开发我们自己的 MCP 服务。
调用 MCP 服务:操作本地文件
Spring AI 提供了 spring-ai-mcp-client,允许我们的应用连接到任何遵循 MCP 标准的服务器。这里我们以官方提供的 文件系统 MCP 服务器 为例,让 AI 具备在本地创建和读取文件的能力。
前提条件:由于文件系统 MCP 服务是基于 Node.js 的,请确保你的本地环境已安装 Node.js (v18+)。
1.添加依赖
在 pom.xml 中引入 MCP Client 相关的依赖:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp</artifactId>
</dependency>
2. 配置 MCP Server 连接
MCP 支持两种连接模式:stdio(标准输入输出,适用于本地进程)和 sse(Server-Sent Events,适用于网络服务)。对于本地文件系统服务,我们使用 stdio 模式。
在 resources 目录下新建 mcp-server.json 文件,定义如何启动文件系统服务:
json
{
"mcpServers": {
"filesystem": {
"command": "D:\\devolop\\node\\npx.cmd",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"D:\\devolop"
]
}
}
}
配置解析:
command: 指向你的npx可执行文件路径(Windows 下通常是npx.cmd)。args:
-y: 自动确认安装。@modelcontextprotocol/server-filesystem: 官方的文件系统 MCP 服务包。D:\\devolop: 这是允许 AI 访问的根目录。为了安全起见,AI 只能操作这个目录及其子目录下的文件。
接着,在 application.yml 中启用 MCP Client 并加载上述配置:
yaml
spring:
ai:
mcp:
client:
enabled: true
name: spring-ai-mcp-client
type: ASYNC # 推荐使用异步非阻塞模式
stdio:
servers-configuration: classpath:mcp-server.json
3.代码改造
我们需要在 ChatService 初始化时,将 MCP Client 发现的工具注册到 ChatClient 中。
java
public ChatServiceImpl(ChatClient.Builder chatClientBuilder, ToolCallbackProvider tools) {
// tools 会自动注入所有配置好的 MCP 工具
this.chatClient = chatClientBuilder
.defaultToolCallbacks(tools)
.build();
}
4.效果测试
启动项目,观察控制台日志,可以看到MCP工具已经连接成功。

现在,我们在对话框中输入这些内容:

打开我们配置的 D:\devolop 目录,可以看到文件已经被成功创建。

开发 MCP 服务:邮件与时间工具
除了调用现有的工具,更常见的场景是将我们自己的业务逻辑(如查询内部系统、发送通知)封装成 MCP 工具供大模型调用。
为了演示,我们创建一个新的模块 mcp-server,实现"获取当前时间"和"发送邮件"两个功能。

在 mcp-server 模块的 pom.xml 中添加必要的依赖。除了 WebFlux(MCP Server 常用底层),我们还需要 spring-boot-starter-mail 来发送邮件,以及 flexmark 用于将 AI 生成的 Markdown 内容转换为邮件友好的 HTML。
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.cc</groupId>
<artifactId>SpringAI-MCP-RAG-Dev</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>mcp-server</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- MCP Server 依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>
<!-- 邮件发送依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Markdown 转 HTML 工具 -->
<dependency>
<groupId>com.vladsch.flexmark</groupId>
<artifactId>flexmark-all</artifactId>
<version>0.64.8</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
1.添加配置
在 application.yml 中配置 OpenAI Key、Redis(用于向量存储或缓存)以及 SMTP 邮件服务信息。
yaml
spring:
application:
name: spring-ai-mcp-server
data:
redis:
host: 127.0.0.1
port: 9379
password: 123456
ai:
mcp:
server:
name: spring-ai-mcp-server-sse
version: 1.0.0
sse-endpoint: /sse
type: async
mail:
host: smtp.163.com
port: 465
username: 123@163.com
password: 123456 # 注意:这里通常是邮箱授权码,不是登录密码
protocol: smtp
default-encoding: UTF-8
properties:
mail:
smtp:
socketFactory:
port: 465
class: javax.net.ssl.SSLSocketFactory
ssl:
enable: true
logging:
level:
root: info
server:
port: 6080
启动项目,访问 http://localhost:6080/sse,如果能正常启动,说明配置无误。

2. 开发时间查询工具
大模型本身对"现在是几点"没有概念,我们需要提供一个工具。
java
package com.cc.mcp.tool;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Component
@Slf4j
public class DateTool {
// @Tool 注解将方法暴露为 MCP 工具
// description 非常重要,大模型根据它来判断何时调用此工具
@Tool(description = "获取当前时间")
public String getCurrentTime() {
log.info("=================调用MCP工具:获取当前时间=================");
return String.format("当前的时间是 %s", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
}
3. 开发邮件发送工具
这是一个稍微复杂一点的工具,需要定义参数结构。
java
package com.cc.mcp.tool;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.data.MutableDataSet;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class EmailTool {
private final JavaMailSender mailSender;
private final String from;
@Autowired
private EmailTool(JavaMailSender mailSender, @Value("${spring.mail.username}") String from) {
this.mailSender = mailSender;
this.from = from;
}
// 定义请求参数类,大模型会自动填充这些字段
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public static class EmailRequest {
@ToolParam(description = "收件人邮箱地址")
private String email;
@ToolParam(description = "发送邮件的标题/主题")
private String subject;
@ToolParam(description = "发送邮件的消息/正文内容")
private String message;
@ToolParam(description = "发送邮件的内容类型,1为HTML格式,2为普通文本格式")
private Integer contentType;
}
@Tool(description = "给指定邮箱发送邮件信息。")
public String sendEmail(EmailRequest emailRequest) {
log.info("=================调用MCP工具:sendEmail=================");
log.info("请求详情: {}", emailRequest);
Integer contentType = emailRequest.getContentType();
try {
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage);
mimeMessageHelper.setFrom(from);
mimeMessageHelper.setTo(emailRequest.getEmail());
mimeMessageHelper.setSubject(emailRequest.getSubject());
// 智能处理:如果是 Markdown 格式,自动转 HTML
if (contentType != null && contentType == 1) {
mimeMessageHelper.setText(convertMarkdownToHtml(emailRequest.getMessage()), true);
} else if (contentType != null && contentType == 2) {
mimeMessageHelper.setText(emailRequest.getMessage(), true);
} else {
// 默认处理
mimeMessageHelper.setText(emailRequest.getMessage());
}
mailSender.send(mimeMessage);
return "邮件发送成功";
} catch (MessagingException e) {
log.error("发送邮件失败", e);
return "发送邮件失败: " + e.getMessage();
}
}
/**
* 将Markdown格式的字符串转换为HTML格式
*/
public static String convertMarkdownToHtml(String markdownStr) {
MutableDataSet dataSet = new MutableDataSet();
Parser parser = Parser.builder(dataSet).build();
HtmlRenderer htmlRenderer = HtmlRenderer.builder(dataSet).build();
return htmlRenderer.render(parser.parse(markdownStr));
}
}
4. 注册工具
最后,在启动类或配置类中,将我们编写的 Tool Bean 注册到 ToolCallbackProvider 中。
typescript
package com.cc.mcp;
import com.cc.mcp.tool.DateTool;
import com.cc.mcp.tool.EmailTool;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
/**
* 注册自定义 MCP 工具
* 这样 ChatClient 就能感知到这些工具的存在
*/
@Bean
public ToolCallbackProvider registerMCPTools(DateTool dateTool, EmailTool emailTool) {
return MethodToolCallbackProvider.builder()
.toolObjects(dateTool, emailTool)
.build();
}
}
5. 综合测试
启动项目,观察日志,确认工具已加载。

测试时间查询:

测试邮件发送:

增加记忆功能:让对话更连贯
在实际使用中,我们可能会分多轮对话来完成任务。但默认情况下,ChatClient 是无状态的,它记不住上一句说了什么:

为了解决这个问题,我们需要引入 Chat Memory 。在 ChatServiceImpl 中注入 ChatMemory,并将其配置到 ChatClient 中:
java
private final ChatClient chatClient;
// 注入 ChatMemory
public ChatServiceImpl(ChatClient.Builder chatClientBuilder,
ToolCallbackProvider tools,
ChatMemory chatMemory) {
this.chatClient = chatClientBuilder
// 添加记忆 Advisor
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.defaultToolCallbacks(tools)
.build();
}
配置完成后,模型就具备了上下文记忆能力,能够流畅地处理多轮意图确认。

小结
通过本篇文章,我们实现了 Spring AI 应用功能的重大飞跃:从单纯的"信息获取者"进化为了"任务执行者"。
- 利用 MCP Client,我们轻松集成了现有的文件系统服务。
- 利用 Spring AI Tool,我们开发了自定义的邮件和时间服务。
- 利用 Chat Memory,我们赋予了 AI 记忆,使其交互更加自然。
现在,我们的 AI 已经可以操作文件、发送邮件了。但在企业级应用中,最核心的数据往往存储在数据库中。如何让大模型安全、准确地查询和操作数据库?下一篇,我们将探讨基于 MCP 的大模型与数据库交互开发。