Skip to content
Spring AI 文档版本 2.1.0-SNAPSHOT 源快照 542f6a2f 更新于 2026-10-16

MCP 服务端注解

MCP 服务端注解提供了一种用 Java 注解实现 MCP 服务端功能的声明式方式。 这些注解简化了工具、资源、提示词和补全处理器的创建。

服务端注解

@McpTool

@McpTool 注解把一个方法标记为 MCP 工具实现,并自动生成 JSON 模式。

基本用法

java
@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,为非原始返回类型自动生成一个 JSON 输出模式。
annotations@McpAnnotations给客户端的额外提示(见下方工具注解)。
metaProviderDefaultMetaProvider.class实现 MetaProvider、为工具声明中 _meta 字段提供数据的类。

工具注解(提示)

java
@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,则该域是封闭的。

带请求上下文

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

java
@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 以做运行时模式处理:

java
@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();
}

进度跟踪

工具可以接收进度令牌以跟踪长时间运行的操作:

java
@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 会向上冒泡并使工具调用失败,因此不会被传达给模型。

换句话说,若你不想让某次失败到达模型,就在工具方法签名中声明一个受检异常并抛出它。

java
@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 会作为错误结果上报给模型,以便它恢复。

java
@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;
}

NOTE

协议级的 McpError 异常(例如用于 URL elicitation 及其他 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 日期时间。

基本用法

java
@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);
    }
}

带 ReadResourceResult

java
@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();
}

带请求上下文

java
@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 字段提供数据的类。

基本用法

java
@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();
    }
}

带可选参数

java
@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 的一个 URI 模板表达式

提示词参数补全

java
@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();
    }
}

资源 URI 补全

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

带 CompleteRequest.CompleteArgument

java
@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

java
@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 作为统一接口,它能同时配合有状态与无状态操作:

java
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";
}

简单操作(无上下文)

对于简单操作,你可以完全省略上下文参数:

java
@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)

对于需要最小传输上下文的无状态操作:

java
@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;
}

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

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

按服务端类型做方法过滤

MCP 注解框架会依据服务端类型与方法特征自动过滤带注解的方法。这确保每种服务端配置只注册合适的方法。 每个被过滤的方法都会记录一条警告以帮助调试。

同步与异步过滤

同步服务端

同步服务端(用 spring.ai.mcp.server.type=SYNC 配置)使用同步提供者,它们:

  • 接受 带非响应式返回类型的方法:

  • 原始类型(int、double、boolean)

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

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

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

  • 过滤掉 带响应式返回类型的方法:

  • Mono<T>

  • Flux<T>

  • Publisher<T>

java
@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 配置)使用异步提供者,它们:

  • 接受 带响应式返回类型的方法:

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

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

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

  • 过滤掉 带非响应式返回类型的方法:

  • 原始类型

  • 对象类型

  • 集合

  • MCP 结果类型

java
@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 采样

java
@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() - 不可用

java
@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 支持异步实现:

java
@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 会被自动检测并注册:

java
@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 注解的 bean
  2. 创建合适的规格
  3. 把它们注册到 MCP 服务端
  4. 依据配置处理同步与异步两种实现

配置属性

配置服务端注解扫描器:

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

其他资源

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。