MCP(模型上下文协议)
约 20317 字大约 68 分钟
2026-09-15
合集说明
本篇合并自参考文档「MCP(Model Context Protocol)」章节,覆盖概览、客户端/服务端启动器、传输方式、安全与注解编程模型,按官方文档顺序排列:(各篇之间以 --- 分隔)
- MCP Overview(模型上下文协议概览)
- MCP Client Boot Starter(MCP 客户端启动器)
- MCP Server Boot Starter(MCP 服务器启动器)
- STDIO and SSE MCP Servers(STDIO 和 SSE MCP 服务器)
- Streamable-HTTP MCP Servers(Streamable-HTTP MCP 服务器)
- Stateless Streamable-HTTP MCP Servers(无状态 Streamable-HTTP 服务器)
- MCP Security(MCP 安全 — WIP)
- MCP Annotations Overview(MCP 注解概览)
- MCP 客户端注解(MCP Client Annotations)
- MCP 服务器注解(MCP Server Annotations)
- MCP 注解特殊参数(MCP Annotation Special Parameters)
- MCP 注解示例(MCP Annotation Examples)
- MCP 工具类(MCP Utilities)
MCP Overview(模型上下文协议概览)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-overview.html 📌 原文版本:Java AI 2.0.1
模型上下文协议(Model Context Protocol, MCP) 是一种标准化协议,使 AI 模型能以结构化方式与外部工具和资源交互。可以把它看作"AI 模型与现实世界之间的桥梁"——通过一致接口访问数据库、API、文件系统和其他外部服务,支持多种传输机制。
白话解读
MCP 就是"工具调用的 USB 接口标准"。以前每个 LLM 厂商自己一套工具协议(OpenAI Function Calling、Anthropic Tool Use、Google Function Calling…),现在统一成 MCP 协议,模型和工具之间只需要一根线就能插上。
MCP Java SDK 提供了协议的 Java 实现,支持同步和异步通信。Java AI 通过专用的 Boot Starters 和 MCP Java 注解提供全面支持——Spring 开发者可以参与 MCP 生态的两端:消费 MCP 服务器的 AI 应用,以及将 Spring 服务暴露成 MCP 服务器。
1. MCP Java SDK 架构(三层)
1.1 客户端/服务器层(顶层)
McpClient— 客户端侧操作和服务器连接管理McpServer— 服务器端协议操作和客户端请求处理- 二者利用下方的会话层进行通信管理
1.2 会话层(中间层)
McpSession— 核心会话管理接口McpClientSession/McpServerSession— 客户端/服务器专用实现
1.3 传输层(底层)
McpTransport— JSON-RPC 消息的序列化与反序列化- 支持多种传输实现:STDIO、HTTP/SSE、Streamable-HTTP
2. MCP 客户端
负责建立并管理到 MCP 服务器的连接,处理:
- 协议版本协商
- 能力协商
- 消息传输与 JSON-RPC 通信
- 工具发现与执行
- 资源访问与管理
- 提示词系统交互
- 可选:根(Roots)管理、采样(Sampling)
支持同步与异步、多种传输(Stdio、基于 Java HttpClient 的 SSE、WebFlux SSE)。
3. MCP 服务器
为客户端提供工具、资源和能力的基础组件,负责:
- 工具暴露与发现
- 基于 URI 访问的资源管理
- 提示词模板提供
- 与客户端的能力协商
- 结构化日志与通知
- 并发客户端连接管理
- 同步/异步 API
- 多种传输:Stdio、Streamable-HTTP、Stateless Streamable-HTTP、SSE
4. Java AI MCP 集成
4.1 客户端 Starters
spring-ai-starter-mcp-client— 核心 starter(STDIO、Servlet Streamable-HTTP、Stateless、SSE)spring-ai-starter-mcp-client-webflux— WebFlux 版的 Streamable-HTTP、Stateless、SSE
4.2 服务器 Starters
| 类型 | 依赖 | 属性 |
|---|---|---|
| STDIO | spring-ai-starter-mcp-server | spring.ai.mcp.server.stdio=true |
| SSE WebMVC | spring-ai-starter-mcp-server-webmvc | spring.ai.mcp.server.protocol=SSE |
| Streamable-HTTP WebMVC | spring-ai-starter-mcp-server-webmvc | spring.ai.mcp.server.protocol=STREAMABLE |
| Stateless WebMVC | spring-ai-starter-mcp-server-webmvc | spring.ai.mcp.server.protocol=STATELESS |
| SSE WebFlux | spring-ai-starter-mcp-server-webflux | spring.ai.mcp.server.protocol=SSE |
| Streamable-HTTP WebFlux | spring-ai-starter-mcp-server-webflux | spring.ai.mcp.server.protocol=STREAMABLE |
| Stateless WebFlux | spring-ai-starter-mcp-server-webflux | spring.ai.mcp.server.protocol=STATELESS |
5. Java AI MCP 注解
通过 MCP Annotations 模块用声明式注解处理 MCP 操作:
- 服务器注解:
@McpTool、@McpResource、@McpPrompt、@McpComplete - 客户端注解:
@McpLogging、@McpSampling、@McpElicitation、@McpProgress - 特殊参数:
McpSyncServerExchange、McpAsyncServerExchange、McpTransportContext、McpMeta - 自动发现:注解扫描,支持包包含/排除配置
- Spring Boot 集成:与 Boot Starters 无缝集成
6. 升级到 Java AI 2.0(破坏性变更)
6.1 依赖坐标迁移
mcp-spring-webflux 和 mcp-spring-webmvc 从 io.modelcontextprotocol.sdk 组迁移到 org.springframework.ai 组:
<!-- 变更前 -->
<dependency>
<groupId>io.modelcontextprotocol.sdk</groupId>
<artifactId>mcp-spring-webflux</artifactId>
</dependency>
<!-- 变更后 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>mcp-spring-webflux</artifactId>
</dependency>使用 spring-ai-bom 或 starter 依赖无需显式指定版本。
6.2 Java 包重定位
所有传输类已迁移到 org.springframework.ai 包下:
| 类 | 旧包 | 新包 |
|---|---|---|
WebFluxSseServerTransportProvider | io.modelcontextprotocol.server.transport | org.springframework.ai.mcp.server.webflux.transport |
WebFluxStreamableServerTransportProvider | 同上 | 同上 |
WebFluxStatelessServerTransport | 同上 | 同上 |
WebMvcSseServerTransportProvider | 同上 | org.springframework.ai.mcp.server.webmvc.transport |
WebMvcStreamableServerTransportProvider | 同上 | 同上 |
WebMvcStatelessServerTransport | 同上 | 同上 |
WebFluxSseClientTransport | io.modelcontextprotocol.client.transport | org.springframework.ai.mcp.client.webflux.transport |
WebClientStreamableHttpTransport | 同上 | 同上 |
6.3 MCP SDK 版本
Java AI 2.0 要求 MCP Java SDK 1.0.0(RC1 或更高)。从 0.18.x 升到 1.0.x 发布线。
6.4 自动配置用户
仅依赖 Java AI starters 自动配置的应用无需改动 Java 代码——只需更新依赖坐标。
7. 附加资源
02_MCP-Client-Boot-Starter.md03_MCP-Server-Boot-Starter.md06_MCP-Annotations-Overview.md- Model Context Protocol 规范
MCP Client Boot Starter(MCP 客户端启动器)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-client-boot-starter-docs.html 📌 原文版本:Java AI 2.0.1
MCP 客户端 Boot Starter 为 Spring Boot 应用提供 MCP 客户端功能的自动配置。支持同步和异步实现,多种传输方式。
白话解读
这就是"接 MCP 服务器"的官方封装:填个 YAML 配置就能连上 N 个 MCP 服务器,自动把它们的工具注册成 ToolCallbackProvider 给你用。
1. 启动器
1.1 标准 MCP 客户端
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>可通过 STDIO、SSE、Streamable-HTTP、Stateless Streamable-HTTP 等传输连接多个 MCP 服务器。SSE 和 Streamable-HTTP 用 JDK HttpClient。
1.2 WebFlux 客户端
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-client-webflux</artifactId>
</dependency>基于 WebFlux 的 Streamable-HTTP、Stateless Streamable-HTTP 和 SSE。生产推荐 WebFlux 版。
2. 配置属性
2.1 通用属性(前缀 spring.ai.mcp.client)
| 属性 | 说明 | 默认 |
|---|---|---|
enabled | 启用 MCP 客户端 | true |
name | 客户端实例名称 | spring-ai-mcp-client |
version | 客户端实例版本 | 1.0.0 |
initialized | 创建时是否初始化客户端 | true |
request-timeout | 请求超时 | 20s |
type | 客户端类型 SYNC/ASYNC | SYNC |
root-change-notification | 启用根变更通知 | true |
toolcallback.enabled | 启用 MCP 工具回调与 Java AI 集成 | true |
不能混用
所有客户端必须统一为 SYNC 或 ASYNC,不能混用。
2.2 MCP 注解属性(前缀 spring.ai.mcp.client.annotation-scanner)
| 属性 | 说明 | 默认 |
|---|---|---|
enabled | 启用客户端注解扫描 | true |
2.3 STDIO 传输(前缀 spring.ai.mcp.client.stdio)
spring:
ai:
mcp:
client:
stdio:
root-change-notification: true
connections:
server1:
command: /path/to/server
args:
- --port=8080
- --mode=production
env:
API_KEY: your-api-key
DEBUG: "true"也可以用 Claude Desktop 格式的外部 JSON 文件:
spring:
ai:
mcp:
client:
stdio:
servers-configuration: classpath:mcp-servers.jsonWindows 下 STDIO 配置
Windows 上 npx、npm 等命令是 .cmd 批处理文件,Java ProcessBuilder 无法直接执行。要用 cmd.exe /c 包装:
{
"mcpServers": {
"filesystem": {
"command": "cmd.exe",
"args": ["/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "C:\\Users\\username\\Desktop"]
}
}
}跨平台编程式配置:
@Bean(destroyMethod = "close")
@ConditionalOnMissingBean(McpSyncClient.class)
public McpSyncClient mcpClient() {
ServerParameters stdioParams;
if (isWindows()) {
var winArgs = new ArrayList<>(Arrays.asList(
"/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "target"));
stdioParams = ServerParameters.builder("cmd.exe").args(winArgs).build();
} else {
stdioParams = ServerParameters.builder("npx")
.args("-y", "@modelcontextprotocol/server-filesystem", "target")
.build();
}
return McpClient.sync(new StdioClientTransport(stdioParams, McpJsonDefaults.getMapper()))
.requestTimeout(Duration.ofSeconds(10))
.build()
.initialize();
}需要 cmd.exe 包装的常见 Windows 批处理:npx.cmd、npm.cmd、python.cmd、pip.cmd、mvn.cmd、gradle.cmd。
2.4 Streamable-HTTP(前缀 spring.ai.mcp.client.streamable-http)
spring:
ai:
mcp:
client:
streamable-http:
connections:
server1:
url: http://localhost:8080
server2:
url: http://otherserver:8081
endpoint: /custom-sse # 默认 /mcp2.5 SSE 传输(前缀 spring.ai.mcp.client.sse)
spring:
ai:
mcp:
client:
sse:
connections:
server1:
url: http://localhost:8080
server2:
url: http://otherserver:8081
sse-endpoint: /custom-sse
mcp-hub:
url: http://localhost:3000
sse-endpoint: /mcp-hub/sse/cf9ec4527e3c4a2cbb149a85ea45ab01
api-server:
url: https://api.example.com
sse-endpoint: /v1/mcp/events?token=abc123&format=jsonURL 拆分指南:
| 完整 URL | url | sse-endpoint |
|---|---|---|
http://localhost:3000/mcp-hub/sse/token123 | localhost:3000 | /mcp-hub/sse/token123 |
http://localhost:8080/sse | localhost:8080 | /sse(或省略用默认) |
3. 功能特性
3.1 SYNC / ASYNC 客户端类型
SYNC(默认):阻塞式请求-响应;只注册同步 MCP 注解方法。ASYNC:响应式;只注册异步方法。
3.2 客户端自定义
实现 McpCustomizer<McpClient.SyncSpec> / McpCustomizer<McpClient.AsyncSpec> 自定义每个客户端:
@Component
public class CustomMcpSyncClientCustomizer implements McpClientCustomizer<McpClient.SyncSpec> {
@Override
public void customize(String serverConfigurationName, McpClient.SyncSpec spec) {
spec.requestTimeout(Duration.ofSeconds(30))
.roots(roots)
.sampling(req -> { /* 采样处理 */ return result; })
.elicitation(req -> new ElicitResult(...))
.progressConsumer(progress -> { /* 进度 */ })
.toolsChangeConsumer(tools -> { /* 工具列表变更 */ })
.resourcesChangeConsumer(resources -> { /* 资源列表变更 */ })
.promptsChangeConsumer(prompts -> { /* 提示词变更 */ })
.loggingConsumer(log -> { /* 日志 */ });
}
}serverConfigurationName 是应用到的服务器配置名。自定义器会被自动检测并应用。
3.3 工具过滤(McpToolFilter)
@Component
public class CustomMcpToolFilter implements McpToolFilter {
@Override
public boolean test(McpConnectionInfo connectionInfo, McpSchema.Tool tool) {
// 排除特定客户端
if (connectionInfo.clientInfo().name().equals("restricted-client")) return false;
// 只允许特定前缀
if (tool.name().startsWith("allowed_")) return true;
// 排除标记为 experimental 的
if (tool.description() != null && tool.description().contains("experimental")) return false;
return true;
}
}McpConnectionInfo 包含 clientCapabilities、clientInfo、initializeResult。
只能有一个 McpToolFilter Bean
要多个过滤器自己组合成一个。
3.4 工具名称前缀生成(McpToolNamePrefixGenerator)
默认 DefaultMcpToolNamePrefixGenerator:自动给跨连接重名工具加 alt_N_ 前缀。
@Bean
public McpToolNamePrefixGenerator mcpToolNamePrefixGenerator() {
return McpToolNamePrefixGenerator.noPrefix(); // 完全禁用前缀(多服务器可能冲突)
}关闭前缀的后果
用 noPrefix() + 多 MCP 服务器 → 重名工具会抛 IllegalStateException。
3.5 ToolContext → MCP Meta 转换器
把 ToolContext 转成 MCP 工具调用元数据(透传给 MCP 服务器):
ChatClient.create(chatModel)
.prompt("Tell me more about the customer with ID 42")
.toolContext(Map.of("progressToken", "my-progress-token"))
.call()
.content();默认 ToolContextToMcpMetaConverter.defaultConverter():过滤 MCP exchange 键 + null 值。要禁用用 noOp()。
3.6 禁用 MCP ToolCallback 自动配置
spring.ai.mcp.client.toolcallback.enabled: false3.7 MCP 客户端注解
@Component
public class McpClientHandlers {
@McpLogging(clients = "server1")
public void handleLoggingMessage(LoggingMessageNotification notification) { ... }
@McpSampling(clients = "server1")
public CreateMessageResult handleSamplingRequest(CreateMessageRequest request) { ... }
@McpProgress(clients = "server1")
public void handleProgressNotification(ProgressNotification notification) { ... }
@McpToolListChanged(clients = "server1")
public void handleToolListChanged(List<McpSchema.Tool> updatedTools) { ... }
}clients 参数指定作用的服务器名。同步/异步实现都支持。
4. 使用示例
spring:
ai:
mcp:
client:
enabled: true
name: my-mcp-client
version: 1.0.0
request-timeout: 30s
type: SYNC
sse:
connections:
server1: { url: http://localhost:8080 }
streamable-http:
connections:
server3: { url: http://localhost:8083, endpoint: /mcp }
stdio:
root-change-notification: false
connections:
server1:
command: /path/to/server
args: [--port=8080, --mode=production]
env: { API_KEY: your-api-key, DEBUG: "true" }@Autowired private List<McpSyncClient> mcpSyncClients;
@Autowired private SyncMcpToolCallbackProvider toolCallbackProvider;
ToolCallback[] toolCallbacks = toolCallbackProvider.getToolCallbacks();5. 示例应用
- Brave Web Search Chatbot
- Default MCP Client Starter
- WebFlux MCP Client Starter
MCP Server Boot Starter(MCP 服务器启动器)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-server-boot-starter-docs.html 📌 原文版本:Java AI 2.0.1
MCP 服务器 Boot Starters 为 Spring Boot 应用中的 MCP 服务器提供自动配置,无缝集成 MCP 能力与 Spring Boot 自动配置系统。
白话解读
这是"把 Spring 服务变成 MCP 服务器"的封装:用 @McpTool 注解方法,启动器自动扫描、注册、暴露成 MCP 协议。客户端那边用 MCP 协议来调你的工具,跟你用 @Tool 给本地 ChatClient 用几乎一样。
默认无认证
基于 HTTP 的服务器传输(SSE、Streamable-HTTP、Stateless)默认暴露未认证的 JSON-RPC 端点。启动器本身不做认证/授权,任何能访问端点的客户端都能枚举和调用所有工具/资源/提示。在暴露到 localhost 之外之前必须加安全层(Spring Security 等)。STDIO 不受影响(进程内通信)。
1. 服务器 Starters 总览
STDIO
| 类型 | 依赖 | 属性 |
|---|---|---|
| STDIO | spring-ai-starter-mcp-server | spring.ai.mcp.server.stdio=true |
WebMVC
| 类型 | 依赖 | 属性 |
|---|---|---|
| SSE WebMVC(2.0 弃用,改用 STREAMABLE) | spring-ai-starter-mcp-server-webmvc | spring.ai.mcp.server.protocol=SSE |
| Streamable-HTTP WebMVC | 同上 | spring.ai.mcp.server.protocol=STREAMABLE |
| Stateless WebMVC | 同上 | spring.ai.mcp.server.protocol=STATELESS |
WebFlux(响应式)
| 类型 | 依赖 | 属性 |
|---|---|---|
| SSE WebFlux(2.0 弃用) | spring-ai-starter-mcp-server-webflux | spring.ai.mcp.server.protocol=SSE |
| Streamable-HTTP WebFlux | 同上 | spring.ai.mcp.server.protocol=STREAMABLE |
| Stateless WebFlux | 同上 | spring.ai.mcp.server.protocol=STATELESS |
2. 服务器能力
- Tools:暴露可被 LLM 调用的工具
- Resources:以 URI 暴露资源
- Prompts:暴露提示模板
- Completions:为提示/资源 URI 提供补全建议
- Logging:向客户端发结构化日志
- Progress:长操作进度跟踪
- Ping:心跳健康检查
所有能力默认启用。
3. 服务器协议
- STDIO:进程内协议,通过 stdin/stdout 通信。启用
spring.ai.mcp.server.stdio=true。 - SSE:服务器发送事件;2.0 起被 Streamable-HTTP 取代。
- Streamable-HTTP:HTTP POST + GET + 可选 SSE 流。
spring.ai.mcp.server.protocol=STREAMABLE。 - Stateless:无状态,适合微服务/云原生部署。
spring.ai.mcp.server.protocol=STATELESS。
4. 保护 MCP 服务器
基于 HTTP 的 MCP 服务器默认与认证无关——自动配置只连接端点,不强制认证。
保护责任在于你在前面加的安全层(Spring Security 等)。默认 POST /mcp 接受所有请求——任何可网络访问的客户端都能无凭据枚举和调用工具、资源、提示。
5. SYNC / ASYNC 服务器
- SYNC(默认):
spring.ai.mcp.server.type=SYNC;只注册同步 MCP 注解方法。 - ASYNC:
spring.ai.mcp.server.type=ASYNC;只注册异步方法。
6. MCP 服务器注解
6.1 关键注解
@McpTool:标记方法为 MCP 工具,自动生成 JSON schema@McpResource:通过 URI 模板提供资源访问@McpPrompt:生成提示消息@McpComplete:提示自动补全
6.2 特殊参数
McpMeta:从 MCP 请求访问元数据@McpProgressToken:接收长操作进度令牌McpSyncServerExchange/McpAsyncServerExchange:完整服务器上下文McpTransportContext:无状态操作的轻量级上下文CallToolRequest:动态 schema 支持
6.3 简单示例
@Component
public class CalculatorTools {
@McpTool(name = "add", description = "Add two numbers together")
public int add(
@McpToolParam(description = "First number", required = true) int a,
@McpToolParam(description = "Second number", required = true) int b) {
return a + b;
}
@McpResource(uri = "config://{key}", name = "Configuration")
public String getConfig(String key) {
return configData.get(key);
}
}7. 向 McpTransportContext 添加数据
默认 McpTransportContext 是空的(McpTransportContext.EMPTY)——保持与传输无关。
要在工具里拿 HTTP 头等传输特定元数据,配置 TransportContextExtractor:
WebMVC
@Bean
public WebMvcStreamableServerTransportProvider transport() {
return WebMvcStreamableServerTransportProvider.builder()
.contextExtractor(serverRequest -> {
String auth = serverRequest.headers().firstHeader("Authorization");
return McpTransportContext.create(Map.of("authorization", auth));
})
.build();
}WebFlux
@Bean
public WebFluxStreamableServerTransportProvider transport() {
return WebFluxStreamableServerTransportProvider.builder()
.contextExtractor(serverRequest -> {
String auth = serverRequest.headers().firstHeader("Authorization");
return McpTransportContext.create(Map.of("authorization", auth));
})
.build();
}工具里通过 McpSyncRequestContext / McpAsyncRequestContext 访问:
@McpTool
public String accessProtectedResource(McpSyncRequestContext requestContext) {
McpTransportContext ctx = requestContext.transportContext();
String auth = (String) ctx.get("authorization");
return "Successfully accessed protected resource.";
}8. 自动配置
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
}自动配置会:
- 扫描带 MCP 注解的 Bean
- 创建相应规范
- 注册到 MCP 服务器
- 按配置处理同步/异步实现
9. 配置属性
spring:
ai:
mcp:
server:
type: SYNC # or ASYNC
annotation-scanner:
enabled: true10. 示例应用
- Weather Server (SSE WebFlux)
- Weather Server (STDIO)
- Weather Server Manual Configuration
- Streamable-HTTP WebFlux/WebMVC Example
- Stateless WebFlux/WebMVC Example
11. 附加资源
04_STDIO-and-SSE-Servers.md— STDIO/SSE 服务器细节05_Streamable-HTTP-Servers.md— Streamable-HTTP 服务器细节- MCP Server Annotations、Special Parameters、MCP Annotations Examples
STDIO and SSE MCP Servers(STDIO 和 SSE MCP 服务器)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-stdio-sse-server-boot-starter-docs.html 📌 原文版本:Java AI 2.0.1
STDIO 和 SSE MCP 服务器支持多种传输机制,每种有专属 starter。客户端侧用 STDIO 客户端或 SSE 客户端连接。
白话解读
STDIO = 通过进程标准输入输出通信(命令行工具场景);SSE = HTTP 长连接,服务器推数据给客户端(Web 场景)。
1. STDIO MCP 服务器
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server</artifactId>
</dependency>特性:
- 命令行工具、桌面工具场景
- 无需 Web 依赖
- 提供基础服务器组件配置
- 处理工具、资源、提示词规范
- 管理服务器能力及变更通知
- 同时支持同步/异步服务器实现
2. SSE WebMVC 服务器
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>- 基于 Spring MVC 的 HTTP 传输(
WebMvcSseServerTransportProvider) - 自动配置 SSE 端点
- 可选 STDIO(
spring.ai.mcp.server.stdio=true) - 含
spring-boot-starter-web+org.springframework.ai:mcp-spring-webmvc
3. SSE WebFlux 服务器
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>激活 McpWebFluxServerAutoConfiguration + McpServerAutoConfiguration:
- WebFlux 响应式传输(
WebFluxSseServerTransportProvider) - 自动配置响应式 SSE 端点
- 可选 STDIO
- 含
spring-boot-starter-webflux+org.springframework.ai:mcp-spring-webflux
Spring Boot 默认行为
类路径上同时有 DispatcherServlet(WebMVC)和 DispatcherHandler(WebFlux)时,Spring Boot 优先 DispatcherServlet。项目用 spring-boot-starter-web 时建议用 spring-ai-starter-mcp-server-webmvc。
4. 配置属性
4.1 通用属性(前缀 spring.ai.mcp.server)
| 属性 | 说明 | 默认 |
|---|---|---|
enabled | 启用 MCP 服务器 | true |
tool-callback-converter | 把 Java AI ToolCallback 转 MCP 规范 | true |
stdio | 启用 STDIO 传输 | false |
name | 服务器名 | mcp-server |
version | 服务器版本 | 1.0.0 |
instructions | 给客户端的指引 | null |
type | SYNC/ASYNC | SYNC |
capabilities.resource | 启用资源能力 | true |
capabilities.tool | 启用工具能力 | true |
capabilities.prompt | 启用提示词能力 | true |
capabilities.completion | 启用补全能力 | true |
resource-change-notification | 资源变更通知 | true |
prompt-change-notification | 提示词变更通知 | true |
tool-change-notification | 工具变更通知 | true |
expose-mcp-client-tools | 把下游 MCP 工具再暴露为本服务器工具 | false |
tool-response-mime-type | 按工具名指定响应 MIME 类型(如 spring.ai.mcp.server.tool-response-mime-type.generateImage=image/png) | - |
request-timeout | 服务器响应超时 | 20s |
4.2 MCP 注解属性(前缀 spring.ai.mcp.server.annotation-scanner)
| 属性 | 说明 | 默认 |
|---|---|---|
enabled | 启用注解自动扫描 | true |
4.3 SSE 属性(前缀 spring.ai.mcp.server)
注意
出于向后兼容,SSE 属性没有额外后缀(如 .sse)。
| 属性 | 说明 | 默认 |
|---|---|---|
sse-message-endpoint | 客户端发消息的 SSE 端点路径 | /mcp/message |
sse-endpoint | SSE 端点路径 | /sse |
base-url | 可选 URL 前缀(如 /api/v1,端点变成 /api/v1/sse 和 /api/v1/mcp/message) | - |
keep-alive-interval | 连接保活间隔 | null(禁用) |
5. 功能与能力
5.1 工具
自动检测注册来自 ToolCallback Bean、List<ToolCallback> Bean、ToolCallbackProvider Bean。
@Bean
public ToolCallbackProvider myTools(...) {
List<ToolCallback> tools = ...;
return ToolCallbackProvider.from(tools);
}或用底层 API:
@Bean
public List<McpServerFeatures.SyncToolSpecification> myTools(...) {
List<McpServerFeatures.SyncToolSpecification> tools = ...;
return tools;
}工具按名称去重。tool-callback-converter=false 禁用自动检测。
5.2 工具上下文支持
ToolContext 在 exchange 键下包含 McpSyncServerExchange,通过 McpToolUtils.getMcpExchange(toolContext) 访问——可调 exchange.loggingNotification(...)、exchange.createMessage(...)。
5.3 资源
@Bean
public List<McpServerFeatures.SyncResourceSpecification> myResources() {
var systemInfoResource = McpSchema.Resource.builder(...).build();
var resourceSpecification = new McpServerFeatures.SyncResourceSpecification(systemInfoResource,
(exchange, request) -> {
var systemInfo = Map.of(...);
String json = new JsonMapper().writeValueAsString(systemInfo);
return McpSchema.ReadResourceResult.builder(
List.of(McpSchema.TextResourceContents.builder(request.uri(), json).mimeType("application/json").build()));
});
return List.of(resourceSpecification);
}5.4 提示词
@Bean
public List<McpServerFeatures.SyncPromptSpecification> myPrompts() {
var prompt = McpSchema.Prompt.builder("greeting").description("A friendly greeting prompt")
.arguments(List.of(McpSchema.PromptArgument.builder("name").required(true).build()))
.build();
var spec = new McpServerFeatures.SyncPromptSpecification(prompt, (exchange, req) -> {
String name = (String) req.arguments().getOrDefault("name", "friend");
var msg = PromptMessage.builder(Role.USER, TextContent.builder("Hello " + name + "!").build()).build();
return GetPromptResult.builder(List.of(msg)).build();
});
return List.of(spec);
}5.5 补全
@Bean
public List<McpServerFeatures.SyncCompletionSpecification> myCompletions() {
var completion = new McpServerFeatures.SyncCompletionSpecification(
McpSchema.PromptReference.builder("code-completion").title("Provides code completion suggestions").build(),
(exchange, request) -> new McpSchema.CompleteResult(List.of("python", "pytorch", "pyside"), 10, true)
);
return List.of(completion);
}5.6 日志
处理器里通过 exchange 发送:
(exchange, request) -> {
exchange.loggingNotification(LoggingMessageNotification.builder(LoggingLevel.INFO, "This is a test").logger("test-logger").build());
}5.7 进度
(exchange, request) -> {
exchange.progressNotification(ProgressNotification.builder("test-progress-token", 0.25).total(1.0).message("in progress").build());
}5.8 根列表变更
@Bean
public BiConsumer<McpSyncServerExchange, List<McpSchema.Root>> rootsChangeHandler() {
return (exchange, roots) -> logger.info("Registering root resources: {}", roots);
}5.9 Ping(心跳)
(exchange, request) -> exchange.ping();5.10 保活
spring:
ai:
mcp:
server:
keep-alive-interval: 30s6. 使用示例
STDIO 服务器
spring:
ai:
mcp:
server:
name: stdio-mcp-server
version: 1.0.0
type: SYNCWebMVC 服务器
spring:
ai:
mcp:
server:
name: webmvc-mcp-server
version: 1.0.0
type: SYNC
instructions: "This server provides weather information tools and resources"
capabilities: { tool: true, resource: true, prompt: true, completion: true }
sse-message-endpoint: /mcp/messages
keep-alive-interval: 30sWebFlux 服务器
spring:
ai:
mcp:
server:
name: webflux-mcp-server
version: 1.0.0
type: ASYNC
instructions: "..."
capabilities: { tool: true, resource: true, prompt: true, completion: true }
sse-message-endpoint: /mcp/messages
keep-alive-interval: 30sSpring Boot 应用
@Service
public class WeatherService {
@Tool(description = "Get weather information by city name")
public String getWeather(String cityName) { /* ... */ return ...; }
}
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}
}自动配置会把所有 ToolCallback Bean 合并注册为 MCP 工具。
Streamable-HTTP MCP Servers(Streamable-HTTP MCP 服务器)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-streamable-http-server-boot-starter-docs.html 📌 原文版本:Java AI 2.0.1
Streamable-HTTP 传输允许 MCP 服务器作为独立进程运行,通过 HTTP POST/GET 处理多个客户端连接,并可选 SSE 流式传输多条服务器消息。它取代了 SSE 传输。
白话解读
SSE 是单向服务器推数据;Streamable-HTTP 是 HTTP POST + GET + 可选 SSE 的组合,更灵活、更现代。这是 MCP 2025-03-26 规范的新传输方式。
适合需要向客户端通知工具、资源或提示动态变化的场景。启用属性 spring.ai.mcp.server.protocol=STREAMABLE。
1. Streamable-HTTP WebMVC 服务器
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
</dependency>spring.ai.mcp.server.protocol=STREAMABLE。
特性:
- 完整 MCP 服务器功能 + Spring MVC Streamable 传输
- 工具、资源、提示、补全、日志、进度、ping、root-changes
- 持久连接管理
2. Streamable-HTTP WebFlux 服务器
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webflux</artifactId>
</dependency>STREAMABLE。特性:响应式 + WebFlux Streamable + 非阻塞持久连接。
3. 配置属性
3.1 通用属性(前缀 spring.ai.mcp.server)
| 属性 | 说明 | 默认 |
|---|---|---|
enabled | 启用服务器 | true |
protocol | 必须设为 STREAMABLE | - |
tool-callback-converter | Java AI ToolCallbacks → MCP Tool 规范 | true |
name / version / instructions / type | 标识信息 | 详见 03_MCP-Server-Boot-Starter.md |
capabilities.{resource,tool,prompt,completion} | 启用对应能力 | true |
{resource,prompt,tool}-change-notification | 变更通知 | true |
expose-mcp-client-tools | 把下游 MCP 工具再暴露为本服务器工具 | false |
tool-response-mime-type | 按工具名指定响应 MIME 类型 | - |
request-timeout | 请求超时 | 20s |
3.2 MCP 注解属性(前缀 spring.ai.mcp.server.annotation-scanner)
enabled=true(默认)启用注解扫描。
3.3 Streamable-HTTP 属性(前缀 spring.ai.mcp.server.streamable-http)
| 属性 | 说明 | 默认 |
|---|---|---|
mcp-endpoint | MCP 端点路径 | /mcp |
keep-alive-interval | 连接保活间隔 | null(禁用) |
disallow-delete | 禁止 DELETE 操作 | false |
4. 功能与能力
完整支持 Tools / Resources / Prompts / Completions / Logging / Progress / Root List Changes / Ping / Keep Alive。用法同 04_STDIO-and-SSE-Servers.md。
保活限制
Streamable-HTTP 服务器的 keep-alive 仅对接收服务器消息的 SSE 连接生效。
5. 使用示例
spring:
ai:
mcp:
server:
protocol: STREAMABLE
name: streamable-mcp-server
version: 1.0.0
type: SYNC
instructions: "This streamable server provides real-time notifications"
resource-change-notification: true
tool-change-notification: true
prompt-change-notification: true
streamable-http:
mcp-endpoint: /api/mcp
keep-alive-interval: 30sSpring Boot 应用代码同 04_STDIO-and-SSE-Servers.md。
Stateless Streamable-HTTP MCP Servers(无状态 Streamable-HTTP 服务器)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-stateless-server-boot-starter-docs.html 📌 原文版本:Java AI 2.0.1
无状态 Streamable-HTTP MCP 服务器请求之间不维护会话状态。简化部署,特别适合微服务架构和云原生部署。
启用属性 spring.ai.mcp.server.protocol=STATELESS。
白话解读
无状态 = 每个 HTTP 请求都是独立的,服务器不记任何上下文("金鱼脑升级版")。好处是可以横向扩任意多个实例、随便重启,适合 K8s/serverless。
不支持的能力
无状态服务器不支持服务器发起的消息请求:elicitation、sampling、ping。
1. 无状态 WebMVC / WebFlux
两个 starter 同 STREAMABLE,只改属性:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
<!-- 或 spring-ai-starter-mcp-server-webflux -->
</dependency>spring.ai.mcp.server.protocol=STATELESS特性:
- 基于 MVC/WebMVC 或 WebFlux 的无状态操作
- 无会话状态管理
- 简化部署模型
- 云原生 / 高吞吐优化
2. 配置属性
2.1 通用属性
同 03_MCP-Server-Boot-Starter.md + protocol=STATELESS。
2.2 MCP 注解属性
spring.ai.mcp.server.annotation-scanner.enabled=true。
2.3 Stateless 连接属性(前缀 spring.ai.mcp.server.stateless)
| 属性 | 说明 | 默认 |
|---|---|---|
mcp-endpoint | MCP 端点路径 | /mcp |
disallow-delete | 禁止 DELETE 操作 | false |
3. 特性与能力
- Tools / Resources / Prompts / Completions 自动注册方式同有状态服务器,区别在于底层 API 是
McpStatelessServerFeatures.*而非McpServerFeatures.*。 - 工具回调自动检测和注册同
04_STDIO-and-SSE-Servers.md。 - 不支持变更通知(无状态服务器不维护会话)。
工具上下文
工具上下文支持(ToolContext + McpSyncServerExchange)不适用于无状态服务器。
4. 使用示例
spring:
ai:
mcp:
server:
protocol: STATELESS
name: stateless-mcp-server
version: 1.0.0
type: ASYNC
instructions: "This stateless server is optimized for cloud deployments"
streamable-http:
mcp-endpoint: /api/mcpSpring Boot 应用代码同 04_STDIO-and-SSE-Servers.md。
MCP Security(MCP 安全 — WIP)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-security.html 📌 原文版本:Java AI 2.0.1
WIP
该功能仍在开发中。文档和 API 在未来版本中可能变化。
Java AI MCP 安全模块为 MCP 提供 OAuth 2.0 和 API 密钥安全支持。这是社区项目(spring-ai-community/mcp-security),尚未获得 Java AI 或 MCP 项目官方认可。
白话解读
Java AI 团队意识到 MCP 服务器默认无认证是个大问题,但官方方案还在做。社区先出了一个"够用"的方案——OAuth 2.0 + API Key。等官方方案落地后会平滑迁移。
1. 三个组件
- MCP 服务器安全:OAuth 2.0 资源服务器 + API 密钥
- MCP 客户端安全:OAuth 2.0 客户端支持
- MCP 授权服务器:增强 Spring Authorization Server + MCP 特性
2. MCP 服务器安全
⚠️ 仅兼容 Spring WebMVC 服务器。
2.1 依赖
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>mcp-server-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 可选:OAuth2 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>2.2 OAuth 2.0 配置
spring.ai.mcp.server.name=my-cool-mcp-server
spring.ai.mcp.server.protocol=STREAMABLE # 或 STATELESS@Configuration
@EnableWebSecurity
class McpServerConfiguration {
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuerUrl;
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.with(McpServerOAuth2Configurer.mcpServerOAuth2(),
(mcpAuthorization) -> {
mcpAuthorization.authorizationServer(issuerUrl); // 必需
mcpAuthorization.validateAudienceClaim(true); // 可选,RFC 8707
})
.build();
}
}仅保护工具调用
让 initialize / tools/list 保持公开,只保护工具调用:
.authorizeHttpRequests(auth -> {
auth.requestMatcher("/mcp").permitAll();
auth.anyRequest().authenticated();
})
.with(McpResourceServerConfigurer.mcpServerOAuth2(), ...)工具方法上加 @PreAuthorize("isAuthenticated()"),或在工具方法内通过 SecurityContextHolder.getContext().getAuthentication() 拿到当前认证信息。
2.3 API 密钥认证
@Configuration
@EnableWebSecurity
class McpServerConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(authz -> authz.anyRequest().authenticated())
.with(mcpServerApiKey(),
(apiKey) -> {
apiKey.apiKeyRepository(apiKeyRepository());
// apiKey.headerName("CUSTOM-API-KEY");
// apiKey.authenticationConverter(request -> ...);
})
.build();
}
private ApiKeyEntityRepository<ApiKeyEntityImpl> apiKeyRepository() {
var apiKey = ApiKeyEntityImpl.builder()
.name("test api key").id("api01").secret("mycustomapikey")
.build();
return new InMemoryApiKeyEntityRepository<>(List.of(apiKey));
}
}不适合高流量生产
InMemoryApiKeyEntityRepository 用 bcrypt 存储密钥,计算开销大。生产请实现自己的 ApiKeyEntityRepository。
请求时通过 X-API-key: api01.mycustomapikey 头调用。
2.4 已知限制
- 不支持已弃用的 SSE 传输(用 Streamable HTTP 或 Stateless)。
- 不支持 WebFlux 服务器。
- 不支持不透明令牌(用 JWT)。
3. MCP 客户端安全
⚠️ 仅支持
McpSyncClient。
3.1 依赖
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>mcp-client-security</artifactId>
</dependency>3.2 三种 OAuth 2.0 流程
- 授权码流程:用户级权限,每个 MCP 请求都在用户请求上下文中。
- 客户端凭证流程:机器对机器,无人值守。
- 混合流程:工具发现在启动时(无人),工具调用需要用户权限——Spring Boot 属性配置场景用这个。
3.3 通用设置
spring.ai.mcp.client.type=SYNC
# 授权码 / 混合
spring.security.oauth2.client.registration.authserver.client-id=...
spring.security.oauth2.client.registration.authserver.client-secret=...
spring.security.oauth2.client.registration.authserver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.authserver.provider=authserver
# 客户端凭证 / 混合
spring.security.oauth2.client.registration.authserver-client-credentials.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.authserver-client-credentials.provider=authserver
# 授权服务器
spring.security.oauth2.client.provider.authserver.issuer-uri=...@Configuration
@EnableWebSecurity
class SecurityConfiguration {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.oauth2Client(Customizer.withDefaults())
.build();
}
}3.4 基于 HttpClient 的客户端(spring-ai-starter-mcp-client)
@Configuration
class McpConfiguration {
@Bean
McpCustomizer<McpClient.SyncSpec> syncClientCustomizer() {
return (name, syncSpec) -> syncSpec.transportContextProvider(
new AuthenticationMcpTransportContextProvider());
}
@Bean
McpSyncHttpClientRequestCustomizer requestCustomizer(OAuth2AuthorizedClientManager cm) {
return new OAuth2AuthorizationCodeSyncHttpRequestCustomizer(cm, "authserver");
}
}可用的自定义器:
OAuth2AuthorizationCodeSyncHttpRequestCustomizerOAuth2ClientCredentialsSyncHttpRequestCustomizerOAuth2HybridSyncHttpRequestCustomizer
3.5 基于 WebClient 的客户端(spring-ai-starter-mcp-client-webflux)
@Bean
WebClient.Builder mcpWebClientBuilder(OAuth2AuthorizedClientManager cm) {
return WebClient.builder().filter(
new McpOAuth2AuthorizationCodeExchangeFilterFunction(cm, "authserver"));
}3.6 绕过自动配置的变通方法
Java AI 自动配置在启动时初始化 MCP 客户端 → 基于用户的认证会有问题。
方案 1:禁用 @Tool 自动配置
@Bean
ToolCallbackResolver resolver() {
return new StaticToolCallbackResolver(List.of());
}方案 2:编程式配置 MCP 客户端(见原文)。
3.7 已知限制
- 不支持 WebFlux 服务器。
- 自动配置启动时初始化需要变通。
- 客户端实现支持 SSE(SSE / HttpClient / WebClient 都支持)。
4. MCP 授权服务器
增强 Spring Authorization Server,加入 MCP 特有功能(动态客户端注册、资源指示器)。
4.1 依赖
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>mcp-authorization-server</artifactId>
</dependency>4.2 配置示例
spring:
application: { name: sample-authorization-server }
security:
oauth2:
authorizationserver:
client:
default-client:
token: { access-token-time-to-live: 1h }
registration:
client-id: "default-client"
client-secret: "{noop}default-secret"
client-authentication-methods: ["client_secret_basic", "none"]
authorization-grant-types: ["authorization_code", "client_credentials"]
redirect-uris:
- "http://127.0.0.1:8080/authorize/oauth2/code/authserver"
- "http://localhost:6274/oauth/callback" # mcp-inspector
- "https://claude.ai/api/mcp/auth_callback" # claude code
user: { name: user, password: password }
server.servlet.session.cookie.name: MCP_AUTHORIZATION_SERVER_SESSIONID@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.with(McpAuthorizationServerConfigurer.mcpAuthorizationServer(), withDefaults())
.formLogin(withDefaults())
.build();
}4.3 已知限制
- 不支持 WebFlux 服务器。
- 每个客户端支持所有资源标识符。
5. 示例与集成
mcp-server-security + 支持它的 mcp-authorization-server 可与以下工具集成:
- Cursor
- Claude Desktop
- MCP Inspector
MCP Inspector 调试
用 MCP Inspector 时可能需要禁用 CSRF 和 CORS 保护。
6. 附加资源
- MCP Authorization Spec
- MCP Security GitHub
- Spring Security OAuth 2.0 资源服务器 / 客户端参考
- Spring Authorization Server 参考
MCP Annotations Overview(MCP 注解概览)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-annotations-overview.html 📌 文档版本:Java AI 2.0.1
Java AI MCP 注解模块为 Java 中的 MCP 服务器和客户端提供基于注解的方法处理能力,以声明式注解简化 MCP 服务器方法和客户端处理器的创建与注册。
白话解读
MCP 注解 = 用注解把 Spring Bean 转成 MCP 协议对象。@McpTool 让 Spring 方法变成 MCP 服务器暴露的工具,@McpLogging 让 Spring 方法变成 MCP 客户端的日志处理器。
1. 架构
1.1 服务器注解
@McpTool— 实现 MCP 工具,自动生成 JSON Schema@McpResource— 通过 URI 模板提供资源访问@McpPrompt— 生成提示消息@McpComplete— 自动补全
1.2 客户端注解
@McpLogging— 日志消息通知@McpSampling— 采样请求@McpElicitation— 诱导请求@McpProgress— 进度通知@McpToolListChanged/@McpResourceListChanged/@McpPromptListChanged— 列表变更通知
1.3 特殊参数与注解
McpSyncRequestContext— 同步操作统一接口:原始请求、服务器 exchange(有状态)、传输上下文(无状态),以及日志/进度/采样/诱导/roots 的便捷方法。自动注入,从 JSON Schema 排除。McpAsyncRequestContext— 异步版,返回Mono。McpTransportContext— 无状态操作的轻量级传输上下文访问。@McpProgressToken— 标记方法参数接收进度令牌。自动注入,从 JSON Schema 排除。McpMeta— 访问 MCP 请求/通知/结果的元数据。自动注入,从参数数量限制和 JSON Schema 排除。MetaProvider— 实现该接口提供工具/提示/资源声明中的_meta字段数据,通过@McpTool/@McpPrompt/@McpResource的metaProvider属性引用。
2. 快速开始
2.1 依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-mcp-annotations</artifactId>
</dependency>使用任一 MCP Boot Starter 时会自动包含:
spring-ai-starter-mcp-client/-client-webfluxspring-ai-starter-mcp-server/-server-webflux/-server-webmvc
2.2 配置
客户端注解扫描:
spring:
ai:
mcp:
client:
annotation-scanner:
enabled: true服务器注解扫描:
spring:
ai:
mcp:
server:
annotation-scanner:
enabled: true2.3 快速示例
服务器端:
@Component
public class CalculatorTools {
@McpTool(name = "add", description = "Add two numbers together")
public int add(
@McpToolParam(description = "First number", required = true) int a,
@McpToolParam(description = "Second number", required = true) int b) {
return a + b;
}
@McpTool(name = "multiply", description = "Multiply two numbers")
public double multiply(
@McpToolParam(description = "First number", required = true) double x,
@McpToolParam(description = "Second number", required = true) double y) {
return x * y;
}
}客户端:
@Component
public class LoggingHandler {
@McpLogging(clients = "my-server")
public void handleLoggingMessage(LoggingMessageNotification notification) {
System.out.println("Received log: " + notification.level() +
" - " + notification.data());
}
}Spring Boot 自动配置会自动检测并注册这些 Bean。
3. 文档
09_MCP-Annotations-Client.md— 客户端注解详细10_MCP-Annotations-Server.md— 服务器注解详细11_MCP-Annotations-Special-Params.md— 特殊参数类型12_MCP-Annotations-Examples.md— 完整示例
4. 附加资源
- MCP Overview / MCP Client Boot Starter / MCP Server Boot Starter
- Model Context Protocol Specification
MCP 客户端注解(MCP Client Annotations)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-annotations-client.html
📌 原文版本:Java AI 2.0.1(Stable)
MCP 客户端注解(MCP Client Annotations)提供了一种声明式的方式,使用 Java 注解来实现 MCP 客户端处理器(handler)。这些注解简化了对服务端通知(server notifications)以及客户端侧操作(client-side operations)的处理。
白话解读
"声明式"是 Spring 的一贯风格——你只需要在方法上贴注解、写参数类型,框架就会自动把这个方法注册成对应类型的处理器;你不用关心它怎么绑定、怎么调用。把这当成"订阅某个事件,写好处理函数"。
所有 MCP 客户端注解必须包含一个 clients 参数,用于将处理器关联到特定的 MCP 客户端连接。该 clients 值必须与应用程序属性(application properties)中配置的连接名称(connection name)相匹配。
客户端注解一览
MCP 客户端侧共提供了 7 个注解,按"事件类型"分为三类:
| 注解 | 触发时机 | 返回类型 |
|---|---|---|
@McpLogging | 服务端发送日志通知时 | void / 任意 |
@McpSampling | 服务端请求 LLM 采样时 | CreateMessageResult 或 Mono<CreateMessageResult> |
@McpElicitation | 服务端需要向用户索取更多信息时 | ElicitResult 或 Mono<ElicitResult> |
@McpProgress | 服务端上报长时间操作进度时 | void 或 Mono<Void> |
@McpToolListChanged | 服务端工具列表变更时 | void / List<McpSchema.Tool> |
@McpResourceListChanged | 服务端资源列表变更时 | void / List<McpSchema.Resource> |
@McpPromptListChanged | 服务端提示列表变更时 | void / List<McpSchema.Prompt> |
@McpLogging
@McpLogging 注解用于处理来自 MCP 服务端的日志消息通知(logging message notifications)。
基本用法
@Component
public class LoggingHandler {
// 把整个通知对象作为参数
@McpLogging(clients = "my-mcp-server")
public void handleLoggingMessage(LoggingMessageNotification notification) {
System.out.println("Received log: " + notification.level() +
" - " + notification.data());
}
}使用独立参数
// 也可以只取通知中的几个字段,框架会按字段名自动匹配注入
@McpLogging(clients = "my-mcp-server")
public void handleLoggingWithParams(LoggingLevel level, String logger, String data) {
System.out.println(String.format("[%s] %s: %s", level, logger, data));
}@McpSampling
@McpSampling 注解用于处理来自 MCP 服务端的采样请求(sampling requests),以进行 LLM 补全(LLM completions)。
白话解读
采样(Sampling)是 MCP 的核心能力之一——服务端在执行某个工具时,可以反过来向客户端要一次 LLM 调用结果。这让"调用方拥有 LLM"成为可能,服务端不需要自带模型。
同步实现
@Component
public class SamplingHandler {
@McpSampling(clients = "llm-server")
public CreateMessageResult handleSamplingRequest(CreateMessageRequest request) {
// 根据请求生成响应(这里示意)
String response = generateLLMResponse(request);
// 用 builder 构造一个 CreateMessageResult,标明这是 ASSISTANT 角色、用 gpt-4 生成的
return CreateMessageResult.builder(Role.ASSISTANT, response, "gpt-4")
.build();
}
}异步实现
@Component
public class AsyncSamplingHandler {
@McpSampling(clients = "llm-server")
public Mono<CreateMessageResult> handleAsyncSampling(CreateMessageRequest request) {
return Mono.fromCallable(() -> {
// 响应式实现:把同步逻辑包装在 Mono 里
String response = generateLLMResponse(request);
return CreateMessageResult.builder(Role.ASSISTANT, response, "gpt-4")
.build();
}).subscribeOn(Schedulers.boundedElastic()); // 放到弹性线程池执行
}
}@McpElicitation
@McpElicitation 注解用于处理引导请求(elicitation requests),以从用户处收集额外信息。
关于 elicitation
"引导"是 MCP 协议中的一种交互——服务端在执行某个工具时发现需要用户补充信息(比如确认收货地址),就向客户端发起 elicitation 请求,客户端把请求展示给用户、收集答案,再回传给服务端。
基本用法
@Component
public class ElicitationHandler {
@McpElicitation(clients = "interactive-server")
public ElicitResult handleElicitationRequest(ElicitRequest request) {
// 在真实应用里通常会弹出一个表单;这里简化为同步取默认数据
Map<String, Object> userData = presentFormToUser(request.requestedSchema());
if (userData != null) {
// 用户填了表单 → 接受
return new ElicitResult(ElicitResult.Action.ACCEPT, userData);
} else {
// 用户点了取消 → 拒绝
return new ElicitResult(ElicitResult.Action.DECLINE, null);
}
}
}与用户交互(更完整版本)
@McpElicitation(clients = "interactive-server")
public ElicitResult handleInteractiveElicitation(ElicitRequest request) {
Map<String, Object> schema = request.requestedSchema();
Map<String, Object> userData = new HashMap<>();
// 检查 schema 里要哪些字段
if (schema != null && schema.containsKey("properties")) {
Map<String, Object> properties = (Map<String, Object>) schema.get("properties");
if (properties.containsKey("name")) {
userData.put("name", promptUser("Enter your name:")); // 让用户输入
}
if (properties.containsKey("email")) {
userData.put("email", promptUser("Enter your email:"));
}
if (properties.containsKey("preferences")) {
userData.put("preferences", gatherPreferences());
}
}
return new ElicitResult(ElicitResult.Action.ACCEPT, userData);
}异步引导
@McpElicitation(clients = "interactive-server")
public Mono<ElicitResult> handleAsyncElicitation(ElicitRequest request) {
return Mono.fromCallable(() -> {
// 异步弹出 UI 收集用户输入
Map<String, Object> userData = asyncGatherUserInput(request);
return new ElicitResult(ElicitResult.Action.ACCEPT, userData);
})
.timeout(Duration.ofSeconds(30)) // 30 秒超时
.onErrorReturn(new ElicitResult(ElicitResult.Action.CANCEL, null)); // 失败时取消
}@McpProgress
@McpProgress 注解用于处理长时间运行操作的进度通知(progress notifications)。
基本用法
@Component
public class ProgressHandler {
@McpProgress(clients = "my-mcp-server")
public void handleProgressNotification(ProgressNotification notification) {
double percentage = notification.progress() * 100;
System.out.println(String.format("Progress: %.2f%% - %s",
percentage, notification.message()));
}
}使用独立参数
@McpProgress(clients = "my-mcp-server")
public void handleProgressWithDetails(
String progressToken, // 进度令牌:用于关联同一次任务的多次上报
double progress, // 0.0 ~ 1.0
Double total, // 可选:若提供则按"X / total"显示
String message) { // 当前步骤描述
if (total != null) {
System.out.println(String.format("[%s] %.0f/%.0f - %s",
progressToken, progress, total, message));
} else {
System.out.println(String.format("[%s] %.2f%% - %s",
progressToken, progress * 100, message));
}
// 同步更新 UI 进度条
updateProgressBar(progressToken, progress);
}特定客户端的进度
@McpProgress(clients = "long-running-server")
public void handleLongRunningProgress(ProgressNotification notification) {
// 按服务器名记录进度
progressTracker.update("long-running-server", notification);
// 当进度达到 100% 时发个完成通知
if (notification.progress() >= 1.0) {
notifyCompletion(notification.progressToken());
}
}@McpToolListChanged
@McpToolListChanged 注解用于处理服务端工具列表发生变化时的通知。
基本用法
@Component
public class ToolListChangedHandler {
@McpToolListChanged(clients = "tool-server")
public void handleToolListChanged(List<McpSchema.Tool> updatedTools) {
System.out.println("Tool list updated: " + updatedTools.size() + " tools available");
// 更新本地工具注册表(同步刷新客户端缓存)
toolRegistry.updateTools(updatedTools);
// 打印新增的工具
for (McpSchema.Tool tool : updatedTools) {
System.out.println(" - " + tool.name() + ": " + tool.description());
}
}
}异步处理
@McpToolListChanged(clients = "tool-server")
public Mono<Void> handleAsyncToolListChanged(List<McpSchema.Tool> updatedTools) {
return Mono.fromRunnable(() -> {
// 异步刷新本地注册表
processToolListUpdate(updatedTools);
// 给感兴趣的下游发事件
eventBus.publish(new ToolListUpdatedEvent(updatedTools));
}).then();
}特定客户端的工具更新
@McpToolListChanged(clients = "dynamic-server")
public void handleDynamicServerToolUpdate(List<McpSchema.Tool> updatedTools) {
// 针对那些"工具列表经常变"的服务器做特殊处理
dynamicToolManager.updateServerTools("dynamic-server", updatedTools);
// 重新评估本客户端的能力(例如是否还能完成某些任务)
reevaluateToolCapabilities();
}@McpResourceListChanged
@McpResourceListChanged 注解用于处理服务端资源列表发生变化时的通知。
基本用法
@Component
public class ResourceListChangedHandler {
@McpResourceListChanged(clients = "resource-server")
public void handleResourceListChanged(List<McpSchema.Resource> updatedResources) {
System.out.println("Resources updated: " + updatedResources.size());
// 清空并重建本地资源缓存
resourceCache.clear();
for (McpSchema.Resource resource : updatedResources) {
resourceCache.register(resource);
}
}
}带资源分析(diff 思路)
@McpResourceListChanged(clients = "resource-server")
public void analyzeResourceChanges(List<McpSchema.Resource> updatedResources) {
// 1. 收集当前所有资源的 URI
Set<String> newUris = updatedResources.stream()
.map(McpSchema.Resource::uri)
.collect(Collectors.toSet());
// 2. 找出被移除的 URI(在旧集合中存在、但不在新集合中的)
Set<String> removedUris = previousUris.stream()
.filter(uri -> !newUris.contains(uri))
.collect(Collectors.toSet());
if (!removedUris.isEmpty()) {
handleRemovedResources(removedUris); // 清理被移除资源对应的本地缓存
}
// 3. 更新本地快照
previousUris = newUris;
}@McpPromptListChanged
@McpPromptListChanged 注解用于处理服务端提示(prompt)列表发生变化时的通知。
基本用法
@Component
public class PromptListChangedHandler {
@McpPromptListChanged(clients = "prompt-server")
public void handlePromptListChanged(List<McpSchema.Prompt> updatedPrompts) {
System.out.println("Prompts updated: " + updatedPrompts.size());
// 刷新提示目录
promptCatalog.updatePrompts(updatedPrompts);
// 如果有 UI 控制器就刷新前端
if (uiController != null) {
uiController.refreshPromptList(updatedPrompts);
}
}
}异步处理
@McpPromptListChanged(clients = "prompt-server")
public Mono<Void> handleAsyncPromptUpdate(List<McpSchema.Prompt> updatedPrompts) {
return Flux.fromIterable(updatedPrompts)
.flatMap(prompt -> validatePrompt(prompt)) // 对每个 prompt 做校验
.collectList()
.doOnNext(validPrompts -> {
promptRepository.saveAll(validPrompts); // 持久化
})
.then();
}Spring Boot 集成
借助 Spring Boot 自动配置(auto-configuration),客户端处理器会被自动检测并注册:
@SpringBootApplication
public class McpClientApplication {
public static void main(String[] args) {
SpringApplication.run(McpClientApplication.class, args);
}
}
@Component
public class MyClientHandlers {
@McpLogging(clients = "my-server")
public void handleLogs(LoggingMessageNotification notification) {
// 处理日志
}
@McpSampling(clients = "my-server")
public CreateMessageResult handleSampling(CreateMessageRequest request) {
// 处理采样请求
}
@McpProgress(clients = "my-server")
public void handleProgress(ProgressNotification notification) {
// 处理进度通知
}
}自动配置将会完成以下五件事:
- 扫描带有 MCP 客户端注解的 bean
- 创建相应的 specifications
- 将它们注册到 MCP 客户端
- 同时支持同步与异步实现
- 通过客户端特定的处理器(client-specific handlers)处理多个客户端
配置属性
配置客户端注解扫描器(annotation scanner)以及客户端连接:
spring:
ai:
mcp:
client:
type: SYNC # 同步客户端(也可以 ASYNC)
annotation-scanner:
enabled: true # 开启注解扫描
# 配置客户端连接 — 连接名称即为注解中 clients 的取值
sse:
connections:
my-server: # 这就是注解里 clients 的取值
url: http://localhost:8080
tool-server: # 另一个连接
url: http://localhost:8081
stdio:
connections:
local-server: # 同样作为 clients 的取值
command: /path/to/mcp-server
args:
- --mode=production注解中的 clients 参数必须与配置中定义的连接名称相匹配。在上面的示例中,有效的 clients 值为:"my-server"、"tool-server" 和 "local-server"。
与 MCP 客户端一起使用
被注解标注的处理器会自动与 MCP 客户端集成——无需手动注册,处理器按 clients 名匹配到对应客户端:
@Autowired
private List<McpSyncClient> mcpClients;
// mcpClients 会自动使用你的被注解处理器,按 clients 名一一对应对于每一个 MCP 客户端连接,clients 匹配的处理器都会被自动注册,并在相应事件发生时被调用。
附加资源
- MCP Annotations Overview(MCP 注解总览)
- Server Annotations(服务端注解)
- Special Parameters(特殊参数)
- MCP Client Boot Starter(MCP 客户端启动器)
MCP 服务器注解(MCP Server Annotations)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-annotations-server.html
📌 原文版本:Java AI 2.0.1(Stable)
MCP 服务器注解提供了一种声明式的方式来使用 Java 注解实现 MCP 服务器功能。这些注解简化了工具(tools)、资源(resources)、提示(prompts)和补全处理器(completion handlers)的创建。
白话解读
这一节是 MCP 服务端的"工具手册"——告诉你 @McpTool、@McpResource、@McpPrompt、@McpComplete 这四个核心注解的字段、过滤规则和异常处理约定。它们和上一节的客户端注解配对使用,构成完整的 MCP 通信能力。
@McpTool
@McpTool 注解将方法标记为 MCP 工具实现,并自动生成 JSON Schema。
基本用法
@Component
public class CalculatorTools {
@McpTool(name = "add", description = "Add two numbers together")
public int add(
@McpToolParam(description = "First number", required = true) int a,
@McpToolParam(description = "Second number", required = true) int b) {
return a + b;
}
}注解属性
| 属性 | 默认值 | 描述 |
|---|---|---|
name | 方法名 | 工具标识符。如果未提供,则默认为方法名。 |
description | 方法名 | 工具的人类可读描述。 |
title | "" | 用于 UI 和最终用户上下文——优化为人类可读。如果未提供,则使用 name 进行显示(优先级:annotations.title > title > name)。 |
generateOutputSchema | false | 如果为 true,则自动为非原始返回类型生成 JSON 输出 Schema。 |
annotations | @McpAnnotations | 为客户端提供额外的提示(参见下面的工具注解)。 |
metaProvider | DefaultMetaProvider.class | 实现 MetaProvider 的类,为工具声明中的 _meta 字段提供数据。 |
工具注解(提示)
通过嵌套的 @McpTool.McpAnnotations 可以向客户端传递额外提示,帮助客户端理解工具行为:
@McpTool(name = "calculate-area",
description = "Calculate the area of a rectangle",
title = "Rectangle Area Calculator",
generateOutputSchema = true,
annotations = @McpTool.McpAnnotations(
title = "Rectangle Area Calculator",
readOnlyHint = true, // 不修改环境
destructiveHint = false, // 不做破坏性操作
idempotentHint = true // 重复调用效果相同
))
public AreaResult calculateRectangleArea(
@McpToolParam(description = "Width", required = true) double width,
@McpToolParam(description = "Height", required = true) double height) {
return new AreaResult(width * height, "square units");
}McpAnnotations 嵌套注解的字段说明:
| 提示 | 默认值 | 描述 |
|---|---|---|
title | "" | 工具的人类可读标题。 |
readOnlyHint | false | 如果为 true,则工具不会修改其环境。 |
destructiveHint | true | 如果为 true,工具可能执行破坏性更新(仅当 readOnlyHint == false 时才有意义)。 |
idempotentHint | false | 如果为 true,使用相同参数调用不会产生额外效果(仅当 readOnlyHint == false 时才有意义)。 |
openWorldHint | true | 如果为 true,工具可能与外部实体(例如网络搜索)交互;如果为 false,则域是封闭的。 |
带请求上下文
工具可以接收 McpSyncRequestContext 来访问 MCP 协议层能力(日志、进度、ping):
@McpTool(name = "process-data", description = "Process data with request context")
public String processData(
McpSyncRequestContext context, // 注入请求上下文
@McpToolParam(description = "Data to process", required = true) String data) {
// 发送日志通知
context.info("Processing data: " + data);
// 发送进度通知(便捷写法)
context.progress(p -> p.progress(0.5).total(1.0).message("Processing..."));
// 给客户端发 ping(健康检查)
context.ping();
return "Processed: " + data.toUpperCase();
}动态 Schema 支持
工具可以接收 CallToolRequest,在运行时根据参数动态处理:
@McpTool(name = "flexible-tool", description = "Process dynamic schema")
public CallToolResult processDynamic(CallToolRequest request) {
Map<String, Object> args = request.arguments(); // 拿到原始的 JSON 参数
// 运行时决定怎么解析
String result = "Processed " + args.size() + " arguments dynamically";
return CallToolResult.builder()
.addTextContent(result)
.build();
}进度跟踪
工具可以访问请求上下文中的进度令牌,跟踪长时间操作:
@McpTool(name = "long-task", description = "Long-running task with progress")
public String performLongTask(
McpSyncRequestContext context,
@McpToolParam(description = "Task name", required = true) String taskName) {
// 从请求里读出进度令牌(只有客户端给了才会非空)
String progressToken = context.request().progressToken();
if (progressToken != null) {
context.progress(p -> p.progress(0.0).total(1.0).message("Starting task"));
// ... 执行实际工作 ...
context.progress(p -> p.progress(1.0).total(1.0).message("Task completed"));
}
return "Task " + taskName + " completed";
}异常处理
@McpTool 方法抛出的异常遵循与常规 Java AI @Tool 方法相同的契约:
RuntimeException转换为错误CallToolResult并传递给模型,模型可以对其进行推理或重试。- 声明的受检异常或
Error抛出并失败工具调用,因此不会传递给模型。
一句话记忆
"想告诉 LLM 就抛 RuntimeException,不想告诉就声明 throws 受检异常。"
// 受检异常:失败会上抛到 MCP 协议层,模型看不到错误内容
@McpTool(name = "read-file", description = "Read a file from disk")
public String readFile(@McpToolParam(description = "Path", required = true) String path) throws IOException {
// IOException 会冒泡到协议层,不会作为错误结果送给模型
return Files.readString(Path.of(path));
}相比之下:
// RuntimeException:作为错误结果发送给模型,模型可以"看到"错误并尝试恢复
@McpTool(name = "divide", description = "Divide two numbers")
public double divide(int a, int b) {
if (b == 0) {
// 转成错误 CallToolResult 报告给模型
throw new IllegalArgumentException("Cannot divide by zero");
}
return (double) a / b;
}协议级异常的例外
协议级别的 McpError 异常(例如用于 URL elicitation 等 MCP 协议流程的异常)始终传播到 MCP 协议层,而不是转换为错误 CallToolResult——无论 McpError 是直接抛出还是通过响应式返回类型(Mono 或 Flux)发出,都适用此规则。
@McpResource
@McpResource 注解通过 URI 模板 提供对资源的访问。
注解属性
| 属性 | 默认值 | 描述 |
|---|---|---|
uri | "" | 资源的 URI(或 URI 模板)。使用 {varName} 表示模板变量。 |
name | "" | 程序化标识符。当 title 缺失时也用作显示名称。 |
title | "" | 可选的人类可读名称,用于显示。 |
description | "" | 资源所代表内容的描述。 |
mimeType | "text/plain" | 资源内容的 MIME 类型。 |
metaProvider | DefaultMetaProvider.class | 实现 MetaProvider 的类,为 _meta 字段提供数据。 |
annotations | @McpAnnotations(…) | 用于受众、优先级和最后修改元数据的客户端注解。 |
资源嵌套的 McpAnnotations 支持:
| 属性 | 默认值 | 描述 |
|---|---|---|
audience | {Role.USER} | 描述预期使用者(Role.USER、Role.ASSISTANT 或两者)。 |
priority | 0.5 | 重要性从 0.0(最低)到 1.0(最高)。值为 1.0 表示实际必需。 |
lastModified | "" | 资源最后修改的 ISO 8601 日期时间。 |
基本用法
@Component
public class ResourceProvider {
@McpResource(
uri = "config://{key}", // URI 模板:{key} 是变量
name = "Configuration",
title = "App Configuration",
description = "Provides configuration data")
public String getConfig(String key) { // 框架按名自动注入
return configData.get(key);
}
}带 ReadResourceResult(带元数据版本)
@McpResource(
uri = "user-profile://{username}",
name = "User Profile",
description = "Provides user profile information")
public ReadResourceResult getUserProfile(String username) {
String profileData = loadUserProfile(username);
return ReadResourceResult.builder(List.of(
TextResourceContents.builder(
"user-profile://" + username,
profileData).mimeType("application/json").build()
)).build();
}带请求上下文
@McpResource(
uri = "data://{id}",
name = "Data Resource",
description = "Resource with request context")
public ReadResourceResult getData(
McpSyncRequestContext context, // 同样可以拿到上下文
String id) {
// 通过便捷方法发日志通知
context.info("Accessing resource: " + id);
// 给客户端发 ping
context.ping();
String data = fetchData(id);
return ReadResourceResult.builder(List.of(
TextResourceContents.builder("data://" + id, data).mimeType("text/plain").build()
)).build();
}@McpPrompt
@McpPrompt 注解为 AI 交互生成提示消息。
注解属性
| 属性 | 默认值 | 描述 |
|---|---|---|
name | "" | 提示的唯一标识符。 |
title | "" | 可选的人类可读名称,用于显示。 |
description | "" | 可选的人类可读描述。 |
metaProvider | DefaultMetaProvider.class | 实现 MetaProvider 的类,为 _meta 字段提供数据。 |
基本用法
@Component
public class PromptProvider {
@McpPrompt(
name = "greeting",
description = "Generate a greeting message")
public GetPromptResult greeting(
@McpArg(name = "name", description = "User's name", required = true)
String name) {
String message = "Hello, " + name + "! How can I help you today?";
return GetPromptResult.builder(List.of(new PromptMessage(Role.ASSISTANT, TextContent.builder(message).build())))
.description("Greeting")
.build();
}
}带可选参数
@McpPrompt(
name = "personalized-message",
description = "Generate a personalized message")
public GetPromptResult personalizedMessage(
@McpArg(name = "name", required = true) String name,
@McpArg(name = "age", required = false) Integer age, // 可选
@McpArg(name = "interests", required = false) String interests) // 可选
{
StringBuilder message = new StringBuilder();
message.append("Hello, ").append(name).append("!\n\n");
if (age != null) {
message.append("At ").append(age).append(" years old, ");
// 添加年龄相关的内容
}
if (interests != null && !interests.isEmpty()) {
message.append("Your interest in ").append(interests);
// 添加兴趣相关的内容
}
return GetPromptResult.builder(List.of(new PromptMessage(Role.ASSISTANT, TextContent.builder(message.toString()).build())))
.description("Personalized Message")
.build();
}@McpComplete
@McpComplete 注解为提示和资源 URI 模板提供自动补全功能。
两个属性二选一
使用 prompt 或 uri 属性之一——不能同时使用两者:
prompt:补全命名提示的参数uri:补全命名资源 URI 的 URI 模板表达式
提示参数补全
@Component
public class CompletionProvider {
@McpComplete(prompt = "city-search") // 给名为 city-search 的提示做参数补全
public List<String> completeCityName(String prefix) { // prefix:用户已输入的前缀
return cities.stream()
.filter(city -> city.toLowerCase().startsWith(prefix.toLowerCase()))
.limit(10)
.toList();
}
}资源 URI 补全
@McpComplete(uri = "config://{key}") // 给 URI 模板里的 {key} 做补全
public List<String> completeConfigKey(String prefix) {
return configKeys.stream()
.filter(key -> key.startsWith(prefix))
.limit(10)
.toList();
}带 CompleteRequest.CompleteArgument
当需要根据参数名做不同补全时,可以接收完整的参数对象:
@McpComplete(prompt = "travel-planner")
public List<String> completeTravelDestination(CompleteRequest.CompleteArgument argument) {
String prefix = argument.value().toLowerCase();
String argumentName = argument.name();
// 根据参数名返回不同的补全列表
if ("city".equals(argumentName)) {
return completeCities(prefix);
} else if ("country".equals(argumentName)) {
return completeCountries(prefix);
}
return List.of();
}带 CompleteResult
如果还想告诉客户端"还有更多"或"总共多少",返回 CompleteResult:
@McpComplete(prompt = "code-completion")
public CompleteResult completeCode(String prefix) {
List<String> completions = generateCodeCompletions(prefix);
// total = 一共有多少候选;hasMore = 是否还有更多未返回
return new CompleteResult(
new CompleteResult.CompleteCompletion(
completions,
completions.size(),
hasMoreCompletions
)
);
}无状态 vs 有状态实现
统一请求上下文(推荐)
使用 McpSyncRequestContext 或 McpAsyncRequestContext 作为统一接口,同时适用于有状态和无状态操作:
public record UserInfo(String name, String email, int age) {}
@McpTool(name = "unified-tool", description = "Tool with unified request context")
public String unifiedTool(
McpSyncRequestContext context,
@McpToolParam(description = "Input", required = true) String input) {
// 访问请求及元数据
String progressToken = context.request().progressToken();
// 用便捷方法记日志
context.info("Processing: " + input);
// 进度上报(百分比形式)
context.progress(50); // 50%
// ping 客户端
context.ping();
// 用任何"反向"操作前先检查能力
if (context.elicitEnabled()) {
// 仅在有状态模式下可用
StructuredElicitResult<UserInfo> elicitResult = context.elicit(UserInfo.class);
if (elicitResult.action() == ElicitResult.Action.ACCEPT) {
// 使用用户输入
}
}
if (context.sampleEnabled()) {
// 仅在有状态模式下可用
CreateMessageResult samplingResult = context.sample("Generate response");
// 使用采样结果
}
if (context.rootsEnabled()) {
// 仅在有状态模式下可用
ListRootsResult roots = context.roots();
roots.roots().forEach(root -> context.info("Root: " + root.uri()));
}
return "Processed with unified context";
}简单操作(无上下文)
对于最简单的工具,可以完全省略上下文参数:
@McpTool(name = "simple-add", description = "Simple addition")
public int simpleAdd(
@McpToolParam(description = "First number", required = true) int a,
@McpToolParam(description = "Second number", required = true) int b) {
return a + b;
}轻量级无状态(带 McpTransportContext)
对于需要最少传输上下文的无状态操作:
@McpTool(name = "stateless-tool", description = "Stateless with transport context")
public String statelessTool(
McpTransportContext context, // 仅传输层上下文
@McpToolParam(description = "Input", required = true) String input) {
// 只能访问传输级信息,不能进行 roots/elicitation/sampling
return "Processed: " + input;
}无状态的双向限制
无状态服务器不支持双向操作——因此在无状态模式下,使用 McpSyncRequestContext 或 McpAsyncRequestContext 的方法将被忽略(不注册)。
按服务器类型过滤方法
MCP 注解框架根据服务器类型和方法特征自动过滤注解方法,确保只为每个服务器配置注册合适的方法。被过滤的方法会记录警告日志,方便调试。
同步 vs 异步过滤
同步服务器
同步服务器(配置为 spring.ai.mcp.server.type=SYNC)使用同步提供者,它们接受:
非响应式返回类型的方法:
- 基本类型(
int、double、boolean) - 对象类型(
String、Integer、自定义 POJOs) - MCP 类型(
CallToolResult、ReadResourceResult、GetPromptResult、CompleteResult) - 集合(
List<String>、Map<String, Object>)
- 基本类型(
过滤掉具有响应式返回类型的方法:
Mono<T>Flux<T>Publisher<T>
@Component
public class SyncTools {
@McpTool(name = "sync-tool", description = "Synchronous tool")
public String syncTool(String input) {
// 这个方法会被注册到 sync 服务器
return "Processed: " + input;
}
@McpTool(name = "async-tool", description = "Async tool")
public Mono<String> asyncTool(String input) {
// 这个方法在 sync 服务器上会被过滤掉,并打 warning
return Mono.just("Processed: " + input);
}
}异步服务器
异步服务器(配置为 spring.ai.mcp.server.type=ASYNC)使用异步提供者,它们接受:
响应式返回类型的方法:
Mono<T>(用于单个结果)Flux<T>(用于流式结果)Publisher<T>(通用响应式类型)
过滤掉具有非响应式返回类型的方法:
- 基本类型、对象类型、集合、MCP 结果类型
@Component
public class AsyncTools {
@McpTool(name = "async-tool", description = "Async tool")
public Mono<String> asyncTool(String input) {
// 这个方法会被注册到 async 服务器
return Mono.just("Processed: " + input);
}
@McpTool(name = "sync-tool", description = "Sync tool")
public String syncTool(String input) {
// 这个方法在 async 服务器上会被过滤掉
return "Processed: " + input;
}
}有状态 vs 无状态过滤
有状态服务器
有状态服务器支持双向通信,并接受具有以下参数的方法:
双向上下文参数:
McpSyncRequestContext(用于同步操作)McpAsyncRequestContext(用于异步操作)McpSyncServerExchange(旧版,用于同步操作)McpAsyncServerExchange(旧版,用于异步操作)
支持双向操作:
roots()—— 访问根目录elicit()—— 请求用户输入sample()—— 请求 LLM 采样
@Component
public class StatefulTools {
@McpTool(name = "interactive-tool", description = "Tool with bidirectional operations")
public String interactiveTool(
McpSyncRequestContext context,
@McpToolParam(description = "Input", required = true) String input) {
// 这个方法会被注册到有状态服务器
if (context.sampleEnabled()) {
var samplingResult = context.sample("Generate response");
// 处理采样结果……
}
return "Processed with context";
}
}无状态服务器
无状态服务器针对简单的请求-响应模式进行了优化:
过滤掉具有双向上下文参数的方法:
- 具有
McpSyncRequestContext的方法被跳过 - 具有
McpAsyncRequestContext的方法被跳过 - 具有
McpSyncServerExchange的方法被跳过 - 具有
McpAsyncServerExchange的方法被跳过 - 每个被过滤的方法都会记录警告
- 具有
接受的方法:
McpTransportContext(轻量级无状态上下文)- 没有上下文参数
- 仅常规
@McpToolParam参数
不支持双向操作:
roots()—— 不可用elicit()—— 不可用sample()—— 不可用
@Component
public class StatelessTools {
@McpTool(name = "simple-tool", description = "Simple stateless tool")
public String simpleTool(@McpToolParam(description = "Input") String input) {
// 这个方法会被注册到无状态服务器
return "Processed: " + input;
}
@McpTool(name = "context-tool", description = "Tool with transport context")
public String contextTool(
McpTransportContext context,
@McpToolParam(description = "Input") String input) {
// 这个方法会被注册到无状态服务器
return "Processed: " + input;
}
@McpTool(name = "bidirectional-tool", description = "Tool with bidirectional context")
public String bidirectionalTool(
McpSyncRequestContext context, // 无状态服务器不接受这种参数
@McpToolParam(description = "Input") String input) {
// 这个方法在无状态服务器上会被过滤掉
return "Processed with sampling";
}
}过滤摘要
| 服务器类型 | 接受的方法 | 过滤的方法 |
|---|---|---|
| 同步有状态 | 非响应式返回 + 双向上下文 | 响应式返回(Mono/Flux) |
| 异步有状态 | 响应式返回(Mono/Flux) + 双向上下文 | 非响应式返回 |
| 同步无状态 | 非响应式返回 + 无双向上下文 | 响应式返回或双向上下文参数 |
| 异步无状态 | 响应式返回(Mono/Flux) + 无双向上下文 | 非响应式返回或双向上下文参数 |
方法过滤的最佳实践
- 保持方法与服务器类型一致——同步服务器使用同步方法,异步使用异步方法
- 将有状态和无状态实现分离到不同的类中 以保持清晰
- 在启动期间检查日志 寻找被过滤方法的警告
- 使用正确的上下文——有状态使用
McpSyncRequestContext/McpAsyncRequestContext,无状态使用McpTransportContext - 如果同时支持有状态和无状态部署,则测试两种模式
异步支持
所有服务器注解都支持使用 Reactor 的异步实现:
@Component
public class AsyncTools {
@McpTool(name = "async-fetch", description = "Fetch data asynchronously")
public Mono<String> asyncFetch(
@McpToolParam(description = "URL", required = true) String url) {
return Mono.fromCallable(() -> {
// 模拟异步操作
return fetchFromUrl(url);
}).subscribeOn(Schedulers.boundedElastic()); // 弹性线程池
}
@McpResource(uri = "async-data://{id}", name = "Async Data")
public Mono<ReadResourceResult> asyncResource(String id) {
return Mono.fromCallable(() -> {
String data = loadData(id);
return ReadResourceResult.builder(List.of(
TextResourceContents.builder("async-data://" + id, data).mimeType("text/plain").build()
)).build();
}).delayElements(Duration.ofMillis(100));
}
}Spring Boot 集成
使用 Spring Boot 自动配置,注解 bean 将被自动检测和注册:
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
}
@Component
public class MyMcpTools {
// 你的 @McpTool 注解方法
}
@Component
public class MyMcpResources {
// 你的 @McpResource 注解方法
}自动配置将:
- 扫描带有 MCP 注解的 bean
- 创建适当的规范(specifications)
- 将它们注册到 MCP 服务器
- 根据配置处理同步和异步实现
配置属性
spring:
ai:
mcp:
server:
type: SYNC # 或 ASYNC
annotation-scanner:
enabled: true # 开启注解扫描相关链接
- MCP Annotations Overview(MCP 注解总览)
- Client Annotations(客户端注解)
- Special Parameters(特殊参数)
- MCP Server Boot Starter(MCP 服务器启动器)
MCP 注解特殊参数(MCP Annotation Special Parameters)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-annotations-special-params.html
📌 原文版本:Java AI 2.0.1(Stable)
MCP 注解支持多种特殊参数类型,这些参数为注解方法提供额外的上下文和功能。这些参数由框架自动注入,并且不会出现在 JSON schema 生成中。
白话解读
"特殊参数"是 Java AI 为 MCP 工具/资源/提示方法准备的"魔法参数"——你只要把它们声明在方法签名里,框架就会自动注入合适的实例。它们不算工具的输入字段,所以不会出现在客户端看到的 JSON schema 里。
特殊参数类型总览
| 类型 | 用途 | 注入时机 |
|---|---|---|
MetaProvider 类引用 | 声明 tool/prompt/resource 的 _meta 字段 | 启动时(注解字段) |
McpMeta 参数 | 读取本次请求里的 _meta | 每次调用 |
@McpProgressToken String | 接收进度令牌 | 每次调用 |
McpSyncRequestContext / McpAsyncRequestContext | 统一接口:日志/进度/采样/引导/根目录 | 每次调用 |
McpSyncServerExchange / McpAsyncServerExchange | 旧版低层上下文(仅有状态) | 每次调用 |
McpTransportContext | 无状态场景下的轻量级上下文 | 每次调用 |
CallToolRequest | 拿到完整请求参数(用于动态 schema) | 每次调用 |
MetaProvider
MetaProvider 接口为 tool、prompt 和 resource 声明中的 _meta 字段提供数据。
概述
- 通过在
@McpTool(metaProvider = …)、@McpPrompt(metaProvider = …)或@McpResource(metaProvider = …)中引用某个类来实现 - 允许在启动时将静态或计算得出的元数据附加到 tool/prompt/resource 规范上
- 默认的
DefaultMetaProvider返回空 map(不附加_meta)
自定义 MetaProvider
public class MyToolMetaProvider implements MetaProvider {
@Override
public Map<String, Object> getMeta() {
return Map.of(
"version", "1.0",
"team", "platform",
"experimental", false
);
}
}
@McpTool(name = "my-tool",
description = "Tool with metadata",
metaProvider = MyToolMetaProvider.class) // 引用上面的实现类
public String myTool(@McpToolParam(description = "Input") String input) {
return "Processed: " + input;
}同样的模式也适用于 @McpPrompt 和 @McpResource。
McpMeta
McpMeta 类提供对 MCP 请求、通知和结果中元数据的访问。
概述
- 当作为方法参数使用时自动注入
- 不计入参数数量限制,也不参与 JSON schema 生成
- 通过
get(String key)方法方便地访问元数据 - 如果请求中没有元数据,则注入一个空的
McpMeta对象(不会为 null)
在 Tools 中使用
@McpTool(name = "contextual-tool", description = "Tool with metadata access")
public String processWithContext(
@McpToolParam(description = "Input data", required = true) String data,
McpMeta meta) { // 自动注入
// 从 _meta 里取业务相关字段
String userId = (String) meta.get("userId");
String sessionId = (String) meta.get("sessionId");
String userRole = (String) meta.get("userRole");
if ("admin".equals(userRole)) {
return processAsAdmin(data, userId);
} else {
return processAsUser(data, userId);
}
}在 Resources 中使用
@McpResource(uri = "secure-data://{id}", name = "Secure Data")
public ReadResourceResult getSecureData(String id, McpMeta meta) {
String requestingUser = (String) meta.get("requestingUser");
String accessLevel = (String) meta.get("accessLevel");
// 根据元数据做权限校验
if (!"admin".equals(accessLevel)) {
return ReadResourceResult.builder(List.of(
TextResourceContents.builder("secure-data://" + id,
"Access denied").mimeType("text/plain").build()
)).build();
}
String data = loadSecureData(id);
return ReadResourceResult.builder(List.of(
TextResourceContents.builder("secure-data://" + id,
data).mimeType("text/plain").build()
)).build();
}在 Prompts 中使用
@McpPrompt(name = "localized-prompt", description = "Localized prompt generation")
public GetPromptResult localizedPrompt(
@McpArg(name = "topic", required = true) String topic,
McpMeta meta) { // 同样可以注入
String language = (String) meta.get("language");
String region = (String) meta.get("region");
String message = generateLocalizedMessage(topic, language, region);
return GetPromptResult.builder(List.of(new PromptMessage(Role.ASSISTANT, TextContent.builder(message).build())))
.description("Localized Prompt")
.build();
}@McpProgressToken
@McpProgressToken 注解用于标记一个参数,以接收来自 MCP 请求的进度令牌。
概述
- 参数类型应为
String - 自动接收请求中的进度令牌值
- 不计入生成的 JSON schema
- 如果不存在进度令牌,则注入
null——务必做空判断 - 用于跟踪长时间运行的操作
在 Tools 中使用
@McpTool(name = "long-operation", description = "Long-running operation with progress")
public String performLongOperation(
@McpProgressToken String progressToken, // 进度令牌(可能为 null)
@McpToolParam(description = "Operation name", required = true) String operation,
@McpToolParam(description = "Duration in seconds", required = true) int duration,
McpSyncServerExchange exchange) {
if (progressToken != null) {
// 起始上报
exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
.total(1.0).message("Starting " + operation).build());
// 每秒上报一次进度
for (int i = 1; i <= duration; i++) {
Thread.sleep(1000);
double progress = (double) i / duration;
exchange.progressNotification(ProgressNotification.builder(progressToken, progress)
.total(1.0).message(String.format("Processing... %d%%", (int)(progress * 100))).build());
}
}
return "Operation " + operation + " completed";
}在 Resources 中使用
@McpResource(uri = "large-file://{path}", name = "Large File Resource")
public ReadResourceResult getLargeFile(
@McpProgressToken String progressToken,
String path,
McpSyncServerExchange exchange) {
File file = new File(path);
long fileSize = file.length();
if (progressToken != null) {
// 报告"开始读"进度(按字节计)
exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
.total(fileSize).message("Reading file").build());
}
String content = readFileWithProgress(file, progressToken, exchange);
if (progressToken != null) {
// 报告"读完成"
exchange.progressNotification(ProgressNotification.builder(progressToken, fileSize)
.total(fileSize).message("File read complete").build());
}
return ReadResourceResult.builder(List.of(
TextResourceContents.builder("large-file://" + path, content).mimeType("text/plain").build()
)).build();
}McpSyncRequestContext / McpAsyncRequestContext
请求上下文对象提供对 MCP 请求信息和服务端操作的统一访问。
概述
- 为有状态和无状态操作提供统一接口
- 作为参数使用时自动注入
- 不计入 JSON schema 生成
- 支持日志、进度通知、采样(sampling)、引导(elicitation)和根目录(roots)访问等高级功能
- 同时支持有状态(server exchange)和无状态(transport context)模式
上下文 Getter
McpSyncRequestContext 和 McpAsyncRequestContext 都暴露以下只读上下文:
| 方法 | 描述 |
|---|---|
request() | 原始 MCP 请求(如 CallToolRequest、ReadResourceRequest)。使用 request().progressToken() 访问进度令牌。 |
exchange() | 底层 server exchange(McpSyncServerExchange / McpAsyncServerExchange)。仅在有状态模式可用;无状态模式下为 null。 |
sessionId() | 当前会话标识符。 |
clientInfo() | 客户端实现信息(Implementation)。 |
clientCapabilities() | 客户端声明的能力。 |
requestMeta() | 来自请求 _meta 字段的元数据 map。当已经使用上下文对象时,优先使用此方法而非注入 McpMeta。 |
transportContext() | 传输层上下文(McpTransportContext)。 |
McpSyncRequestContext 功能
public record UserInfo(String name, String email, int age) {}
@McpTool(name = "advanced-tool", description = "Tool with full server capabilities")
public String advancedTool(
McpSyncRequestContext context,
@McpToolParam(description = "Input", required = true) String input) {
// 发送日志通知
context.info("Processing: " + input);
// 给客户端 ping
context.ping();
// 进度上报(百分比形式)
context.progress(50); // 50% complete
// 用 elicitation 前先检查能力
if (context.elicitEnabled()) {
// 向用户索取结构化信息
StructuredElicitResult<UserInfo> elicitResult = context.elicit(
e -> e.message("Need additional information"),
UserInfo.class
);
if (elicitResult.action() == ElicitResult.Action.ACCEPT) {
UserInfo userInfo = elicitResult.structuredContent();
// 使用用户信息
}
}
// 用 sampling 前先检查能力
if (context.sampleEnabled()) {
// 请求 LLM 采样
CreateMessageResult samplingResult = context.sample(
s -> s.message("Process: " + input)
.modelPreferences(pref -> pref.modelHints("gpt-4"))
);
}
// 访问客户端根目录(仅在有状态模式下可用)
if (context.rootsEnabled()) {
ListRootsResult roots = context.roots();
roots.roots().forEach(root -> context.info("Client root: " + root.uri()));
}
return "Processed with advanced features";
}McpAsyncRequestContext 功能
public record UserInfo(String name, String email, int age) {}
@McpTool(name = "async-advanced-tool", description = "Async tool with server capabilities")
public Mono<String> asyncAdvancedTool(
McpAsyncRequestContext context,
@McpToolParam(description = "Input", required = true) String input) {
return context.info("Async processing: " + input) // 链式调用
.then(context.progress(25))
.then(context.ping())
.flatMap(v -> {
// 能力检查后再做 elicitation
if (context.elicitEnabled()) {
return context.elicitation(UserInfo.class)
.map(userInfo -> "Processing for user: " + userInfo.name());
}
return Mono.just("Processing...");
})
.flatMap(msg -> {
// 能力检查后再做 sampling
if (context.sampleEnabled()) {
return context.sampling("Process: " + input)
.map(result -> "Completed: " + result);
}
return Mono.just("Completed: " + msg);
});
}McpTransportContext
用于无状态操作的轻量级上下文。
概述
- 在无完整 server exchange 的情况下提供最小上下文
- 用于无状态实现
- 作为参数使用时自动注入
- 不计入 JSON schema 生成
使用示例
@McpTool(name = "stateless-tool", description = "Stateless tool with context")
public String statelessTool(
McpTransportContext context, // 仅传输层上下文
@McpToolParam(description = "Input", required = true) String input) {
// 受限的上下文访问(只能拿到传输层信息)
return "Processed in stateless mode: " + input;
}
@McpResource(uri = "stateless://{id}", name = "Stateless Resource")
public ReadResourceResult statelessResource(
McpTransportContext context,
String id) {
String data = loadData(id);
return ReadResourceResult.builder(List.of(
TextResourceContents.builder("stateless://" + id, data).mimeType("text/plain").build()
)).build();
}CallToolRequest
适用于需要访问带动态 schema的完整请求的 tools 的特殊参数。
概述
- 提供对完整 tool 请求的访问
- 支持运行时的动态 schema 处理
- 自动注入且不计入 schema 生成
- 适用于能够适应不同输入 schema 的灵活工具
使用示例
@McpTool(name = "dynamic-tool", description = "Tool with dynamic schema support")
public CallToolResult processDynamicSchema(CallToolRequest request) {
Map<String, Object> args = request.arguments(); // 原始 JSON 参数
// 按运行时 schema 处理
StringBuilder result = new StringBuilder("Processed:\n");
for (Map.Entry<String, Object> entry : args.entrySet()) {
result.append(" ").append(entry.getKey())
.append(": ").append(entry.getValue()).append("\n");
}
return CallToolResult.builder()
.addTextContent(result.toString())
.build();
}混合参数(已知 + 动态)
@McpTool(name = "hybrid-tool", description = "Tool with typed and dynamic parameters")
public String processHybrid(
@McpToolParam(description = "Operation", required = true) String operation,
@McpToolParam(description = "Priority", required = false) Integer priority,
CallToolRequest request) { // 同时拿到动态部分
// 使用强类型参数处理已知字段
String result = "Operation: " + operation;
if (priority != null) {
result += " (Priority: " + priority + ")";
}
// 取剩余的"未知"参数
Map<String, Object> allArgs = request.arguments();
Map<String, Object> additionalArgs = new HashMap<>(allArgs);
additionalArgs.remove("operation"); // 去掉已知字段
additionalArgs.remove("priority");
if (!additionalArgs.isEmpty()) {
result += " with " + additionalArgs.size() + " additional parameters";
}
return result;
}配合进度令牌
@McpTool(name = "flexible-with-progress", description = "Flexible tool with progress")
public CallToolResult flexibleWithProgress(
@McpProgressToken String progressToken,
CallToolRequest request,
McpSyncServerExchange exchange) {
Map<String, Object> args = request.arguments();
if (progressToken != null) {
exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
.total(1.0).message("Processing dynamic request").build());
}
String result = processDynamicArgs(args);
if (progressToken != null) {
exchange.progressNotification(ProgressNotification.builder(progressToken, 1.0)
.total(1.0).message("Complete").build());
}
return CallToolResult.builder()
.addTextContent(result)
.build();
}参数注入规则
自动注入
以下参数由框架自动注入:
McpMeta—— 来自请求_meta字段的元数据@McpProgressToken String—— 进度令牌(如果可用)McpSyncRequestContext/McpAsyncRequestContext—— 统一请求上下文(推荐)McpSyncServerExchange/McpAsyncServerExchange—— 低层 server exchange 上下文(仅限有状态)McpTransportContext—— 用于无状态操作的传输上下文CallToolRequest—— 完整的 tool 请求,用于动态 schema(仅限 tools)
Schema 生成
特殊参数不参与 JSON schema 生成:
- 它们不会出现在 tool 的输入 schema 中
- 它们不计入参数数量限制
- 它们对 MCP 客户端不可见
Null 处理
McpMeta—— 永不为 null,无元数据时为空对象@McpProgressToken—— 如果未提供令牌,可能为 null- Server exchanges —— 正确配置时永不为 null
CallToolRequest—— 对于 tool 方法永不为 null
最佳实践
使用 McpMeta 获取上下文
@McpTool(name = "context-aware", description = "Context-aware tool")
public String contextAware(
@McpToolParam(description = "Data", required = true) String data,
McpMeta meta) {
// 元数据里的字段一定要做 null 检查
String userId = (String) meta.get("userId");
if (userId == null) {
userId = "anonymous";
}
return processForUser(data, userId);
}进度令牌 Null 检查
@McpTool(name = "safe-progress", description = "Safe progress handling")
public String safeProgress(
@McpProgressToken String progressToken,
@McpToolParam(description = "Task", required = true) String task,
McpSyncServerExchange exchange) {
// 始终判断令牌是否非空
if (progressToken != null) {
exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
.total(1.0).message("Starting").build());
}
// 执行工作……
if (progressToken != null) {
exchange.progressNotification(ProgressNotification.builder(progressToken, 1.0)
.total(1.0).message("Complete").build());
}
return "Task completed";
}选择合适的上下文
- 使用
McpSyncRequestContext/McpAsyncRequestContext获得统一的请求上下文访问,支持有状态和无状态操作,并提供便捷的辅助方法 - 当你只需要传输层上下文时,对简单的无状态操作使用
McpTransportContext - 对于最简单的情况,完全省略上下文参数
能力检查
在使用客户端功能之前,始终检查其能力支持:
@McpTool(name = "capability-aware", description = "Tool that checks capabilities")
public String capabilityAware(
McpSyncRequestContext context,
@McpToolParam(description = "Data", required = true) String data) {
// elicitation 前判断
if (context.elicitEnabled()) {
var result = context.elicit(UserInfo.class);
// 处理 result……
}
// sampling 前判断
if (context.sampleEnabled()) {
var samplingResult = context.sample("Process: " + data);
// 处理 samplingResult……
}
// 注意:无状态服务器不支持双向操作(roots/elicitation/sampling),
// 这些检查会返回 false
return "Processed with capability awareness";
}附加资源
- MCP Annotations Overview(MCP 注解总览)
- Server Annotations(服务器注解)
- Client Annotations(客户端注解)
- Examples(注解示例)
MCP 注解示例(MCP Annotation Examples)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-annotations-examples.html
📌 原文版本:Java AI 2.0.1(Stable)
本页面提供在 Java AI 应用中使用 MCP(Model Context Protocol,模型上下文协议)注解的综合示例。从最基础的计算器服务器,到支持采样/引导/进度的复杂客户端,再到多 LLM 提供商协同的完整流程——下面这些例子基本覆盖了注解式 MCP 开发的常见模式。
白话解读
"示例"页面的特点是代码量大、细节多。建议边读边把项目拉下来跑一遍:很多 MCP 的隐式行为(比如过滤规则、能力开关)光看文档看不出来,跑一遍才发现"原来如此"。
一、完整应用示例
1.1 简单计算器服务器(Simple Calculator Server)
这是一个完整的 MCP 服务器示例,提供一组计算器工具:
@SpringBootApplication
public class CalculatorServerApplication {
public static void main(String[] args) {
SpringApplication.run(CalculatorServerApplication.class, args);
}
}
@Component
public class CalculatorTools {
// 加法工具
@McpTool(name = "add", description = "Add two numbers")
public double add(
@McpToolParam(description = "First number", required = true) double a,
@McpToolParam(description = "Second number", required = true) double b) {
return a + b;
}
// 减法工具
@McpTool(name = "subtract", description = "Subtract two numbers")
public double subtract(
@McpToolParam(description = "First number", required = true) double a,
@McpToolParam(description = "Second number", required = true) double b) {
return a - b;
}
// 乘法工具
@McpTool(name = "multiply", description = "Multiply two numbers")
public double multiply(
@McpToolParam(description = "First number", required = true) double a,
@McpToolParam(description = "Second number", required = true) double b) {
return a * b;
}
// 除法工具(含除零检查)
@McpTool(name = "divide", description = "Divide two numbers")
public double divide(
@McpToolParam(description = "Dividend", required = true) double dividend,
@McpToolParam(description = "Divisor", required = true) double divisor) {
if (divisor == 0) {
// 抛 RuntimeException → 转成错误 CallToolResult 发回模型
throw new IllegalArgumentException("Division by zero");
}
return dividend / divisor;
}
// 复杂数学表达式计算工具(带日志上下文)
@McpTool(name = "calculate-expression",
description = "Calculate a complex mathematical expression")
public CallToolResult calculateExpression(
CallToolRequest request, // 动态 schema
McpSyncRequestContext context) { // 请求上下文
Map<String, Object> args = request.arguments();
String expression = (String) args.get("expression");
// 用便捷方法记日志
context.info("Calculating: " + expression);
try {
double result = evaluateExpression(expression);
return CallToolResult.builder()
.addTextContent("Result: " + result)
.build();
} catch (Exception e) {
return CallToolResult.builder()
.isError(true) // 标记为错误结果
.addTextContent("Error: " + e.getMessage())
.build();
}
}
}配置(application.yml):
spring:
ai:
mcp:
server:
name: calculator-server
version: 1.0.0
type: SYNC
protocol: SSE # 可选: STDIO, STREAMABLE
capabilities:
tool: true
resource: true
prompt: true
completion: true1.2 文档处理服务器(Document Processing Server)
文档处理服务器示例,包含资源(Resources) 和 提示(Prompts):
@Component
public class DocumentServer {
// 文档存储(使用 ConcurrentHashMap 保证线程安全)
private final Map<String, Document> documents = new ConcurrentHashMap<>();
// 资源访问 - 根据 URI 模板获取文档
@McpResource(
uri = "document://{id}",
name = "Document",
description = "Access stored documents")
public ReadResourceResult getDocument(String id, McpMeta meta) {
Document doc = documents.get(id);
// 文档不存在
if (doc == null) {
return ReadResourceResult.builder(List.of(
TextResourceContents.builder("document://" + id,
"Document not found").mimeType("text/plain").build()
)).build();
}
// 从元数据中检查访问权限
String accessLevel = (String) meta.get("accessLevel");
if ("restricted".equals(doc.getClassification()) &&
!"admin".equals(accessLevel)) {
return ReadResourceResult.builder(List.of(
TextResourceContents.builder("document://" + id,
"Access denied").mimeType("text/plain").build()
)).build();
}
// 返回文档内容
return ReadResourceResult.builder(List.of(
TextResourceContents.builder("document://" + id,
doc.getContent()).mimeType(doc.getMimeType()).build()
)).build();
}
// 文档分析工具(带进度通知)
@McpTool(name = "analyze-document",
description = "Analyze document content")
public String analyzeDocument(
McpSyncRequestContext context,
@McpToolParam(description = "Document ID", required = true) String docId,
@McpToolParam(description = "Analysis type", required = false) String type) {
Document doc = documents.get(docId);
if (doc == null) {
return "Document not found";
}
// 从上下文访问进度令牌
String progressToken = context.request().progressToken();
if (progressToken != null) {
context.progress(p -> p.progress(0.0).total(1.0).message("Starting analysis"));
}
String analysisType = type != null ? type : "summary";
String result = performAnalysis(doc, analysisType);
if (progressToken != null) {
context.progress(p -> p.progress(1.0).total(1.0).message("Analysis complete"));
}
return result;
}
// 文档摘要提示模板
@McpPrompt(
name = "document-summary",
description = "Generate document summary prompt")
public GetPromptResult documentSummaryPrompt(
@McpArg(name = "docId", required = true) String docId,
@McpArg(name = "length", required = false) String length) {
Document doc = documents.get(docId);
if (doc == null) {
return GetPromptResult.builder(List.of(new PromptMessage(Role.SYSTEM,
TextContent.builder("Document not found").build())))
.description("Error")
.build();
}
String promptText = String.format(
"Please summarize the following document in %s:\n\n%s",
length != null ? length : "a few paragraphs",
doc.getContent()
);
return GetPromptResult.builder(List.of(new PromptMessage(Role.USER, TextContent.builder(promptText).build())))
.description("Document Summary")
.build();
}
// 自动补全功能
@McpComplete(prompt = "document-summary")
public List<String> completeDocumentId(String prefix) {
return documents.keySet().stream()
.filter(id -> id.startsWith(prefix))
.sorted()
.limit(10)
.toList();
}
}1.3 包含各种处理器的 MCP 客户端
@SpringBootApplication
public class McpClientApplication {
public static void main(String[] args) {
SpringApplication.run(McpClientApplication.class, args);
}
}
@Component
public class ClientHandlers {
private final Logger logger = LoggerFactory.getLogger(ClientHandlers.class);
private final ProgressTracker progressTracker = new ProgressTracker();
private final ChatModel chatModel;
public ClientHandlers(@Lazy ChatModel chatModel) {
this.chatModel = chatModel;
}
// 日志通知处理器
@McpLogging(clients = "server1")
public void handleLogging(LoggingMessageNotification notification) {
switch (notification.level()) {
case ERROR:
logger.error("[MCP] {} - {}", notification.logger(), notification.data());
break;
case WARNING:
logger.warn("[MCP] {} - {}", notification.logger(), notification.data());
break;
case INFO:
logger.info("[MCP] {} - {}", notification.logger(), notification.data());
break;
default:
logger.debug("[MCP] {} - {}", notification.logger(), notification.data());
}
}
// 采样处理器 - 使用 Spring AI ChatModel 进行 LLM 调用
@McpSampling(clients = "server1")
public CreateMessageResult handleSampling(CreateMessageRequest request) {
// 把 MCP 消息转换为 Spring AI 消息格式
List<Message> messages = request.messages().stream()
.map(msg -> {
if (msg.role() == Role.USER) {
return new UserMessage(((TextContent) msg.content()).text());
} else {
return AssistantMessage.builder()
.content(((TextContent) msg.content()).text())
.build();
}
})
.toList();
// 用本地 ChatModel 真正调用一次 LLM
ChatResponse response = chatModel.call(new Prompt(messages));
return CreateMessageResult.builder(Role.ASSISTANT,
response.getResult().getOutput().getText(),
request.modelPreferences().hints().get(0).name())
.build();
}
// 引导用户输入处理器
@McpElicitation(clients = "server1")
public ElicitResult handleElicitation(ElicitRequest request) {
Map<String, Object> userData = new HashMap<>();
logger.info("Elicitation requested: {}", request.message());
Map<String, Object> schema = request.requestedSchema();
if (schema != null && schema.containsKey("properties")) {
Map<String, Object> properties = (Map<String, Object>) schema.get("properties");
// 这里只是给字段填默认值;真实应用里每个字段都要问用户
properties.forEach((key, value) -> {
userData.put(key, getDefaultValueForProperty(key, value));
});
}
return new ElicitResult(ElicitResult.Action.ACCEPT, userData);
}
// 进度通知处理器
@McpProgress(clients = "server1")
public void handleProgress(ProgressNotification notification) {
progressTracker.update(
notification.progressToken(),
notification.progress(),
notification.total(),
notification.message()
);
broadcastProgress(notification); // 推送到 UI / WebSocket
}
// 工具列表变更处理器
@McpToolListChanged(clients = "server1")
public void handleServer1ToolsChanged(List<McpSchema.Tool> tools) {
logger.info("Server1 tools updated: {} tools available", tools.size());
toolRegistry.updateServerTools("server1", tools);
eventBus.publish(new ToolsUpdatedEvent("server1", tools));
}
// 资源列表变更处理器
@McpResourceListChanged(clients = "server1")
public void handleServer1ResourcesChanged(List<McpSchema.Resource> resources) {
logger.info("Server1 resources updated: {} resources available", resources.size());
// 清除该服务器的资源缓存
resourceCache.clearServer("server1");
// 注册新资源
resources.forEach(resource ->
resourceCache.register("server1", resource));
}
}客户端配置:
spring:
ai:
mcp:
client:
type: SYNC
initialized: true
request-timeout: 30s
annotation-scanner:
enabled: true
sse:
connections:
server1:
url: http://localhost:8080
stdio:
connections:
local-tool:
command: /usr/local/bin/mcp-tool
args:
- --mode=production二、异步示例(Async Examples)
2.1 异步工具服务器
@Component
public class AsyncDataProcessor {
// 异步获取数据工具
@McpTool(name = "fetch-data", description = "Fetch data from external source")
public Mono<DataResult> fetchData(
@McpToolParam(description = "Data source URL", required = true) String url,
@McpToolParam(description = "Timeout in seconds", required = false) Integer timeout) {
Duration timeoutDuration = Duration.ofSeconds(timeout != null ? timeout : 30);
return WebClient.create()
.get()
.uri(url)
.retrieve()
.bodyToMono(String.class)
.map(data -> new DataResult(url, data, System.currentTimeMillis()))
.timeout(timeoutDuration)
.onErrorReturn(new DataResult(url, "Error fetching data", 0L));
}
// 流式数据处理工具(带进度通知)
@McpTool(name = "process-stream", description = "Process data stream")
public Flux<String> processStream(
McpAsyncRequestContext context, // 异步上下文
@McpToolParam(description = "Item count", required = true) int count) {
String progressToken = context.request().progressToken();
return Flux.range(1, count)
.delayElements(Duration.ofMillis(100))
.flatMap(i -> {
if (progressToken != null) {
double progress = (double) i / count;
// 上报进度后再 yield 当前项
return context.progress(p -> p.progress(progress).total(1.0).message("Processing item " + i))
.thenReturn("Processed item " + i);
}
return Mono.just("Processed item " + i);
});
}
// 异步资源访问
@McpResource(uri = "async-data://{id}", name = "Async Data")
public Mono<ReadResourceResult> getAsyncData(String id) {
return Mono.fromCallable(() -> loadDataAsync(id))
.subscribeOn(Schedulers.boundedElastic())
.map(data -> ReadResourceResult.builder(List.of(
TextResourceContents.builder("async-data://" + id,
data).mimeType("application/json").build()
)).build());
}
}2.2 异步客户端处理器
@Component
public class AsyncClientHandlers {
// 异步采样处理
@McpSampling(clients = "async-server")
public Mono<CreateMessageResult> handleAsyncSampling(CreateMessageRequest request) {
return Mono.fromCallable(() -> {
String prompt = extractPrompt(request);
return prompt;
})
.flatMap(prompt -> callLLMAsync(prompt))
.map(response -> CreateMessageResult.builder(Role.ASSISTANT, response, "gpt-4")
.build())
.timeout(Duration.ofSeconds(30));
}
// 异步进度处理
@McpProgress(clients = "async-server")
public Mono<Void> handleAsyncProgress(ProgressNotification notification) {
return Mono.fromRunnable(() -> updateProgressAsync(notification))
.then(broadcastProgressAsync(notification))
.subscribeOn(Schedulers.parallel());
}
// 异步引导用户输入
@McpElicitation(clients = "async-server")
public Mono<ElicitResult> handleAsyncElicitation(ElicitRequest request) {
return showUserDialogAsync(request)
.map(userData -> {
if (userData != null && !userData.isEmpty()) {
return new ElicitResult(ElicitResult.Action.ACCEPT, userData);
} else {
return new ElicitResult(ElicitResult.Action.DECLINE, null);
}
})
.timeout(Duration.ofMinutes(5))
.onErrorReturn(new ElicitResult(ElicitResult.Action.CANCEL, null));
}
}三、无状态服务器示例(Stateless Server Examples)
@Component
public class StatelessTools {
// 简单无状态文本格式化工具
@McpTool(name = "format-text", description = "Format text")
public String formatText(
@McpToolParam(description = "Text to format", required = true) String text,
@McpToolParam(description = "Format type", required = true) String format) {
return switch (format.toLowerCase()) {
case "uppercase" -> text.toUpperCase();
case "lowercase" -> text.toLowerCase();
case "title" -> toTitleCase(text);
case "reverse" -> new StringBuilder(text).reverse().toString();
default -> text;
};
}
// 带传输上下文的 JSON 验证工具
@McpTool(name = "validate-json", description = "Validate JSON")
public CallToolResult validateJson(
McpTransportContext context, // 仅传输层
@McpToolParam(description = "JSON string", required = true) String json) {
try {
JsonMapper mapper = new JsonMapper();
mapper.readTree(json);
return CallToolResult.builder()
.addTextContent("Valid JSON")
.structuredContent(Map.of("valid", true)) // 结构化输出
.build();
} catch (JacksonException e) {
return CallToolResult.builder()
.addTextContent("Invalid JSON: " + e.getMessage())
.structuredContent(Map.of("valid", false, "error", e.getMessage()))
.build();
}
}
// 静态资源访问
@McpResource(uri = "static://{path}", name = "Static Resource")
public String getStaticResource(String path) {
return loadStaticContent(path);
}
// 模板提示
@McpPrompt(name = "template", description = "Template prompt")
public GetPromptResult templatePrompt(
@McpArg(name = "template", required = true) String templateName,
@McpArg(name = "variables", required = false) String variables) {
String template = loadTemplate(templateName);
if (variables != null) {
template = substituteVariables(template, variables);
}
return GetPromptResult.builder(List.of(new PromptMessage(Role.USER, TextContent.builder(template).build())))
.description("Template: " + templateName)
.build();
}
}四、多 LLM 提供商的 MCP 采样示例
本示例演示了如何使用 MCP 采样从多个 LLM 提供商生成创意内容——展示了服务器端和客户端的注解方法。
关键能力
多模型采样:服务器使用模型提示(model hints)从多个 LLM 提供商请求内容。本例中服务端拿到天气数据 → 让客户端分别用 OpenAI 和 Anthropic 各写一首诗 → 把结果汇总。
4.1 采样服务器实现
服务器提供一个天气工具,使用 MCP 采样从不同 LLM 提供商生成诗歌:
关于上下文选择
此示例直接使用 McpSyncServerExchange 进行细粒度的低层 MCP API 控制。对于简单情况,请使用 McpSyncRequestContext,它提供更高级、更便捷的接口(例如 context.sampleEnabled()、context.sample(…)、context.info(…))。
@Service
public class WeatherService {
private final RestClient restClient = RestClient.create();
// 天气响应数据记录
public record WeatherResponse(Current current) {
public record Current(LocalDateTime time, int interval, double temperature_2m) {
}
}
@McpTool(description = "Get the temperature (in celsius) for a specific location")
public String getTemperature2(McpSyncServerExchange exchange, // 旧版低层 API
@McpToolParam(description = "The location latitude") double latitude,
@McpToolParam(description = "The location longitude") double longitude) {
// 1) 调真实天气 API
WeatherResponse weatherResponse = restClient
.get()
.uri("https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}¤t=temperature_2m",
latitude, longitude)
.retrieve()
.body(WeatherResponse.class);
StringBuilder openAiWeatherPoem = new StringBuilder();
StringBuilder anthropicWeatherPoem = new StringBuilder();
// 2) 发日志通知(Start)
exchange.loggingNotification(LoggingMessageNotification.builder(LoggingLevel.INFO, "Start sampling")
.build());
// 3) 确认客户端支持 sampling,再发起请求
if (exchange.getClientCapabilities().sampling() != null) {
var samplingMessages = List.of(McpSchema.SamplingMessage.builder(McpSchema.Role.USER,
McpSchema.TextContent.builder(
"Please write a poem about this weather forecast (temperature is in Celsius). Use markdown format :\n "
+ new JsonHelper().toJson(weatherResponse)).build()).build());
var messageRequestBuilder = McpSchema.CreateMessageRequest.builder(samplingMessages, 500)
.systemPrompt("You are a poet!");
// 4a) 让 OpenAI 写诗
var openAiLlmMessageRequest = messageRequestBuilder
.modelPreferences(ModelPreferences.builder().addHint("openai").build())
.build();
CreateMessageResult openAiLlmResponse = exchange.createMessage(openAiLlmMessageRequest);
openAiWeatherPoem.append(((McpSchema.TextContent) openAiLlmResponse.content()).text());
// 4b) 让 Anthropic 写诗
var anthropicLlmMessageRequest = messageRequestBuilder
.modelPreferences(ModelPreferences.builder().addHint("anthropic").build())
.build();
CreateMessageResult anthropicAiLlmResponse = exchange.createMessage(anthropicLlmMessageRequest);
anthropicWeatherPoem.append(((McpSchema.TextContent) anthropicAiLlmResponse.content()).text());
}
// 5) 发日志通知(Finish)
exchange.loggingNotification(LoggingMessageNotification.builder(LoggingLevel.INFO, "Finish Sampling")
.build());
// 6) 合并两首诗 + 原始天气数据
String responseWithPoems = "OpenAI poem about the weather: " + openAiWeatherPoem.toString() + "\n\n" +
"Anthropic poem about the weather: " + anthropicWeatherPoem.toString() + "\n"
+ new JsonHelper().toJson(weatherResponse);
return responseWithPoems;
}
}4.2 采样客户端实现
客户端根据模型提示将采样请求路由到相应的 LLM 提供商:
@Service
public class McpClientHandlers {
private static final Logger logger = LoggerFactory.getLogger(McpClientHandlers.class);
@Autowired
Map<String, ChatClient> chatClients; // key 一般是 "openai"、"anthropic" 等
// 进度通知处理
@McpProgress(clients = "server1")
public void progressHandler(ProgressNotification progressNotification) {
logger.info("MCP PROGRESS: [{}] progress: {} total: {} message: {}",
progressNotification.progressToken(), progressNotification.progress(),
progressNotification.total(), progressNotification.message());
}
// 日志通知处理
@McpLogging(clients = "server1")
public void loggingHandler(LoggingMessageNotification loggingMessage) {
logger.info("MCP LOGGING: [{}] {}", loggingMessage.level(), loggingMessage.data());
}
// 采样请求处理 - 根据模型提示路由
@McpSampling(clients = "server1")
public CreateMessageResult samplingHandler(CreateMessageRequest llmRequest) {
logger.info("MCP SAMPLING: {}", llmRequest);
// 抽取用户提示和模型提示
var userPrompt = ((McpSchema.TextContent) llmRequest.messages().get(0).content()).text();
String modelHint = llmRequest.modelPreferences().hints().get(0).name();
// 找到名字里带 modelHint 的 ChatClient
ChatClient hintedChatClient = chatClients.entrySet().stream()
.filter(e -> e.getKey().contains(modelHint))
.findFirst()
.orElseThrow()
.getValue();
String response = hintedChatClient.prompt()
.system(llmRequest.systemPrompt())
.user(userPrompt)
.call()
.content();
return CreateMessageResult.builder(Role.ASSISTANT, response, modelHint)
.build();
}
}4.3 客户端应用启动
@SpringBootApplication
public class McpClientApplication {
public static void main(String[] args) {
SpringApplication.run(McpClientApplication.class, args).close();
}
@Bean
public CommandLineRunner predefinedQuestions(OpenAiChatModel openAiChatModel,
ToolCallbackProvider mcpToolProvider) {
return args -> {
ChatClient chatClient = ChatClient.builder(openAiChatModel)
.defaultTools(mcpToolProvider)
.build();
String userQuestion = """
What is the weather in Amsterdam right now?
Please incorporate all creative responses from all LLM providers.
After the other providers add a poem that synthesizes the poems from all the other providers.
""";
System.out.println("> USER: " + userQuestion);
System.out.println("> ASSISTANT: " + chatClient.prompt(userQuestion).call().content());
};
}
}4.4 配置
服务器配置(application.properties):
spring.ai.mcp.server.name=mcp-sampling-server-annotations
spring.ai.mcp.server.version=0.0.1
spring.ai.mcp.server.protocol=STREAMABLE
spring.main.banner-mode=off客户端配置(application.properties):
spring.application.name=mcp
spring.main.web-application-type=none
# 禁用默认 ChatClient 自动配置以支持多个模型
spring.ai.chat.client.enabled=false
# API 密钥
spring.ai.openai.api-key=${OPENAI_API_KEY}
spring.ai.anthropic.api-key=${ANTHROPIC_API_KEY}
# 使用 stateless-http 传输的 MCP 客户端连接
spring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:8080
# 禁用工具回调以防止循环依赖
spring.ai.mcp.client.toolcallback.enabled=false4.5 关键功能演示
- 多模型采样:服务器使用模型提示从多个 LLM 提供商请求内容
- 基于注解的处理器:客户端使用
@McpSampling、@McpLogging和@McpProgress注解 - 无状态 HTTP 传输:使用 streamable 协议进行通信
- 创意内容生成:从不同模型生成关于天气数据的诗歌
- 统一响应处理:将多个提供商的响应合并为单个结果
4.6 示例输出
> USER: What is the weather in Amsterdam right now?
Please incorporate all creative responses from all LLM providers.
After the other providers add a poem that synthesizes the poems from all the other providers.
> ASSISTANT:
OpenAI poem about the weather:
**Amsterdam's Winter Whisper**
*Temperature: 4.2°C*
In Amsterdam's embrace, where canals reflect the sky,
A gentle chill of 4.2 degrees drifts by...
Anthropic poem about the weather:
**Canal-Side Contemplation**
*Current conditions: 4.2°C*
Along the waterways where bicycles rest,
The winter air puts Amsterdam to test...
Weather Data:
{
"current": {
"time": "2025-01-23T11:00",
"interval": 900,
"temperature_2m": 4.2
}
}五、与 Java AI 集成
将 MCP 工具与 Java AI 的函数调用集成:
@RestController
@RequestMapping("/chat")
public class ChatController {
private final ChatModel chatModel;
private final SyncMcpToolCallbackProvider toolCallbackProvider;
public ChatController(ChatModel chatModel,
SyncMcpToolCallbackProvider toolCallbackProvider) {
this.chatModel = chatModel;
this.toolCallbackProvider = toolCallbackProvider;
}
@PostMapping
public ChatResponse chat(@RequestBody ChatRequest request) {
// 把 MCP 工具拿成 Spring AI 函数回调
ToolCallback[] mcpTools = toolCallbackProvider.getToolCallbacks();
// 用 MCP 工具构造 prompt
Prompt prompt = new Prompt(
request.getMessage(),
ChatOptionsBuilder.builder()
.withTools(mcpTools)
.build()
);
// 真正调用一次 chat model
return chatModel.call(prompt);
}
}
@Component
public class WeatherTools {
@McpTool(name = "get-weather", description = "Get current weather")
public WeatherInfo getWeather(
@McpToolParam(description = "City name", required = true) String city,
@McpToolParam(description = "Units (metric/imperial)", required = false) String units) {
String unit = units != null ? units : "metric";
return weatherService.getCurrentWeather(city, unit);
}
@McpTool(name = "get-forecast", description = "Get weather forecast")
public ForecastInfo getForecast(
@McpToolParam(description = "City name", required = true) String city,
@McpToolParam(description = "Days (1-7)", required = false) Integer days) {
int forecastDays = days != null ? days : 3;
return weatherService.getForecast(city, forecastDays);
}
}六、相关资源
- MCP Annotations Overview(MCP 注解总览)
- Server Annotations(服务器注解参考)
- Client Annotations(客户端注解参考)
- Special Parameters(特殊参数参考)
- GitHub 上的 Java AI MCP 示例
MCP 工具类(MCP Utilities)
📌 原文地址:https://docs.spring.io/java-ai/reference/api/mcp/mcp-helpers.html
📌 原文版本:Java AI 2.0.1(Stable)
MCP 工具类为将 Model Context Protocol 与 Java AI 应用集成提供基础支持。这些工具类使 Java AI 的工具系统与 MCP 服务器之间的通信无缝衔接,支持同步和异步操作。它们通常用于编程式的 MCP 客户端和服务器配置与交互。如需更简化的配置,可考虑使用 Boot Starters。
白话解读
如果说 Boot Starter 是"配置驱动"的接入方式,那这一节讲的就是"代码驱动"的接入方式——你手动 new 出 callback、callback provider、utility 类,把 MCP 工具接到自己写的 pipeline 里。适合需要精细控制、不想被自动配置"指手画脚"的场景。
Tool Callback Adapter(工具回调适配器)
将 MCP 工具适配为 Java AI 的 ToolCallback 接口,同时支持同步和异步执行。
Sync(同步)
McpSyncClient mcpClient = // 获取 MCP 客户端
Tool mcpTool = // 获取 MCP 工具定义
// 把 MCP 工具包成 Spring AI 的 ToolCallback
ToolCallback callback = new SyncMcpToolCallback(mcpClient, mcpTool);
// 通过 Spring AI 接口使用工具
ToolDefinition definition = callback.getToolDefinition();
String result = callback.call("{\"param\": \"value\"}");Async(异步)
McpAsyncClient mcpClient = // 获取 MCP 客户端
Tool mcpTool = // 获取 MCP 工具定义
ToolCallback callback = new AsyncMcpToolCallback(mcpClient, mcpTool);
ToolDefinition definition = callback.getToolDefinition();
String result = callback.call("{\"param\": \"value\"}");Tool Callback Providers(工具回调提供者)
从 MCP 客户端发现并提供 MCP 工具。
Sync(同步)
McpSyncClient mcpClient = // 获取 MCP 客户端
ToolCallbackProvider provider = new SyncMcpToolCallbackProvider(mcpClient);
// 获取所有可用工具
ToolCallback[] tools = provider.getToolCallbacks();针对多个客户端:
List<McpSyncClient> clients = // 客户端列表
List<ToolCallback> callbacks = SyncMcpToolCallbackProvider.syncToolCallbacks(clients);动态选择客户端子集:
@Autowired
private List<McpSyncClient> mcpSyncClients;
public ToolCallbackProvider buildProvider(Set<String> allowedServerNames) {
// 按 server.name() 过滤
List<McpSyncClient> selected = mcpSyncClients.stream()
.filter(c -> allowedServerNames.contains(c.getServerInfo().name()))
.toList();
return new SyncMcpToolCallbackProvider(selected);
}Async(异步)
McpAsyncClient mcpClient = // 获取 MCP 客户端
ToolCallbackProvider provider = new AsyncMcpToolCallbackProvider(mcpClient);
ToolCallback[] tools = provider.getToolCallbacks();针对多个客户端:
List<McpAsyncClient> clients = // 异步客户端列表
Flux<ToolCallback> callbacks = AsyncMcpToolCallbackProvider.asyncToolCallbacks(clients);McpToolUtils
McpToolUtils 提供双向转换:Java AI 工具回调 ↔ MCP 工具规格,MCP 客户端 → Java AI 工具回调。
ToolCallbacks 转 ToolSpecifications
将 Java AI 工具回调转换为 MCP 工具规格。
Sync(同步)
List<ToolCallback> toolCallbacks = // 获取工具回调
// 转成同步工具规格列表
List<SyncToolSpecifications> syncToolSpecs = McpToolUtils.toSyncToolSpecifications(toolCallbacks);随后可以使用 McpServer.SyncSpecification 来注册工具规格:
McpServer.SyncSpecification syncSpec = ...
syncSpec.tools(syncToolSpecs); // 把工具规格挂到同步 specificationAsync(异步)
List<ToolCallback> toolCallbacks = // 获取工具回调
List<AsyncToolSpecification> asyncToolSpecifications = McpToolUtils.toAsyncToolSpecifications(toolCallbacks);随后可以使用 McpServer.AsyncSpecification 来注册工具规格:
McpServer.AsyncSpecification asyncSpec = ...
asyncSpec.tools(asyncToolSpecifications);MCP Clients 转 ToolCallbacks
从 MCP 客户端直接获取工具回调。
Sync(同步)
List<McpSyncClient> syncClients = // 同步客户端列表
List<ToolCallback> syncCallbacks = McpToolUtils.getToolCallbacksFromSyncClients(syncClients);Async(异步)
List<McpAsyncClient> asyncClients = // 异步客户端列表
List<ToolCallback> asyncCallbacks = McpToolUtils.getToolCallbacksFromAsyncClients(asyncClients);一句话总结转换关系
McpToolUtils.toSyncToolSpecifications/toAsyncToolSpecifications:Java AI → MCPMcpToolUtils.getToolCallbacksFromSyncClients/getToolCallbacksFromAsyncClients:MCP → Java AI
Native Image Support(原生镜像支持)
GraalVM 原生镜像
McpHints 类为 MCP schema 类提供 GraalVM 原生镜像提示(hints)。当构建原生镜像时,该类会自动注册 MCP schema 类所需的所有反射提示(reflection hints)。
如果你用 Java AI + MCP 跑 GraalVM 原生镜像,直接依赖 McpHints 即可,不用自己写 reflect-config.json 之类的反射配置——schema 类会被自动识别并通过 GraalVM 的代理初始化阶段正确注册。
附加资源
- MCP Overview(MCP 概览)
- MCP Client Boot Starter(MCP 客户端启动器)
- MCP Server Boot Starter(MCP 服务器启动器)
- MCP Annotations Overview(MCP 注解总览)