此版本仍在开发中,尚未被视为稳定版。如需最新的快照版本,请使用 Spring AI 1.1.3spring-doc.cadn.net.cn

MCP 服务器注解

MCP 服务器注解提供了一种使用 Java 注解实现 MCP 服务器功能的声明式方法。 这些注解简化了工具、资源、提示和完成处理程序的创建。spring-doc.cadn.net.cn

服务器注解

@McpTool

@McpTool 注解将方法标记为 MCP 工具实现,并支持自动生成 JSON 架构。spring-doc.cadn.net.cn

基本用法

@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 注解支持以下属性:spring-doc.cadn.net.cn

属性 默认 <description> </description>

namespring-doc.cadn.net.cn

方法名称spring-doc.cadn.net.cn

工具标识符。如果未提供,则默认为方法名。spring-doc.cadn.net.cn

descriptionspring-doc.cadn.net.cn

方法名称spring-doc.cadn.net.cn

该工具的易于理解的描述。spring-doc.cadn.net.cn

titlespring-doc.cadn.net.cn

""spring-doc.cadn.net.cn

适用于 UI 和最终用户场景——优化为人类可读。如果未提供,则显示时使用 name。(优先级:annotations.title > title > namespring-doc.cadn.net.cn

generateOutputSchemaspring-doc.cadn.net.cn

falsespring-doc.cadn.net.cn

如果为 true,则自动为非原始返回类型生成 JSON 输出架构。spring-doc.cadn.net.cn

annotationsspring-doc.cadn.net.cn

@McpAnnotationsspring-doc.cadn.net.cn

给客户端的额外提示(参见下方的工具注解)。spring-doc.cadn.net.cn

metaProviderspring-doc.cadn.net.cn

DefaultMetaProvider.classspring-doc.cadn.net.cn

实现 MetaProvider 的类,用于为工具声明中的 _meta 字段提供数据。spring-doc.cadn.net.cn

工具注解(提示)

@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 嵌套注解提供客户端提示:spring-doc.cadn.net.cn

提示 默认 <description> </description>

titlespring-doc.cadn.net.cn

""spring-doc.cadn.net.cn

该工具的人类可读标题。spring-doc.cadn.net.cn

readOnlyHintspring-doc.cadn.net.cn

falsespring-doc.cadn.net.cn

如果为 true,该工具不会修改其环境。spring-doc.cadn.net.cn

destructiveHintspring-doc.cadn.net.cn

truespring-doc.cadn.net.cn

如果为 true,工具可能会执行破坏性更新(仅在值为 readOnlyHint == false 时有意义)。spring-doc.cadn.net.cn

idempotentHintspring-doc.cadn.net.cn

falsespring-doc.cadn.net.cn

如果为 true,使用相同参数调用不会产生额外效果(仅在为 readOnlyHint == false 时有意义)。spring-doc.cadn.net.cn

openWorldHintspring-doc.cadn.net.cn

truespring-doc.cadn.net.cn

如果为 true,该工具可以与外部实体(例如网络搜索)进行交互。如果为 false,则域是封闭的。spring-doc.cadn.net.cn

使用请求上下文

工具可以访问请求上下文以执行高级操作:spring-doc.cadn.net.cn

@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 用于运行时模式处理:spring-doc.cadn.net.cn

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

进度跟踪

工具可以接收进度Tokens以跟踪长时间运行的操作:spring-doc.cadn.net.cn

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

@Mcp 资源

@McpResource 注解通过 URI 模板提供对资源的访问。spring-doc.cadn.net.cn

注解属性

属性 默认 <description> </description>

urispring-doc.cadn.net.cn

""spring-doc.cadn.net.cn

资源的 URI(或 URI 模板)。对模板变量使用 {varName}spring-doc.cadn.net.cn

namespring-doc.cadn.net.cn

""spring-doc.cadn.net.cn

编程标识符。当缺少 title 时,也用作显示名称。spring-doc.cadn.net.cn

titlespring-doc.cadn.net.cn

""spring-doc.cadn.net.cn

用于显示目的的可选人类可读名称。spring-doc.cadn.net.cn

descriptionspring-doc.cadn.net.cn

""spring-doc.cadn.net.cn

该资源所代表内容的描述。spring-doc.cadn.net.cn

mimeTypespring-doc.cadn.net.cn

"text/plain"spring-doc.cadn.net.cn

资源内容的 MIME 类型。spring-doc.cadn.net.cn

metaProviderspring-doc.cadn.net.cn

DefaultMetaProvider.classspring-doc.cadn.net.cn

实现 MetaProvider 的类,用于为 _meta 字段提供数据。spring-doc.cadn.net.cn

annotationsspring-doc.cadn.net.cn

@McpAnnotations(…​)spring-doc.cadn.net.cn

用于受众、优先级和最后修改元数据的客户端注解。spring-doc.cadn.net.cn

嵌套的McpAnnotations用于资源支持:spring-doc.cadn.net.cn

属性 默认 <description> </description>

audiencespring-doc.cadn.net.cn

{Role.USER}spring-doc.cadn.net.cn

描述预期的消费者(Role.USERRole.ASSISTANT 或两者)。spring-doc.cadn.net.cn

priorityspring-doc.cadn.net.cn

0.5spring-doc.cadn.net.cn

重要性从 0.0(最低)到 1.0(最高)。值为 1.0 表示实际上为必需。spring-doc.cadn.net.cn

lastModifiedspring-doc.cadn.net.cn

""spring-doc.cadn.net.cn

资源最后修改时的 ISO 8601 日期时间。spring-doc.cadn.net.cn

基本用法

@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

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

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

    // Ping the client
    context.ping();

    String data = fetchData(id);

    return new ReadResourceResult(List.of(
        new TextResourceContents("data://" + id, "text/plain", data)
    ));
}

