Spring Boot 接入 MCP 实战:用 Spring AI 调用本地工具的最小闭环

Spring Boot 接入 MCP 实战:用 Spring AI 调用本地工具的最小闭环

摘要:本文讲清楚 Spring Boot 项目如何通过 Spring AI 接入 MCP,把外部工具变成大模型可调用的 Tool。内容基于 Spring AI 1.1.7 官方 MCP Client Boot Starter 文档,重点放在 STDIO 本地工具接入、配置结构、工具发现、ChatClient 调用链路和常见排查点。适合 Java 后端、Spring Boot 开发者和正在把 Agent 能力落到工程系统里的同学。

1. MCP 解决的不是"模型更聪明",而是"工具接入更标准"

很多 Java 后端第一次接触 Agent 工程时,容易把注意力放在 Prompt 上。但真正落到生产系统,最麻烦的通常不是让模型回答一句话,而是让模型安全、稳定、可维护地调用外部能力。

比如:

  • 查询文件系统;
  • 查数据库;
  • 调内部 HTTP API;
  • 查询知识库;
  • 调用运维脚本;
  • 访问某个业务系统的只读接口。

如果每个工具都用一套自定义协议,Agent 应用很快会变成一堆临时适配代码。MCP,也就是 Model Context Protocol,要解决的正是这类问题:用统一协议把外部工具、资源和 Prompt 暴露给 AI 应用。

从 Spring AI 的视角看,MCP 可以理解成三层:

层级 职责 Java 后端需要关心什么
MCP Server 暴露工具、资源、Prompt 这个服务提供哪些工具,入参出参是什么
MCP Client 连接 MCP Server,发现并调用工具 Spring Boot 如何配置连接和生命周期
Transport JSON-RPC 消息传输 用 STDIO、SSE 还是 Streamable HTTP

本文的目标不是讲完 MCP 全部规范,而是跑通一个最小工程闭环:

复制代码
Spring Boot 应用
  -> Spring AI MCP Client
  -> 本地 MCP Server
  -> 自动发现 Tools
  -> ChatClient 把 Tools 暴露给模型调用

验证状态说明:本文依赖、配置项和 API 结构来自 Spring AI 1.1.7 官方文档;示例工程代码用于说明最小接入方式,具体版本兼容性以你项目实际 Spring Boot、Spring AI BOM 和模型供应商为准。

2. 先选对接入方式:STDIO、SSE 还是 Streamable HTTP

Spring AI MCP Client Boot Starter 支持多种传输方式:

  • STDIO:本地进程通信,适合本地工具、开发环境、CLI 类 MCP Server;
  • SSE:基于 HTTP Server-Sent Events,适合远程服务端推送;
  • Streamable HTTP:适合更标准的 HTTP 化 MCP 服务;
  • Stateless Streamable HTTP:适合无状态 HTTP 场景。

本文先用 STDIO,因为它最适合 Java 后端做本地最小验证:不用先部署远程 MCP Server,只要本机能通过命令启动一个 MCP Server 即可。

典型场景是文件系统 MCP Server:

复制代码
Spring Boot
  -> 启动/连接 npx @modelcontextprotocol/server-filesystem
  -> 暴露读取指定目录的工具
  -> 模型在对话中按需调用工具

生产环境不建议一上来就把文件系统、数据库、内部接口全部开放给模型。正确顺序应该是:

  1. 先在本地最小目录验证工具发现和调用链路;
  2. 再做工具白名单和权限边界;
  3. 最后再考虑远程 MCP Server、鉴权、审计和限流。

3. 环境版本和依赖

本文示例按下面环境理解:

项目 版本/说明
JDK 17 或 21
Spring Boot 3.x
Spring AI 1.1.7
MCP Client Starter spring-ai-starter-mcp-client
MCP Server 示例 @modelcontextprotocol/server-filesystem
传输方式 STDIO

Maven 依赖建议通过 Spring AI BOM 管理版本。示例:

复制代码
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <version>1.1.7</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-mcp-client</artifactId>
    </dependency>

    <!-- 任选一个 Chat Model Starter。这里以 OpenAI 为例,实际项目可替换成你自己的模型供应商。 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-starter-model-openai</artifactId>
    </dependency>
</dependencies>

如果你的项目使用的是 Spring AI 1.0.x 或 2.0.0-M 系列,依赖名、配置项和行为可能有差异,不建议直接复制本文配置到不同大版本。

4. 配置 MCP Client:连接本地文件系统 MCP Server

Spring AI 的 MCP Client 公共配置前缀是:

复制代码
spring.ai.mcp.client

STDIO 连接配置前缀是:

