Spring AI + MCP 文件工具未调用问题复盘:为什么初始化成功却没有写入文件?

Spring AI + MCP 文件工具未调用问题复盘:为什么初始化成功却没有写入文件?

在一次 AutoAgent 项目实践中,我遇到了一个很典型、也很容易误判的问题:文件 MCP 明明已经初始化成功,但执行过程中却始终没有真正写入文件。日志看起来一切正常,最后却发现目标文件根本不存在。

这篇文章记录这次排障过程,也顺便总结一下:为什么"工具已注册"不等于"工具已执行",以及如何把这类"依赖模型自动决策"的流程改造成更可靠的确定性执行链路。

一、运行环境

  • Java:17
  • Spring Boot:3.4.3
  • Spring AI:1.0.0
  • MCP Java SDK:0.10.0
  • 模型:gpt-4.1-mini
  • 文件 MCP:5003
  • MCP Server:secure-filesystem-server 0.2.0
  • 允许访问目录:C:\Users\24209\Desktop

Agent 角色划分:

  • 3101:任务分析器
  • 3102:精准执行器
  • 3103:质量监督器

二、问题现象

系统启动时,文件 MCP 的初始化日志是成功的:

text 复制代码
Tool Stdio MCP Initialized
成功注册 Bean: ai_client_tool_mcp_5003

但在实际执行过程中,精准执行器 3102 一直没有调用 write_file,反而多次输出类似信息:

text 复制代码
当前环境没有文件写入工具
无法真实创建并保存文件

更奇怪的是,模型输出的路径是:

text 复制代码
/workspace/小傅哥技术项目学习指南.md

这个路径有两个问题:

  1. 它是 Linux 风格路径;
  2. 它不在文件 MCP 允许访问的目录中。

最终结果就是:文件没有被真实创建,质量监督器不断判定任务未完成,系统一直重试,直到达到最大执行轮数。

三、核心误区:把三个层次混为一谈

这次问题最关键的地方,不是"没有 MCP",而是把以下三个层次混淆了:

  1. Agent 是否具备文件工具能力
  2. 模型这一轮是否选择调用文件工具
  3. 业务上是否必须真实生成文件

MCP 初始化成功,只能说明文件服务已经启动,并不代表:

  • 最终请求一定携带 write_file
  • 模型一定会主动调用 write_file
  • 文件一定会真实落盘。

换句话说:

工具可用 ≠ 工具必用 ≠ 文件必生成

如果业务结果要求"必须保存文件",那就不能把这件事完全交给模型自由发挥。

四、问题根因分析

1. Tool 同时配置在 Model 和 Client 两层

原来的设计思路类似于:

text 复制代码
Model 2001
  └─ MCP tools

Client 3102
  └─ MCP tools

问题在于,Spring AI 1.0.0 中,运行时 Tool 的优先级会影响默认 Tool 的行为。当 Tool 同时分布在 Model 和 Client 两层时,最终请求到底携带了哪些工具,会受到默认配置、运行时配置和构建方式的共同影响。

这会带来几个后果:

  • 工具来源不够清晰;
  • 不容易判断当前角色实际拥有哪些能力;
  • 多个 Client 共享同一个 Model 时,工具集可能互相污染;
  • 排查时只能看到 MCP 已初始化,但看不到角色级别的真实能力。

2. 模型是否调用工具,仍然是"自动决策"

当前提示词只要求:

text 复制代码
使用必要的工具

但这并不等于模型一定会调用工具。只要工具调用策略是自动模式,模型完全可能选择:

  • 直接生成 Markdown;
  • 口头说明"已经保存";
  • 输出一个建议路径;
  • 甚至错误地声称没有文件工具。

也就是说,即使工具存在,模型也可以不使用它。

3. 文件路径不在 MCP 允许范围内

文件 MCP 的允许目录是:

text 复制代码
C:\Users\24209\Desktop

但模型给出的路径却是:

text 复制代码
/workspace/小傅哥技术项目学习指南.md

这显然不符合实际文件系统约束。对于 Windows 环境下的 MCP 文件服务,正确路径应该类似:

text 复制代码
C:\Users\24209\Desktop\小傅哥技术项目学习指南.md

路径错了,即使模型真的尝试写,也可能因为权限或目录限制失败。

4. 缺少确定性的结果验证

原流程主要依赖模型的自然语言输出判断任务是否完成,而没有做这些验证:

  • write_file 是否实际执行;
  • MCP 是否返回成功;
  • 文件是否真实存在;
  • 路径是否在允许目录中;
  • 交付物是否真的落盘。

如果系统没有验证闭环,那么模型即使说"我已经保存好了",也只是文本层面的完成 ,不是真实世界的完成

5. 质量监督器无法替代执行器

质量监督器能发现问题,但不能替代执行器完成写文件动作。

于是就容易形成这种循环:

text 复制代码
执行器没有写文件
        ↓
监督器判定未完成
        ↓
执行器再次输出文字
        ↓
仍然没有写文件
        ↓
直到达到最大执行轮数

这类问题本质上不是"评审不够严格",而是"执行链路不具备确定性"。

五、解决方案

1. Model 层不再绑定业务 Tool

Model 应该只负责:

  • 模型名称;
  • API 调用;
  • temperature;
  • maxTokens;
  • 基础生成参数。

例如:

text 复制代码
Model 2001
  └─ gpt-4.1-mini

而不是在 Model 层挂载 MCP 这类业务工具。

2. Tool 统一绑定到 Client / Agent Role