@McpPrompt

@McpPrompt 注解为 AI 交互生成提示消息。spring-doc.cadn.net.cn

注解属性

属性 默认 <description> </description>

namespring-doc.cadn.net.cn

""spring-doc.cadn.net.cn

提示的唯一标识符。spring-doc.cadn.net.cn

titlespring-doc.cadn.net.cn

""spring-doc.cadn.net.cn

用于显示目的的可选人类可读名称。spring-doc.cadn.net.cn

descriptionspring-doc.cadn.net.cn

""spring-doc.cadn.net.cn

可选的人类可读描述。spring-doc.cadn.net.cn

metaProviderspring-doc.cadn.net.cn

DefaultMetaProvider.classspring-doc.cadn.net.cn

实现 MetaProvider 的类,用于为 _meta 字段提供数据。spring-doc.cadn.net.cn

基本用法

@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, ");
        // Add age-specific content
    }

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

    return new GetPromptResult(
        "Personalized Message",
        List.of(new PromptMessage(Role.ASSISTANT, new TextContent(message.toString())))
    );
}

@McpComplete

@McpComplete 注解为提示和资源 URI 模板提供自动完成功能。spring-doc.cadn.net.cn

请使用 prompturi 属性——不要同时使用两者:spring-doc.cadn.net.cn

提示参数补全

@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 补全

@McpComplete(uri = "config://{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();

    // 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
        )
    );
}

无状态与有状态实现

使用 McpSyncRequestContextMcpAsyncRequestContext 以获得一个统一接口,该接口同时适用于有状态和无状态操作:spring-doc.cadn.net.cn

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

简单操作(无上下文)

对于简单操作,您可以完全省略上下文参数:spring-doc.cadn.net.cn

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

对于需要最小传输上下文的无状态操作:spring-doc.cadn.net.cn

@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;
}
无状态服务器不支持双向操作:

因此,在无状态模式下使用 McpSyncRequestContextMcpAsyncRequestContext 的方法将被忽略。spring-doc.cadn.net.cn

按服务器类型过滤方法

MCP 注解框架会根据服务器类型和方法特性自动过滤已注解的方法。这确保了仅为每个服务器配置注册适当的方法。 每个被过滤的方法都会记录一条警告信息,以帮助调试。spring-doc.cadn.net.cn

同步与异步过滤

同步服务器

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

@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)使用提供以下功能的异步提供者:spring-doc.cadn.net.cn

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

有状态与无状态过滤

有状态服务器

有状态服务器支持双向通信,并接受具有以下特征的方法:spring-doc.cadn.net.cn

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

无状态服务器

无状态服务器针对简单的请求 - 响应模式进行了优化,并且:spring-doc.cadn.net.cn

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

过滤摘要

服务器类型 接受的方法 过滤后的方法

同步有状态spring-doc.cadn.net.cn

非响应式返回 + 双向上下文spring-doc.cadn.net.cn

响应式返回(Mono/Flux)spring-doc.cadn.net.cn

异步有状态spring-doc.cadn.net.cn

响应式返回(Mono/Flux)+ 双向上下文spring-doc.cadn.net.cn

非响应式返回spring-doc.cadn.net.cn

同步无状态spring-doc.cadn.net.cn

非响应式返回 + 无双向上下文spring-doc.cadn.net.cn

响应式返回或双向上下文参数spring-doc.cadn.net.cn

异步无状态spring-doc.cadn.net.cn

响应式返回(Mono/Flux)+ 无双向上下文spring-doc.cadn.net.cn

非响应式返回或双向上下文参数spring-doc.cadn.net.cn

方法过滤的最佳实践:
  1. 保持方法与服务器类型对齐 - 同步服务器使用同步方法,异步服务器使用异步方法spring-doc.cadn.net.cn

  2. 将有状态和无状态的实现分离到不同的类中,以提高清晰度spring-doc.cadn.net.cn

  3. 检查日志 以在启动期间查看被过滤的方法警告spring-doc.cadn.net.cn

  4. 使用正确的上下文 - McpSyncRequestContext/McpAsyncRequestContext 用于有状态,McpTransportContext 用于无状态spring-doc.cadn.net.cn

  5. 测试两种模式,如果您同时支持有状态和无状态部署spring-doc.cadn.net.cn

异步支持

所有服务器端注解都支持使用 Reactor 的异步实现:spring-doc.cadn.net.cn

@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 new ReadResourceResult(List.of(
                new TextResourceContents("async-data://" + id, "text/plain", data)
            ));
        }).delayElements(Duration.ofMillis(100));
    }
}

Spring Boot 集成

借助 Spring Boot 自动配置,带注解的 Bean 会被自动检测并注册:spring-doc.cadn.net.cn

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

自动配置将:spring-doc.cadn.net.cn

  1. 扫描带有 MCP 注解的 Beanspring-doc.cadn.net.cn

  2. 创建适当的规范spring-doc.cadn.net.cn

  3. 将它们注册到 MCP 服务器spring-doc.cadn.net.cn

  4. 根据配置同时支持同步和异步实现spring-doc.cadn.net.cn

配置属性

配置服务器注解扫描器:spring-doc.cadn.net.cn

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