跳到主要内容
版本:2.0.1

MCP 服务器注解

QWen 3.8 中英对照 Server Annotations MCP Server Annotations

MCP 服务器注解提供了一种声明式的方式,使用 Java 注解来实现 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;
}
}

注解属性​

@McpTool 注解支持以下属性:

属性默认值描述
name方法名工具标识符。如果未提供,则默认为方法名。
description方法名工具的人类可读描述。
title""旨在用于 UI 和终端用户上下文——优化为人类可读。如果未提供,则使用 name 进行显示。(优先级:annotations.title > title > name)
generateOutputSchemafalse如果为 true,则为非基本类型(non-primitive return types)自动生成 JSON 输出模式(schema)。
annotations@McpAnnotations供客户端使用的额外提示(参见下文中的工具注解)。
metaProviderDefaultMetaProvider.class实现 MetaProvider 接口的类,用于提供工具声明中 _meta 字段的数据。

工具注释(提示)​

@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""工具的可读标题。
readOnlyHintfalse如果为 true,该工具不会修改其环境。
destructiveHinttrue如果为 true,该工具可能会执行破坏性更新(仅在 readOnlyHint == false 时有意义)。
idempotentHintfalse如果为 true,使用相同参数调用不会产生额外影响(仅在 readOnlyHint == false 时有意义)。
openWorldHinttrue如果为 true,该工具可能会与外部实体(例如,网络搜索)进行交互。如果为 false,领域是封闭的。

带有请求上下文​

工具可以访问请求上下文以执行高级操作:

@McpTool(name = "process-data", description = "Process data with request context")
public String processData(
McpSyncRequestContext context,
@McpToolParam(description = "Data to process", required = true) String data) {

// Send logging notification
context.info("Processing data: " + data);

// Send progress notification (using convenient method)
context.progress(p -> p.progress(0.5).total(1.0).message("Processing..."));

// Ping the client
context.ping();

return "Processed: " + data.toUpperCase();
}

动态模式支持​

工具可以接受 CallToolRequest 用于运行时 schema 处理:

@McpTool(name = "flexible-tool", description = "Process dynamic schema")
public CallToolResult processDynamic(CallToolRequest request) {
Map<String, Object> args = request.arguments();

// Process based on runtime schema
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) {

// Access progress token from context
String progressToken = context.request().progressToken();

if (progressToken != null) {
context.progress(p -> p.progress(0.0).total(1.0).message("Starting task"));

// Perform work...

context.progress(p -> p.progress(1.0).total(1.0).message("Task completed"));
}

return "Task " + taskName + " completed";
}

异常处理​

由 @McpTool 方法抛出的异常遵循与常规 Spring AI @Tool 方法相同的约定。

  • RuntimeException 会被转换为一个错误类型的 CallToolResult 并传递给模型,模型可以据此进行推理或重试。
  • 声明的受检异常或 Error 会向上抛出并导致工具调用失败,因此不会被传递给模型。

换句话说,如果你不希望将失败状态传递到模型中,请在工具方法签名中声明一个受检异常,并将其抛出。

@McpTool(name = "read-file", description = "Read a file from disk")
public String readFile(@McpToolParam(description = "Path", required = true) String path) throws IOException {
// An IOException bubbles up and fails the tool call instead of being
// sent back to the model as an error result.
return Files.readString(Path.of(path));
}

相对地,RuntimeException 会作为错误结果报告给模型,使其能够进行恢复。

@McpTool(name = "divide", description = "Divide two numbers")
public double divide(int a, int b) {
if (b == 0) {
// Conveyed to the model as an error result.
throw new IllegalArgumentException("Cannot divide by zero");
}
return (double) a / b;
}
备注

协议级别的 McpError 异常(例如,用于 URL 提示及其他 MCP 协议流程的异常)始终会传播到 MCP 协议层,而不是被转换为一个表示错误的 CallToolResult。无论该 McpError 是直接抛出,还是通过响应式返回类型(Mono 或 Flux)发出,此规则均适用。

@McpResource​

@McpResource 注解通过 URI 模板提供对资源的访问。

注解属性​

