一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

第 9 章:用 Spring AI 掌握 Model Context Protocol(MCP)

时间:2026-09-15 11:46:01 编辑:袖梨 来源:一聚教程网

当 AI 应用需要调用另一个服务甚至另一种语言提供的工具时,继续为每项能力单独设计私有 HTTP 接口,很容易让发现、命名和接入方式逐渐失控。MCP 提供了一套统一协议,而在 Spring AI 中落地它,首先要弄清 Host 与 Server 的边界,再正确选择 Starter、传输协议和 ChatClient 的工具挂载方式。

版本:Spring AI 2.0.1(MCP Java SDK 2.0.0

目标:分清 Host / Server,选对 starter 与传输,用注解暴露工具,并把 MCP Tool 桥进 ChatClient。 

本地 @Tool 写在本进程里。工具若在别的服务、别的语言、别的团队,各自再包一层私有 HTTP,目录和鉴权很快就会散。

MCP 约定的是一套标准协议:Host(通常是你的 AI 应用)去连接一个或多个 MCP Server,发现并调用其上的 Tools、Resources、Prompts。Spring AI 在两边都能站:当 Client 连别人,或当 Server 对外暴露能力。对 ChatClient 而言,远端 Tool 最终会桥成 ToolCallback,挂载方式与第 8 章相同。

本地 @Tool     → 同进程 Bean
MCP Tool       → 跨进程 / 跨语言,桥成 ToolCallback 后再挂

MCP 管「工具从哪来」,不管「怎么编排」;也管不了权限——鉴权仍在你的 Java 代码里。


9.1 角色与能力

上图里:

  • Host:Spring AI 应用,里面有 ChatClient

  • 传输:STDIO / SSE / HTTP 等

  • Server:提供 Tools、Resources、Prompts

  • ToolCallback:把远端工具结果接回 Host

情况做法
工具就是本服务 Bean本地 @Tool
工具在别的进程 / 语言 / 团队对端做 MCP Server,本应用做 Client
两边都要.tools(本地 POJO, mcpProvider) 一起挂

三类常见能力:

能力做什么
Tools可执行操作,如查单、建工单
Resources按 URI 读内容,如政策原文
Prompts可复用的提示模板

日常先把 Tools 桥进 ChatClient;Resources / Prompts 按产品需要再接。


9.2 Starter 与传输

角色Starter
本应用连别人(Client)spring-ai-starter-mcp-client;响应式栈可用 …-client-webflux
本服务对外暴露(Server,MVC)spring-ai-starter-mcp-server-webmvc
本服务对外暴露(Server,WebFlux)spring-ai-starter-mcp-server-webflux

已有 Spring MVC 业务选 webmvc;整站 WebFlux 选 webflux。Client 同步 / 异步要和 Web 栈一致:

spring.ai.mcp.client.type=SYNC

依赖示例:

<!-- Host:连远端 MCP Server -->
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
​
<!-- Server:MVC 对外暴露 -->
<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>

Client 用 Streamable-HTTP 连接远端(2.0.1 推荐;键名以当前 starter 文档为准):

spring:
  ai:
    mcp:
      client:
        enabled: true
        type: SYNC
        streamable-http:
          connections:
            order-server:
              url: http://localhost:8091
              endpoint: /mcp

Server 侧对应:

spring:
  ai:
    mcp:
      server:
        type: SYNC
        protocol: STREAMABLE
        mcp-endpoint: /mcp

本机子进程也可用 STDIO;旧的 SSE 传输仍可用,但新项目优先 STREAMABLE。

传输常见用途
STDIO本机子进程 / sidecar
Streamable-HTTP服务之间(2.0.1 推荐)
SSE旧传输,仍可用

连不上时先查:urlendpoint、端口和防火墙。 若已连上 Server,但暂时不想把工具交给 ChatClient:

spring.ai.mcp.client.toolcallback.enabled=false

9.3 Server:用注解暴露工具

注解作用
@McpTool / @McpToolParam声明工具与参数(自动生成 JSON Schema)
@McpResource声明可读资源
@McpPrompt / @McpComplete声明提示模板与补全

annotations = @McpTool.McpAnnotations(...) 里的 readOnlyHintdestructiveHintidempotentHint 只是给调用方的提示,不是鉴权。租户、用户校验仍要写在方法体内。

@Service
public class OrderMcpTools {
​
    private final OrderService orderService;
​
    public OrderMcpTools(OrderService orderService) {
        this.orderService = orderService;
    }
​
    @McpTool(
            name = "query_order",
            description = "按订单号查询订单摘要,返回简短 JSON",
            annotations = @McpTool.McpAnnotations(readOnlyHint = true))
    public String queryOrder(
            @McpToolParam(description = "订单号", required = true) String orderId) {
        // 在这里做调用方鉴权,不要依赖 hint
        return orderService.findBrief(orderId);
    }
​
    @McpTool(
            name = "create_ticket",
            description = "创建售后工单,成功返回工单号",
            annotations = @McpTool.McpAnnotations(destructiveHint = true))
    public String createTicket(
            @McpToolParam(description = "订单号", required = true) String orderId,
            @McpToolParam(description = "问题描述", required = true) String problem) {
        return orderService.createTicket(orderId, problem);
    }
}

异常处理与本地 @Tool 同一套约定(2.0.1):

  • checked 异常、ErrorMcpError:继续上抛

  • 普通 RuntimeException(且不是 McpError):转成工具错误结果给调用方

不要默认「所有异常都会变成一段错误字符串」。

资源示例:

@McpResource(
        uri = "handbook://refund-policy",
        name = "refund-policy",
        description = "退款政策原文")
public String refundPolicy() {
    return handbookRepository.load("refund-policy");
}

Resource 常被读进 Prompt,脱敏和租户隔离要与 Tool 同等对待。


9.4 Client:挂到 ChatClient

链路是:MCP Server → 传输 → MCP Client → ToolCallbackProviderChatClient.tools(...) / defaultTools(...)

Boot 会提供 Provider(类名以自动配置为准,常见如 SyncMcpToolCallbackProvider)。它实现 ToolCallbackProvider,可直接传给第 8 章学过的挂载入口。

默认挂上(本 Client 的请求都能看见这些工具):

@Configuration
class AiConfig {
​
    @Bean
    ChatClient chatClient(
            ChatClient.Builder builder,
            SyncMcpToolCallbackProvider mcpTools,
            OrderLocalTools localTools) {
        return builder
                .defaultTools(mcpTools, localTools)
                .build();
    }
}

只在某次请求挂载(目录按场景裁剪时更常用):

String answer = chatClient.prompt()
        .user("查一下订单 10086 能不能退")
        .tools(mcpTools, localTools)
        .call()
        .content();

注意第 8 章的覆盖规则:请求级 tools(...) 会整组替换 defaultTools,不是合并。需要「默认 + 再加几个」时,把完整列表一次传入。

权限仍分两层:

  1. Server:接口鉴权、数据租户隔离

  2. Host:业务上谁能调哪个工具(例如只有客服角色能 create_ticket

模型只会按名称发起调用;是否真正执行,由你的代码决定。

工具很多时,可先按权限过滤可见集,再对可见集做目录检索(第 8 章 Tool Search),避免一次把远端全量 schema 塞进 Prompt。远端工具列表变更时,记得失效缓存或重建索引。


9.5 和本地 @Tool 怎么选

本地 @ToolMCP
部署同进程跨进程 / 跨语言
发布随应用发版Server 可单独升级
超时本地方法调用必须设超时,建议加熔断
失败本地异常网络故障 + 远端业务错误

命名建议带前缀(如 order_query_order),减少远程 search 与本地 search 撞名。若同一批工具既给 ChatClient 用,也给图编排用,放进同一注册表,避免两套名字。


9.6 常见坑

现象先查什么
ChatClient 里没有 MCP 工具是否连上 Server;toolcallback.enabled;Provider 是否注入;有没有 .tools / defaultTools
连不上urlendpoint(STREAMABLE)或 sse-endpoint(SSE)、端口、防火墙;Client SYNC/ASYNC 是否匹配 Web 栈
一调远端就卡住超时与熔断;错误是否回成短字符串给模型
鉴权失败 / 串租户凭证如何进入 MCP Session;不要依赖线程 ThreadLocal 碰巧还能用
不该调的工具被调了Host 侧白名单与业务鉴权;不要只靠 Server hint
异常表现和本地 @Tool 不一致是否误吞了应上抛的异常;对照 2.0.1 异常约定
与本地工具同名前缀;打印最终暴露给模型的 tool name 列表核对

9.7 小结

MCP 让工具可以放在别的进程里,但对 ChatClient 仍是 ToolCallback 供应线。先分清自己做 Client 还是 Server,选对 starter 和传输,用 @McpTool 暴露能力,再和本地 @Tool 一起挂载。超时、鉴权、命名和目录裁剪处理好,比抠协议细节更影响能不能稳定跑。

热门栏目