复制代码
spring.ai.mcp.client.stdio.connections

一个最小 application.yml 可以这样写:

复制代码
spring:
  ai:
    openai:
      api-key: ${OPENAI_API_KEY}
      chat:
        options:
          model: gpt-4o-mini
    mcp:
      client:
        enabled: true
        name: spring-ai-mcp-demo
        version: 1.0.0
        initialized: true
        request-timeout: 30s
        type: SYNC
        toolcallback:
          enabled: true
        stdio:
          connections:
            filesystem:
              command: npx
              args:
                - -y
                - @modelcontextprotocol/server-filesystem
                - ./target/mcp-demo-root

这段配置做了几件事:

  • 启用 MCP Client;
  • 使用同步客户端 SYNC
  • 通过 STDIO 启动一个名为 filesystem 的 MCP Server;
  • 命令是 npx -y @modelcontextprotocol/server-filesystem ./target/mcp-demo-root
  • 启用 toolcallback,让 MCP 工具进入 Spring AI 的工具调用体系。

启动前先准备一个测试目录:

复制代码
mkdir -p target/mcp-demo-root
printf "hello from mcp filesystem" > target/mcp-demo-root/hello.txt

验证状态:上面的 MCP 配置结构来自 Spring AI 1.1.7 官方 MCP Client Boot Starter 文档;@modelcontextprotocol/server-filesystem 为 MCP 官方生态中常见示例 Server,实际使用时以你本机 Node/npm 可用性为准。

5. 在 ChatClient 中接入 MCP Tools

Spring AI MCP Client Starter 在 toolcallback.enabled=true 时,会把已发现的 MCP Tools 提供为 ToolCallbackProvider。在同步客户端场景下,可以注入 SyncMcpToolCallbackProvider

一个最小 Controller 示例:

java 复制代码
`package com.example.mcpdemo;

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class McpChatController {

    private final ChatClient chatClient;
    private final SyncMcpToolCallbackProvider toolCallbackProvider;

    public McpChatController(ChatClient.Builder chatClientBuilder,
                             SyncMcpToolCallbackProvider toolCallbackProvider) {
        this.chatClient = chatClientBuilder.build();
        this.toolCallbackProvider = toolCallbackProvider;
    }

    @GetMapping("/ai/ask-file")
    public String askFile(@RequestParam(defaultValue = "读取 hello.txt 的内容,并用一句话概括") String question) {
        return chatClient.prompt()
                .system("你是一个只通过 MCP 工具读取授权目录文件的 Java 助手。不要猜测文件内容。")
                .user(question)
                .tools(toolCallbackProvider.getToolCallbacks())
                .call()
                .content();
    }
}
`

调用示例:

复制代码
curl "http://localhost:8080/ai/ask-file?question=读取hello.txt并告诉我里面写了什么"

预期链路是:

复制代码
HTTP 请求进入 Controller
  -> ChatClient 收到用户问题
  -> 模型判断需要读取文件
  -> 调用 MCP filesystem 暴露的工具
  -> MCP Server 在授权目录中读取 hello.txt
  -> 结果返回给模型
  -> 模型组织最终回答

这里有一个关键点:工具不是手动塞进 Prompt 的字符串,而是通过 Spring AI Tool Calling 机制接入模型。这样做的好处是工具有结构化入参、可发现、可过滤,也更容易做权限和审计。

6. 如果想先确认工具是否被发现

在排查 MCP 接入问题时,最好先确认工具列表,而不是直接问模型"能不能读文件"。你可以加一个调试接口:

java 复制代码
`package com.example.mcpdemo;

import java.util.Arrays;
import java.util.List;

import org.springframework.ai.mcp.SyncMcpToolCallbackProvider;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class McpToolController {

    private final SyncMcpToolCallbackProvider toolCallbackProvider;

    public McpToolController(SyncMcpToolCallbackProvider toolCallbackProvider) {
        this.toolCallbackProvider = toolCallbackProvider;
    }

    @GetMapping("/ai/mcp/tools")
    public List<String> tools() {
        return Arrays.stream(toolCallbackProvider.getToolCallbacks())
                .map(tool -> tool.getToolDefinition().name())
                .toList();
    }
}
`

调用:

复制代码
curl http://localhost:8080/ai/mcp/tools

如果这里返回空数组,问题大概率不在 Prompt,而在 MCP Server 启动、STDIO 命令、连接初始化或工具过滤上。

7. 工程调用链路拆解

把上面的代码抽象一下,Spring Boot 接入 MCP 的链路可以拆成 6 步:

这里最容易踩坑的是第 3、4、5 步。