应该由 Client 或 Agent Role 决定当前角色具备哪些能力:

text 复制代码
Client 3101 分析器
  └─ Search

Client 3102 执行器
  ├─ Search
  ├─ Filesystem
  └─ AMap

Client 3103 监督器
  └─ Filesystem Read/Verify

这样做的好处是:

  • 角色职责更清晰;
  • 工具归属更明确;
  • 排查更直观;
  • 不同角色不会互相污染工具集。

3. 启动时打印每个 Client 的真实工具列表

构建 Client 时,建议直接记录:

text 复制代码
客户端 3101 实际工具:[search]
客户端 3102 实际工具:[search, write_file, ...]
客户端 3103 实际工具:[read_file, get_file_info]

如果 3102 没有 write_file,那就应该在启动阶段或任务执行前直接报错,而不是等模型跑到一半才发现能力缺失。

4. 文件写入这种副作用必须由 Java 工作流主动控制

对于"保存文件"这种强制业务动作,不应该依赖模型自己选工具,而应该由 Java 工作流主动调用 MCP。

推荐流程如下:

text 复制代码
模型生成结构化文档内容
        ↓
Java 执行节点主动调用 MCP write_file
        ↓
MCP 返回写入结果
        ↓
Java 验证文件真实存在
        ↓
记录 artifactPath
        ↓
质量监督器验收

模型负责输出结构化内容,例如:

json 复制代码
{
  "fileName": "小傅哥技术项目学习指南.md",
  "content": "完整 Markdown 正文"
}

Java 再固定调用:

  • tool:write_file
  • path:C:\Users\24209\Desktop\小傅哥技术项目学习指南.md
  • content:模型生成的 Markdown 内容

这样才能保证"保存文件"一定发生。

5. 增加确定性的 artifact 验证

建议在文件写入后记录:

text 复制代码
artifactRequired = true
artifactVerified = true
artifactPath = C:\Users\24209\Desktop\小傅哥技术项目学习指南.md

最终完成条件也应该改成:

质量监督结果为 PASS,且必要文件已真实验证存在。

如果监督器返回 PASS,但文件不存在,那么任务仍然不能标记为完成。

6. 增加 BLOCKED 状态

流程状态不要只分成"完成"和"继续",还应支持:

  • PASS:任务完整完成;
  • OPTIMIZE:下一轮可以继续优化;
  • FAIL:执行结果错误;
  • BLOCKED:缺少工具、权限、额度等外部能力,继续重试没有意义。

如果连续多轮遇到同一个不可恢复问题,就应该进入 BLOCKED,而不是一直重试到最大轮数。

六、职责边界应该怎么划分

最终比较合理的职责分层是:

text 复制代码
Model
负责生成和推理
不负责业务工具权限

Client / Agent Role
负责角色工具、提示词、Memory、Advisor 和权限

Execution Runtime
负责真实工具调用、异常处理、重试和执行记录

Quality Supervisor
负责内容质量评估

Deterministic Validator
负责验证文件、路径和其他必要交付物是否真实存在

这套划分的核心思想是:

让模型负责"想什么",让 Java 负责"做什么",让验证器负责"是否真的做到了"。

七、最终结论

这次问题本身不是"文件 MCP 没启动",而是:

  1. Tool 同时配置在 Model 和 Client 层,职责不清晰;
  2. 最终有效工具集合不透明;
  3. 文件写入依赖模型自动选择工具;
  4. 模型使用了错误的文件路径;
  5. 系统没有验证 write_file 和真实文件;
  6. 监督器只能发现问题,不能替执行器完成文件写入。

所以,正确的处理方式应该是:

将 Tool 从 Model 层移到 Client / Agent Role 层统一管理,并把文件保存这类强制业务动作交给 Java Execution Runtime 主动调用 MCP,最后通过真实文件验证决定任务是否完成。

八、补充说明

后续日志里出现的 AIsearch HTTP 429 / QUOTA_USER_DAILY_FREE 是另一个独立问题,表示搜索服务的每日免费额度耗尽,和文件 MCP 是否能写入没有直接关系。


最后一句话总结

工具注册成功,只代表能力"存在";文件真实落盘,才代表任务"完成"。

如果你也在做 Spring AI、MCP 或多 Agent 编排,建议一定把"模型决策"和"副作用执行"分开,否则很容易踩到同样的坑。

相关推荐
Java小白笔记26 分钟前
Java中大数据实时归集与指标汇总方案
java·大数据·开发语言
随遇而安zx28 分钟前
【地基篇】---Java 8 JVM 知识大纲
java·开发语言·jvm
雾隐隐o29 分钟前
Docker 镜像归档与容器管理
java·docker·eureka
Meta3934 分钟前
Java八股文之Spring Boot 中解决 MySQL 和 Elasticsearch (ES) 的数据一致性问题
java·spring boot·mysql
夜猫逐梦36 分钟前
【MCP】ida-pro-mcp 入门实战:安装、启动并用 MCP Inspector 调通四层连通
ida·mcp·ida-pro-mcp
liangbo744 分钟前
01-JVM 内存模型全景
java·jvm
十年Java程序媛44 分钟前
@Async 深度避坑:事务丢失、MDC 上下文丢失、异常静默吞掉完整修复
java·spring boot
橘子编程1 小时前
Java邮件发送全攻略:从入门到实战
java·开发语言·spring boot·spring·spring cloud·maven
阿哉1 小时前
一次 Lombok 静默失效排查:JDK 23 注解处理默认策略变更引发的满屏「找不到符号」
java