Spring AI 1.1.7 接入 MCP:Filesystem Server 最小 Demo

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 等实战内容。

相关推荐
程序员小富1 小时前
我开源了一个开发者专属的智能 JSON 工具,得到了媳妇高度认可
前端·vue.js·后端
Token炼金师1 小时前
大模型推理超参数原理详解
人工智能
Token炼金师1 小时前
大模型训练超参数:从Loss曲面到收敛策略的底层逻辑
人工智能
苍何1 小时前
深度测评 MiniMax M3,能打但不贵
后端
苍何1 小时前
爆款博主,已经没有秘密了。。。
后端
后端小肥肠1 小时前
Skill 囤了一堆却用不起来?我用 Codex 写了个整理神器
人工智能·agent
魏祖潇1 小时前
从"会聊天"到"能干活":用 OpenCode 给自己找个 AI 搭子
人工智能
dunky1 小时前
Spring 的三级缓存与循环依赖
后端·spring