步骤 常见问题 排查方式
启动 MCP Server npx 不存在、Node 版本异常、命令路径错 单独在终端执行同样命令
初始化 MCP Client 请求超时、进程无响应 调大 request-timeout,查看启动日志
工具发现 工具列表为空 暴露 /ai/mcp/tools 调试接口
模型调用工具 模型直接回答,不调用工具 换支持工具调用的模型,增强 system 约束
文件读取 访问目录不在授权范围 检查 filesystem server 启动参数

8. Windows 下 STDIO 命令要特别处理

如果你在 Windows 上跑,npxnpmnode 这类命令通常是 .cmd 批处理文件。Java 的 ProcessBuilder 不能像执行原生 .exe 一样直接执行这些批处理命令,通常需要通过 cmd.exe /c 包一层。

Windows 配置示例:

复制代码
spring:
  ai:
    mcp:
      client:
        stdio:
          connections:
            filesystem:
              command: cmd.exe
              args:
                - /c
                - npx
                - -y
                - @modelcontextprotocol/server-filesystem
                - target

Linux/macOS 则可以直接写:

复制代码
spring:
  ai:
    mcp:
      client:
        stdio:
          connections:
            filesystem:
              command: npx
              args:
                - -y
                - @modelcontextprotocol/server-filesystem
                - target

如果你的应用要跨平台运行,不建议把这一段完全写死在同一个配置里。更稳妥的方式是按环境拆 profile,或者用 Java 代码根据 os.name 做条件创建。

9. 多 MCP Server 时要处理工具命名冲突

真实项目不会只接一个 MCP Server。你可能同时接:

  • filesystem;
  • database-readonly;
  • internal-api;
  • git;
  • browser;
  • knowledge-base。

多个 Server 很可能提供同名工具,例如都叫 searchreadlist。Spring AI MCP Client Starter 提供了工具名前缀生成能力,默认会尽量避免冲突,比如给重复工具名增加前缀。

如果你希望工具名更可控,可以实现自定义 McpToolNamePrefixGenerator。示意代码:

java 复制代码
`package com.example.mcpdemo;

import org.springframework.ai.mcp.McpConnectionInfo;
import org.springframework.ai.mcp.McpToolNamePrefixGenerator;
import org.springframework.ai.tool.definition.ToolDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class McpToolNameConfig {

    @Bean
    public McpToolNamePrefixGenerator mcpToolNamePrefixGenerator() {
        return new McpToolNamePrefixGenerator() {
            @Override
            public String prefixedToolName(McpConnectionInfo connectionInfo, ToolDefinition tool) {
                String clientName = connectionInfo.clientInfo().name();
                return clientName.replaceAll("[^a-zA-Z0-9]", "_") + "_" + tool.name();
            }
        };
    }
}
`

注意:这段代码用于解释扩展点,具体接口泛型和导入路径请以你使用的 Spring AI 版本源码为准。不同小版本可能存在签名变化。

生产建议是:不要为了 Prompt 好看而关闭工具名前缀。工具名冲突造成的误调用,比名字长一点更难排查。

10. 工具过滤:不要把所有工具都交给模型

Spring AI MCP Client 支持 McpToolFilter,可以按连接信息和工具属性过滤工具。

为什么需要过滤?因为生产系统里不是所有工具都应该暴露给模型。例如:

  • 写文件工具;
  • 删除数据工具;
  • 触发发布工具;
  • 调支付、发短信、改权限这类有副作用的工具。

一个保守策略是:第一阶段只开放只读工具。

示意代码:

java 复制代码
`package com.example.mcpdemo;

import org.springframework.ai.mcp.McpConnectionInfo;
import org.springframework.ai.mcp.McpToolFilter;
import org.springframework.ai.mcp.protocol.McpSchema;
import org.springframework.stereotype.Component;

@Component
public class ReadOnlyMcpToolFilter implements McpToolFilter {

    @Override
    public boolean test(McpConnectionInfo connectionInfo, McpSchema.Tool tool) {
        String name = tool.name().toLowerCase();
        return name.contains("read")
                || name.contains("list")
                || name.contains("search")
                || name.contains("get");
    }
}
`

这类过滤不是安全的全部,只是第一道门。真正生产化还要考虑:

  • MCP Server 自身权限;
  • 目录白名单;
  • 网络出口控制;
  • API 鉴权;
  • 调用审计;
  • 用户确认机制;
  • 敏感参数脱敏。

11. 常见问题和排查清单

11.1 应用启动时报 npx 找不到

先在终端确认:

复制代码
which npx
npx -y @modelcontextprotocol/server-filesystem ./target/mcp-demo-root

