最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
第 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 | 旧传输,仍可用 |
连不上时先查:url、endpoint、端口和防火墙。 若已连上 Server,但暂时不想把工具交给 ChatClient:
spring.ai.mcp.client.toolcallback.enabled=false
9.3 Server:用注解暴露工具
| 注解 | 作用 |
|---|---|
@McpTool / @McpToolParam | 声明工具与参数(自动生成 JSON Schema) |
@McpResource | 声明可读资源 |
@McpPrompt / @McpComplete | 声明提示模板与补全 |
annotations = @McpTool.McpAnnotations(...) 里的 readOnlyHint、destructiveHint、idempotentHint 只是给调用方的提示,不是鉴权。租户、用户校验仍要写在方法体内。
@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 异常、
Error、McpError:继续上抛 -
普通
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 → ToolCallbackProvider → ChatClient.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,不是合并。需要「默认 + 再加几个」时,把完整列表一次传入。
权限仍分两层:
-
Server:接口鉴权、数据租户隔离
-
Host:业务上谁能调哪个工具(例如只有客服角色能
create_ticket)
模型只会按名称发起调用;是否真正执行,由你的代码决定。
工具很多时,可先按权限过滤可见集,再对可见集做目录检索(第 8 章 Tool Search),避免一次把远端全量 schema 塞进 Prompt。远端工具列表变更时,记得失效缓存或重建索引。
9.5 和本地 @Tool 怎么选
本地 @Tool | MCP | |
|---|---|---|
| 部署 | 同进程 | 跨进程 / 跨语言 |
| 发布 | 随应用发版 | Server 可单独升级 |
| 超时 | 本地方法调用 | 必须设超时,建议加熔断 |
| 失败 | 本地异常 | 网络故障 + 远端业务错误 |
命名建议带前缀(如 order_query_order),减少远程 search 与本地 search 撞名。若同一批工具既给 ChatClient 用,也给图编排用,放进同一注册表,避免两套名字。
9.6 常见坑
| 现象 | 先查什么 |
|---|---|
| ChatClient 里没有 MCP 工具 | 是否连上 Server;toolcallback.enabled;Provider 是否注入;有没有 .tools / defaultTools |
| 连不上 | url、endpoint(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 一起挂载。超时、鉴权、命名和目录裁剪处理好,比抠协议细节更影响能不能稳定跑。