属性默认值描述
uri""资源的 URI(或 URI 模板)。使用 {varName} 表示模板变量。
name""程序化标识符。当 title 缺失时,也用作显示名称。
title""用于显示目的的可选人类可读名称。
description""描述该资源所代表的内容。
mimeType"text/plain"资源内容的 MIME 类型。
metaProviderDefaultMetaProvider.class实现 MetaProvider 并为其 _meta 字段提供数据的类。
annotations@McpAnnotations(…​)客户端注解,用于指定受众、优先级和最后修改时间等元数据。

资源的嵌套 McpAnnotations 支持:

属性默认值描述
audience{Role.USER}描述预期的使用者(Role.USER、Role.ASSISTANT 或两者)。
priority0.5重要程度,从 0.0(最低)到 1.0(最高)。值为 1.0 表示实际上是必需的。
lastModified""资源最后修改时间的 ISO 8601 日期时间。

基础用法​

@Component
public class ResourceProvider {

@McpResource(
uri = "config://{key}",
name = "Configuration",
title = "App Configuration",
description = "Provides configuration data")
public String getConfig(String key) {
return configData.get(key);
}
}

With 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) {

// Send logging notification using convenient method
context.info("Accessing resource: " + id);

// Ping the client
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""可选的人类可读描述。
metaProviderDefaultMetaProvider.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, ");
// Add age-specific content
}

if (interests != null && !interests.isEmpty()) {
message.append("Your interest in ").append(interests);
// Add interest-specific content
}

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 模板表达式

参数补全提示​

@Component
public class CompletionProvider {

@McpComplete(prompt = "city-search")
public List<String> completeCityName(String prefix) {
return cities.stream()
.filter(city -> city.toLowerCase().startsWith(prefix.toLowerCase()))
.limit(10)
.toList();
}
}

Resource URI 自动补全​

@McpComplete(uri = "config://{key}")
public List<String> completeConfigKey(String prefix) {
return configKeys.stream()
.filter(key -> key.startsWith(prefix))
.limit(10)
.toList();
}

With CompleteRequest.CompleteArgument​

@McpComplete(prompt = "travel-planner")
public List<String> completeTravelDestination(CompleteRequest.CompleteArgument argument) {
String prefix = argument.value().toLowerCase();
String argumentName = argument.name();

// Different completions based on 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) {
List<String> completions = generateCodeCompletions(prefix);

return new CompleteResult(
new CompleteResult.CompleteCompletion(
completions,
completions.size(), // total
hasMoreCompletions // hasMore flag
)
);
}

无状态与有状态实现​

使用 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) {

// Access request and metadata
String progressToken = context.request().progressToken();

// Logging with convenient methods
context.info("Processing: " + input);

// Progress notifications (Note client should set a progress token
// with its request to be able to receive progress updates)
context.progress(50); // Simple percentage

// Ping client
context.ping();

// Check capabilities before using
if (context.elicitEnabled()) {
// Request user input (only in stateful mode)
StructuredElicitResult<UserInfo> elicitResult = context.elicit(UserInfo.class);
if (elicitResult.action() == ElicitResult.Action.ACCEPT) {
// Use elicited data
}
}

if (context.sampleEnabled()) {
// Request LLM sampling (only in stateful mode)
CreateMessageResult samplingResult = context.sample("Generate response");
// Use sampling result
}

// Access root directories (only in stateful mode)
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) {
// Access transport-level context only
// No bidirectional operations (roots, elicitation, sampling)
return "Processed: " + input;
}
important

无状态服务器不支持双向操作:

因此,在无状态模式下,使用 McpSyncRequestContext 或 McpAsyncRequestContext 的方法会被忽略。

按服务器类型过滤方法​

MCP 注解框架会根据服务器类型和方法特性自动过滤被注解的方法。这确保每种服务器配置仅注册合适的方法。每个被过滤的方法都会记录一条警告日志,以辅助调试。

同步 vs 异步过滤​

同步服务器​

