
Spring AI 1.1.7 接入 MCP:Filesystem Server 最小 Demo
做 Tool Calling Demo 时,我们通常会把工具直接写在 Java 应用里:
java
@Tool(description = "查询订单")
public String queryOrder(String orderId) {
return orderService.query(orderId);
}
这种写法很适合接入自己项目里的业务方法。
但如果工具来自外部系统,比如文件系统、GitHub、数据库、浏览器、运维平台,每个 AI 应用都重复写一套适配代码,就会很累。
MCP 要解决的就是这个问题:
外部工具怎么用统一协议接进 AI 应用。
它不是替代 Tool Calling,而是和 Tool Calling 配合:
text
Tool Calling:模型怎么请求调用工具
MCP:外部工具怎么标准化接进来
这次只跑一条最小链路:
Spring AI 作为 MCP Client,连接本地 Filesystem MCP Server,让模型读取一个测试文件。
先把这条链路跑通,再去接 GitHub、数据库、浏览器这些 MCP Server,就不会乱。
注意:这篇只演示本地测试目录。生产环境不要随便把真实目录暴露给模型。
一、先把范围缩小
Spring AI 里和 MCP 相关的写法不止一种:
text
Spring AI 做 MCP Client
Spring AI 做 MCP Server
stdio 连接本地 MCP Server
SSE / Streamable HTTP 连接远程 MCP Server
入门时不要全塞在一起。我们先选最容易跑通的一条:
Spring AI 做 MCP Client,通过 stdio 连接本地 Filesystem MCP Server。
整体关系是这样:
text
Spring AI 应用
→ 启动并连接 Filesystem MCP Server
→ 获取 Server 暴露的文件工具
→ 把工具交给模型
→ 模型通过 Tool Calling 请求调用
→ MCP Server 真正读取文件
简单说:
text
MCP Client:你的 Spring AI 应用
MCP Server:外部工具服务
这篇用现成的 Filesystem Server,不从零写 Server。先把接入跑通。
二、准备一个测试目录
先准备几个前提:
- 你已经有一个能正常调用
ChatClient的 Spring Boot 项目; - 项目使用 Spring AI 1.1.7;
- 本机已经安装 Node.js 和
npx; - 当前模型支持 Tool Calling。
这篇示例使用 DeepSeek,模型用 deepseek-v4-flash。如果你走的是第三方兼容网关,要确认网关会不会完整透传 tools 参数和 tool call 响应。
先建一个测试目录:
bash
mkdir -p /tmp/spring-ai-mcp-demo
echo "Hello from Spring AI MCP Demo" > /tmp/spring-ai-mcp-demo/test.txt
先手动验证 Filesystem MCP Server 能不能启动:
bash
npx -y @modelcontextprotocol/server-filesystem /tmp/spring-ai-mcp-demo
如果看到类似输出,说明本地 Server 能跑:
text
Secure MCP Filesystem Server running on stdio
验证完可以停掉。真正接入 Spring AI 时,Spring AI 会根据配置自动启动它。
三、添加依赖
pom.xml 至少需要这几个依赖:
xml
<properties>
<java.version>17</java.version>
<spring-ai.version>1.1.7</spring-ai.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>${spring-ai.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
</dependencies>
spring-ai-starter-mcp-client 负责把 Spring AI 应用变成 MCP Client。这篇连接的是本地 stdio Server。
如果以后要接远程 MCP Server,再考虑 spring-ai-starter-mcp-client-webflux。
四、配置 DeepSeek 和 MCP
application.yaml 这样写:
yaml
spring:
application:
name: spring-ai-mcp-demo
ai:
model:
chat: deepseek
deepseek:
api-key: ${DEEPSEEK_API_KEY}
chat:
options:
model: deepseek-v4-flash
temperature: 0.2
mcp:
client:
type: SYNC
stdio:
connections:
filesystem:
command: npx
args:
- -y
- "@modelcontextprotocol/server-filesystem"
- /tmp/spring-ai-mcp-demo
启动前设置环境变量:
bash
export DEEPSEEK_API_KEY=你的 API Key
这段配置的关键点有三个:
- 使用 DeepSeek 作为聊天模型;
- 通过
npx启动 Filesystem MCP Server; - 只暴露
/tmp/spring-ai-mcp-demo测试目录。
如果用 IDEA 运行,在 Run/Debug Configurations 里给启动配置加环境变量:
text
DEEPSEEK_API_KEY=你的 API Key
Windows 上通常要用 cmd.exe /c 包一层:
yaml
command: cmd.exe
args:
- /c
- npx
- -y
- "@modelcontextprotocol/server-filesystem"
- C:\tmp\spring-ai-mcp-demo
macOS / Linux 按前面的写法即可。
五、写一个测试接口
新建 McpController:
java
package com.example.springaideepseekdemo.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class McpController {
private final ChatClient chatClient;
private final ToolCallbackProvider mcpTools;
public McpController(ChatClient.Builder builder, ToolCallbackProvider mcpTools) {
this.chatClient = builder
.defaultSystem("""
你是一个文件读取助手。
只能通过工具访问 /tmp/spring-ai-mcp-demo 目录。
当用户说 test.txt 或测试目录时,都指 /tmp/spring-ai-mcp-demo。
不要请求访问 /、用户主目录、项目源码目录或其他目录。
""")
.build();
this.mcpTools = mcpTools;
}
@GetMapping("/ask")
public String ask(@RequestParam String question) {
return chatClient.prompt()
.user(question)
.toolCallbacks(mcpTools)
.call()
.content();
}
}
这里有个容易写错的点:不要想当然写成 McpClient.getTools("filesystem")。
在 Spring AI 1.1.7 里,MCP Client Starter 会把 MCP Server 暴露的工具转换成 ToolCallbackProvider。
ChatClient 可以直接这样接:
java
.toolCallbacks(mcpTools)
如果项目里还有其他 ToolCallbackProvider Bean,构造方法注入时可能需要用 @Qualifier 指定。这个最小 Demo 只有一个 MCP Provider,可以直接注入。
六、启动测试
启动应用:
bash
./mvnw spring-boot:run
读取 test.txt:
bash
curl --get "http://localhost:8080/ask" \
--data-urlencode "question=帮我读取 /tmp/spring-ai-mcp-demo/test.txt 的内容"
如果模型选择了文件读取工具,最终会返回类似:
text
test.txt 的内容是:Hello from Spring AI MCP Demo
也可以测试列目录:
bash
curl --get "http://localhost:8080/ask" \
--data-urlencode "question=列出 /tmp/spring-ai-mcp-demo 目录下有哪些文件"
如果日志里出现:
text
Access denied - path outside allowed directories
不用慌。
这说明模型请求了白名单之外的路径,比如 /。Filesystem MCP Server 拒绝了这次访问,这是安全拦截,不是 MCP 没接上。
如果模型一直普通回答,没有请求工具,优先检查:
text
模型是否支持 Tool Calling
DEEPSEEK_API_KEY 是否正确
中间网关是否透传 tools 参数
先跑编译:
bash
./mvnw clean compile -DskipTests
然后启动应用,请求 /ask,能读到:
text
Hello from Spring AI MCP Demo
七、背后发生了什么
业务代码看起来只有这一段:
java
chatClient.prompt()
.user(question)
.toolCallbacks(mcpTools)
.call()
.content();
背后其实分成两条链路。
应用启动时:
text
Spring AI 读取 application.yaml
→ 启动 Filesystem MCP Server
→ 建立 stdio 连接
→ 获取 Server 的工具列表
→ 转成 Spring AI 的 ToolCallback
用户提问时:
text
用户提问
→ 模型看到可用工具
→ 模型判断要调用文件工具
→ Spring AI 执行 ToolCallback
→ ToolCallback 通过 MCP 调用 Filesystem Server
→ Server 读取文件并返回结果
→ 模型基于工具结果组织回答
所以 MCP 和 Tool Calling 不是两套互斥方案:
text
MCP 负责把外部工具接进来
Tool Calling 负责让模型请求调用这些工具
八、什么时候用 MCP
如果只是当前项目里的几个业务方法,直接写 @Tool 更轻:
text
查订单
查物流
查库存
这些能力本来就在当前应用里,没必要为了 MCP 多绕一层。
MCP 更适合:
text
工具要被多个 AI 应用复用
工具需要独立部署和维护
要接入现成的外部 MCP Server
工具很多,不想每个应用都重复适配
可以这样记:
text
@Tool:把当前应用里的方法暴露给模型
MCP:把外部工具服务标准化接入应用
Spring AI 不需要知道 Filesystem Server 内部怎么读文件,只要按 MCP 协议连接它、拿到工具、执行工具调用就行。
九、别忽略安全边界
MCP 接入不难,但安全边界一定要提前想清楚。Filesystem Server 会真正读写文件。
入门 Demo 只暴露:
text
/tmp/spring-ai-mcp-demo
不要暴露:
text
/
/Users/你的用户名
项目源码目录
.ssh
配置和密钥目录
工具描述不是权限控制。真正的目录边界、权限校验、危险操作拦截,都要在应用侧或 MCP Server 侧做。
入门阶段建议只读文件、列目录。
如果要开放写入、创建目录、移动文件,至少要加:
text
用户权限校验
目录白名单
操作日志
二次确认
限流和超时
还有一个常见误区:MCP Server 能连上,不代表模型一定会调用工具。
模型会不会调用,还取决于模型能力、工具描述、用户问题,以及模型服务是否支持 Tool Calling。
排查时分两步看:
text
先确认 MCP Server 能启动
再确认模型真的返回 tool call
这两个问题不要混在一起。
写在最后
这篇文章跑通的是一条最小链路:
text
Filesystem MCP Server 暴露工具
→ Spring AI MCP Client 连接 Server
→ MCP 工具转成 ToolCallbackProvider
→ ChatClient 把工具交给模型
→ 模型通过 Tool Calling 请求调用
→ Spring AI 通过 MCP 执行工具
如果说 @Tool 是把工具写在应用里,那 MCP 就是把工具从应用里拆出去,再用协议接回来。
它的价值不在于"更高级",而在于:
- 工具可以独立维护;
- 多个 AI 应用可以复用;
- 外部工具生态可以标准化接入。
但 MCP 只负责连接,不负责替你做权限、业务规则和风险兜底。
先用本地 Filesystem Server 跑通,再考虑 GitHub、数据库、浏览器或公司内部系统。
Agent 能力越多,安全边界越要清楚。
后续会继续更新 Spring AI、RAG、Memory、Tool Calling、MCP 等实战内容。