MCP 服务端注解提供了一种声明式的方式,通过 Java 注解实现 MCP 服务端功能。这些注解简化了工具、资源、提示词和补全处理器的创建。
服务端注解
@McpTool
@McpTool 注解将方法标记为 MCP 工具实现,并支持自动生成 JSON 模式。
基础用法
@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 = "calculate-area",
description = "Calculate the area of a rectangle",
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");
}携带请求上下文
工具可访问请求上下文以执行高级操作:
@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..."));
// 向客户端发送心跳
context.ping();
return "Processed: " + data.toUpperCase();
}动态模式支持
工具可接收 CallToolRequest 以实现运行时模式处理:
@McpTool(name = "flexible-tool", description = "Process dynamic schema")
public CallToolResult processDynamic(CallToolRequest request) {
Mapargs = request.arguments();
// 基于运行时模式处理
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";
}@McpResource
@McpResource 注解通过 URI 模板提供资源访问能力。
基础用法
@Component
public class ResourceProvider {
@McpResource(
uri = "config://{key}",
name = "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 new ReadResourceResult(List.of(
new TextResourceContents(
"user-profile://" + username,
"application/json",
profileData)
));
}携带请求上下文
@McpResource(
uri = "data://{id}",
name = "Data Resource",
description = "Resource with request context")
public ReadResourceResult getData(
McpSyncRequestContext context,
String id) {
// 使用便捷方法发送日志通知
context.info("Accessing resource: " + id);
// 向客户端发送心跳
context.ping();
String data = fetchData(id);
return new ReadResourceResult(List.of(
new TextResourceContents("data://" + id, "text/plain", data)
));
}@McpPrompt
@McpPrompt 注解为 AI 交互生成提示词消息。
基础用法
@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 new GetPromptResult(
"Greeting",
List.of(new PromptMessage(Role.ASSISTANT, new TextContent(message)))
);
}
}携带可选参数
@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 new GetPromptResult(
"Personalized Message",
List.of(new PromptMessage(Role.ASSISTANT, new TextContent(message.toString())))
);
}@McpComplete
@McpComplete 注解为提示词提供自动补全功能。
基础用法
@Component
public class CompletionProvider {
@McpComplete(prompt = "city-search")
public ListcompleteCityName(String prefix) {
return cities.stream()
.filter(city -> city.toLowerCase().startsWith(prefix.toLowerCase()))
.limit(10)
.toList();
}
}携带 CompleteRequest.CompleteArgument
@McpComplete(prompt = "travel-planner")
public ListcompleteTravelDestination(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
@McpComplete(prompt = "code-completion")
public CompleteResult completeCode(String prefix) {
Listcompletions = generateCodeCompletions(prefix);
return new CompleteResult(
new CompleteResult.CompleteCompletion(
completions,
completions.size(), // 总数
hasMoreCompletions // 是否有更多补全标识
)
);
}无状态与有状态实现
统一请求上下文(推荐)
使用 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); // 简单百分比
// 向客户端发送心跳
context.ping();
// 使用前检查能力是否支持
if (context.elicitEnabled()) {
// 请求用户输入(仅在有状态模式下)
StructuredElicitResultelicitResult = context.elicit(UserInfo.class);
if (elicitResult.action() == ElicitResult.Action.ACCEPT) {
// 使用诱导获取的数据
}
}
if (context.sampleEnabled()) {
// 请求大语言模型采样(仅在有状态模式下)
CreateMessageResult samplingResult = context.sample("Generate response");
// 使用采样结果
}
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) {
// 仅访问传输级上下文
// 无双向操作(根目录、诱导、采样)
return "Processed: " + input;
}无状态服务端不支持双向操作: 因此在无状态模式下,使用 McpSyncRequestContext 或 McpAsyncRequestContext 的方法会被忽略。
按服务端类型过滤方法
MCP 注解框架会根据服务端类型和方法特征自动过滤注解方法。这确保每个服务端配置仅注册合适的方法。每个被过滤的方法都会记录一条警告日志,便于调试。
同步与异步过滤
同步服务端
同步服务端(配置为 spring.ai.mcp.server.type=SYNC)使用同步提供者,规则如下:
接收非响应式返回类型的方法:
基本类型(int、double、boolean)
对象类型(String、Integer、自定义 POJO)
MCP 类型(CallToolResult、ReadResourceResult、GetPromptResult、CompleteResult)
集合(List
、Map ) 过滤响应式返回类型的方法:
Mono
Flux
Publisher
@Component
public class SyncTools {
@McpTool(name = "sync-tool", description = "Synchronous tool")
public String syncTool(String input) {
// 该方法会在同步服务端注册
return "Processed: " + input;
}
@McpTool(name = "async-tool", description = "Async tool")
public MonoasyncTool(String input) {
// 该方法会在同步服务端被过滤
// 会记录警告日志
return Mono.just("Processed: " + input);
}
}异步服务端
异步服务端(配置为 spring.ai.mcp.server.type=ASYNC)使用异步提供者,规则如下:
接收响应式返回类型的方法:
Mono
(单个结果) Flux
(流式结果) Publisher
(通用响应式类型) 过滤非响应式返回类型的方法:
基本类型
对象类型
集合
MCP 结果类型
@Component
public class AsyncTools {
@McpTool(name = "async-tool", description = "Async tool")
public MonoasyncTool(String input) {
// 该方法会在异步服务端注册
return Mono.just("Processed: " + input);
}
@McpTool(name = "sync-tool", description = "Sync tool")
public String syncTool(String input) {
// 该方法会在异步服务端被过滤
// 会记录警告日志
return "Processed: " + input;
}
}有状态与无状态过滤
有状态服务端
有状态服务端支持双向通信,接收包含以下内容的方法:
双向上下文参数:
McpSyncRequestContext(同步操作)
McpAsyncRequestContext(异步操作)
McpSyncServerExchange(旧版,同步操作)
McpAsyncServerExchange(旧版,异步操作)
支持双向操作:
roots() - 访问根目录
elicit() - 请求用户输入
sample() - 请求大语言模型采样
@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 MonoasyncFetch(
@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 MonoasyncResource(String id) {
return Mono.fromCallable(() -> {
String data = loadData(id);
return new ReadResourceResult(List.of(
new TextResourceContents("async-data://" + id, "text/plain", data)
));
}).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
创建对应的规范定义
将其注册到 MCP 服务端
根据配置自动处理同步和异步实现
配置属性
配置服务端注解扫描器:
spring: ai: mcp: server: type: SYNC # 或 ASYNC annotation-scanner: enabled: true
附加资源
MCP 注解概述
客户端注解
特殊参数
MCP 服务端启动器