同步服务器(配置为 spring.ai.mcp.server.type=SYNC)使用同步提供者,其特性如下:

  • 接受(Accept)具有非响应式返回类型的方法:

    • 基本类型(int、double、boolean)

    • 对象类型(String、Integer、自定义 POJO)

    • MCP 类型(CallToolResult、ReadResourceResult、GetPromptResult、CompleteResult)

    • 集合(List<String>、Map<String, Object>)

  • 过滤掉(Filter out)具有响应式返回类型的方法:

    • Mono<T>

    • Flux<T>

    • Publisher<T>

@Component
public class SyncTools {

@McpTool(name = "sync-tool", description = "Synchronous tool")
public String syncTool(String input) {
// This method WILL be registered on sync servers
return "Processed: " + input;
}

@McpTool(name = "async-tool", description = "Async tool")
public Mono<String> asyncTool(String input) {
// This method will be FILTERED OUT on sync servers
// A warning will be logged
return Mono.just("Processed: " + input);
}
}

异步服务器​

异步服务器(配置为 spring.ai.mcp.server.type=ASYNC)使用异步提供者,它们:

  • Accept 具有响应式返回类型的方法:

    • Mono<T>(用于单个结果)

    • Flux<T>(用于流式结果)

    • Publisher<T>(通用响应式类型)

  • Filter out 具有非响应式返回类型的方法:

    • 基本类型

    • 对象类型

    • 集合

    • MCP 结果类型

@Component
public class AsyncTools {

@McpTool(name = "async-tool", description = "Async tool")
public Mono<String> asyncTool(String input) {
// This method WILL be registered on async servers
return Mono.just("Processed: " + input);
}

@McpTool(name = "sync-tool", description = "Sync tool")
public String syncTool(String input) {
// This method will be FILTERED OUT on async servers
// A warning will be logged
return "Processed: " + input;
}
}

有状态过滤与无状态过滤​

有状态服务器​

有状态服务器支持双向通信,并接受具有以下特性的方法:

  • 双向上下文参数:

    • 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) {

// This method WILL be registered on stateful servers
// Can use elicitation, sampling, roots
if (context.sampleEnabled()) {
var samplingResult = context.sample("Generate response");
// Process sampling result...
}

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) {
// This method WILL be registered on stateless servers
return "Processed: " + input;
}

@McpTool(name = "context-tool", description = "Tool with transport context")
public String contextTool(
McpTransportContext context,
@McpToolParam(description = "Input") String input) {
// This method WILL be registered on stateless servers
return "Processed: " + input;
}

@McpTool(name = "bidirectional-tool", description = "Tool with bidirectional context")
public String bidirectionalTool(
McpSyncRequestContext context,
@McpToolParam(description = "Input") String input) {
// This method will be FILTERED OUT on stateless servers
// A warning will be logged
return "Processed with sampling";
}
}

过滤汇总​

服务器类型接受的方法过滤的方法
同步有状态非响应式返回 + 双向上下文响应式返回 (Mono/Flux)
异步有状态响应式返回 (Mono/Flux) + 双向上下文非响应式返回
同步无状态非响应式返回 + 无双向上下文响应式返回 或 双向上下文参数
异步无状态响应式返回 (Mono/Flux) + 无双向上下文非响应式返回 或 双向上下文参数
提示

方法过滤的最佳实践:

  1. 保持方法对齐 - 同步服务器使用同步方法,异步服务器使用异步方法

  2. 分离有状态和无状态 实现到不同的类中以提高清晰度

  3. 检查日志 - 启动期间注意被过滤方法的警告

  4. 使用正确的上下文 - 有状态使用 McpSyncRequestContext/McpAsyncRequestContext,无状态使用 McpTransportContext

  5. 测试两种模式 - 如果你同时支持有状态和无状态部署

异步支持​

所有服务器注解都支持使用 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(() -> {
// Simulate async operation
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 {
// Your @McpTool annotated methods
}

@Component
public class MyMcpResources {
// Your @McpResource annotated methods
}

自动配置将执行以下操作:

  1. 扫描带有 MCP 注解的 beans
  2. 创建相应的 specifications
  3. 将它们注册到 MCP server
  4. 根据配置处理同步和异步实现

配置属性​

配置服务器注解扫描器:

spring:
ai:
mcp:
server:
type: SYNC # or ASYNC
annotation-scanner:
enabled: true

其他资源​