如果终端能执行,但 Spring Boot 里不能执行,通常是应用启动进程的 PATH 和你的交互式 Shell 不一致。可以临时改成绝对路径,或者在启动脚本里显式配置 PATH。

11.2 工具列表为空

按顺序查:

  1. spring.ai.mcp.client.enabled 是否为 true
  2. spring.ai.mcp.client.initialized 是否为 true
  3. spring.ai.mcp.client.toolcallback.enabled 是否为 true
  4. STDIO 命令能否单独运行;
  5. MCP Server 是否真的暴露 tools;
  6. 是否被 McpToolFilter 过滤掉了。

11.3 模型不调用工具,直接编答案

这通常有三类原因:

  • 当前模型或供应商 Tool Calling 支持不完整;
  • Prompt 没有明确要求"不要猜测,必须调用工具";
  • 工具描述不够清晰,模型不知道该用哪个工具。

可以先用更强约束的 system prompt:

复制代码
当用户询问文件内容时,你必须调用可用的 MCP 文件读取工具。
如果工具不可用,直接说明无法读取,不要猜测。

11.4 读取不到文件

文件系统 MCP Server 通常只允许访问启动参数中指定的目录。比如你配置的是:

复制代码
args:
  - ./target/mcp-demo-root

那就不要期待它能读取项目根目录、用户目录或系统目录。这个限制是好事,生产环境一定要保留边界。

11.5 超时或进程挂住

可以先调大请求超时:

复制代码
spring:
  ai:
    mcp:
      client:
        request-timeout: 60s

但不要把超时无限放大。更重要的是查看 MCP Server 是否启动过慢、依赖下载过慢、命令阻塞,或者 STDIO 输出了非协议内容。

12. 生产落地建议

最小 Demo 跑通后,不建议直接把 MCP 接进生产核心系统。至少要补下面几层:

能力 为什么重要 建议
工具白名单 防止模型看到过多能力 只开放必要只读工具
权限边界 防止越权访问 MCP Server 侧做目录/API 权限限制
调用审计 方便排查误调用 记录用户、工具名、参数摘要、耗时
敏感信息处理 防止 Token/隐私泄漏 参数和返回值脱敏
超时和限流 防止工具调用拖垮服务 设置 request-timeout、并发和熔断
人工确认 控制副作用动作 写操作、发布、删除类工具必须二次确认

在 Java 后端系统里,MCP 更适合先从"只读增强"做起:查文档、查知识库、读文件、读配置、查接口状态。等调用链路、审计和权限稳定后,再逐步接入有副作用的工具。

13. 总结

Spring Boot 接入 MCP 的核心不是多写几段 Prompt,而是把外部能力按标准协议接入 Spring AI 的工具调用体系。

最小闭环可以概括为:

复制代码
引入 spring-ai-starter-mcp-client
  -> 配置 STDIO/SSE/Streamable HTTP 连接
  -> 启用 toolcallback
  -> 注入 SyncMcpToolCallbackProvider
  -> ChatClient 调用 tools
  -> 用调试接口确认工具发现

如果只是本地验证,STDIO + filesystem MCP Server 足够跑通第一步。如果要进生产,重点就不再是"能不能调用",而是工具权限、过滤、审计、超时、脱敏和副作用控制。

如果你关注 Java 后端、Spring Boot、MCP/Agent 落地和线上问题排查,可以关注我的 CSDN 专栏,后面会继续拆解更接近生产环境的 Java AI 工程实践。

相关推荐
Refrain_zc1 小时前
无触摸屏场景下的蓝牙交互:Android 纯按键蓝牙扫描配对与 A2DP/Headset 连接
java·蓝牙
计算机安禾1 小时前
【算法设计与分析】第29篇:启发式与元启发式搜索方法综述
java·数据库·算法
DIY源码阁1 小时前
JavaSwing学生选课系统 - MySQL版
java·数据库·mysql·eclipse
西凉的悲伤1 小时前
Spring Boot 、Spring Cloud 微服务架构认证授权方案
spring boot·spring cloud·微服务·架构·认证授权
砍材农夫1 小时前
物联网实战:Spring Boot + Netty 搭建 MQTT | MQTT 设备模拟器
java·spring boot·后端·物联网·struts·spring·netty
城管不管1 小时前
Agent——001
android·java·数据库·llm·prompt
AC赳赳老秦1 小时前
OpenClaw批量任务队列优化:解决任务堆积、执行缓慢、优先级混乱问题
java·大数据·数据库·c++·自动化·php·openclaw
Pluchon1 小时前
萌萌技术分享笔记——Java综合项目
java·开发语言·笔记·git·github·mybatis·postman