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
这个路径有两个问题:
- 它是 Linux 风格路径;
- 它不在文件 MCP 允许访问的目录中。
最终结果就是:文件没有被真实创建,质量监督器不断判定任务未完成,系统一直重试,直到达到最大执行轮数。
三、核心误区:把三个层次混为一谈
这次问题最关键的地方,不是"没有 MCP",而是把以下三个层次混淆了:
- Agent 是否具备文件工具能力;
- 模型这一轮是否选择调用文件工具;
- 业务上是否必须真实生成文件。
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 没启动",而是:
- Tool 同时配置在 Model 和 Client 层,职责不清晰;
- 最终有效工具集合不透明;
- 文件写入依赖模型自动选择工具;
- 模型使用了错误的文件路径;
- 系统没有验证
write_file和真实文件; - 监督器只能发现问题,不能替执行器完成文件写入。
所以,正确的处理方式应该是:
将 Tool 从 Model 层移到 Client / Agent Role 层统一管理,并把文件保存这类强制业务动作交给 Java Execution Runtime 主动调用 MCP,最后通过真实文件验证决定任务是否完成。
八、补充说明
后续日志里出现的 AIsearch HTTP 429 / QUOTA_USER_DAILY_FREE 是另一个独立问题,表示搜索服务的每日免费额度耗尽,和文件 MCP 是否能写入没有直接关系。
最后一句话总结
工具注册成功,只代表能力"存在";文件真实落盘,才代表任务"完成"。
如果你也在做 Spring AI、MCP 或多 Agent 编排,建议一定把"模型决策"和"副作用执行"分开,否则很容易踩到同样的坑。