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

MCP 注解特殊参数

MCP 注解支持若干特殊参数类型,为带注解的方法提供额外的上下文与功能。 这些参数由框架自动注入,并在 JSON 模式生成中被排除。

特殊参数类型

MetaProvider

MetaProvider 接口为工具、提示词和资源声明中的 _meta 字段提供数据。

概览

  • 以在 @McpTool(metaProvider = ...)、@McpPrompt(metaProvider = ...) 或 @McpResource(metaProvider = ...) 中引用的类来实现
  • 允许在启动时把静态或计算得出的元数据附加到某个工具/提示词/资源规格
  • 默认的 DefaultMetaProvider 返回一个空 map(不追加 _meta)

自定义 MetaProvider

java
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 模式生成中排除
  • 通过 get(String key) 方法便捷地访问元数据
  • 若请求中没有元数据,会注入一个空的 McpMeta 对象

在工具中使用

java
@McpTool(name = "contextual-tool", description = "Tool with metadata access")
public String processWithContext(
        @McpToolParam(description = "Input data", required = true) String data,
        McpMeta meta) {
    
    // Access metadata from the request
    String userId = (String) meta.get("userId");
    String sessionId = (String) meta.get("sessionId");
    String userRole = (String) meta.get("userRole");
    
    // Use metadata to customize behavior
    if ("admin".equals(userRole)) {
        return processAsAdmin(data, userId);
    } else {
        return processAsUser(data, userId);
    }
}

在资源中使用

java
@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");
    
    // Check access permissions using metadata
    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();
}

在提示词中使用

java
@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");
    
    // Generate localized content based on metadata
    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 模式中排除
  • 若不存在进度令牌,则注入 null
  • 用于跟踪长时间运行的操作

在工具中使用

java
@McpTool(name = "long-operation", description = "Long-running operation with progress")
public String performLongOperation(
        @McpProgressToken String progressToken,
        @McpToolParam(description = "Operation name", required = true) String operation,
        @McpToolParam(description = "Duration in seconds", required = true) int duration,
        McpSyncServerExchange exchange) {
    
    if (progressToken != null) {
        // Send initial progress
        exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
            .total(1.0).message("Starting " + operation).build());

        // Simulate work with progress updates
        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";
}

在资源中使用

java
@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) {
        // Track file reading progress
        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 模式生成中排除
  • 启用诸如日志、进度通知、sampling、elicitation 和 roots 访问的高级特性
  • 同时适用于有状态(服务端 exchange)与无状态(传输上下文)模式

上下文获取器

McpSyncRequestContext 和 McpAsyncRequestContext 都暴露以下只读上下文:

方法描述
request()原始的 MCP 请求(例如 CallToolRequest、ReadResourceRequest)。用 request().progressToken() 访问进度令牌。
exchange()底层的服务端 exchange(McpSyncServerExchange / McpAsyncServerExchange)。仅有状态模式可用;无状态模式下为 null。
sessionId()当前的会话标识符。
clientInfo()客户端实现信息(Implementation)。
clientCapabilities()客户端声明的能力。
requestMeta()来自请求 _meta 字段的元数据 map。当你已在使用上下文对象时,优先用它而非注入 McpMeta。
transportContext()传输级的上下文(McpTransportContext)。

McpSyncRequestContext 特性

java
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) {
    
    // Send logging notification
    context.info("Processing: " + input);
    
    // Ping the client
    context.ping();
    
    // Send progress updates
    context.progress(50); // 50% complete
    
    // Check if elicitation is supported before using it
    if (context.elicitEnabled()) {
        // Request additional information from user
        StructuredElicitResult<UserInfo> elicitResult = context.elicit(
            e -> e.message("Need additional information"),
            UserInfo.class
        );
        
        if (elicitResult.action() == ElicitResult.Action.ACCEPT) {
            UserInfo userInfo = elicitResult.structuredContent();
            // Use the user information
        }
    }
    
    // Check if sampling is supported before using it
    if (context.sampleEnabled()) {
        // Request LLM sampling
        CreateMessageResult samplingResult = context.sample(
            s -> s.message("Process: " + input)
                .modelPreferences(pref -> pref.modelHints("gpt-4"))
        );
    }

    // Access client root directories (only available in stateful mode)
    if (context.rootsEnabled()) {
        ListRootsResult roots = context.roots();
        roots.roots().forEach(root -> context.info("Client root: " + root.uri()));
    }

    return "Processed with advanced features";
}

McpAsyncRequestContext 特性

java
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 -> {
            // Perform elicitation if supported
            if (context.elicitEnabled()) {
                return context.elicitation(UserInfo.class)
                    .map(userInfo -> "Processing for user: " + userInfo.name());
            }
            return Mono.just("Processing...");
        })
        .flatMap(msg -> {
            // Perform sampling if supported
            if (context.sampleEnabled()) {
                return context.sampling("Process: " + input)
                    .map(result -> "Completed: " + result);
            }
            return Mono.just("Completed: " + msg);
        });
}

McpTransportContext

供无状态操作使用的轻量上下文。

概览

  • 在不带完整服务端 exchange 的情况下提供最小上下文
  • 用于无状态实现
  • 作为参数使用时自动注入
  • 从 JSON 模式生成中排除

用法示例

java
@McpTool(name = "stateless-tool", description = "Stateless tool with context")
public String statelessTool(
        McpTransportContext context,
        @McpToolParam(description = "Input", required = true) String input) {
    
    // Limited context access
    // Useful for transport-level operations
    
    return "Processed in stateless mode: " + input;
}

@McpResource(uri = "stateless://{id}", name = "Stateless Resource")
public ReadResourceResult statelessResource(
        McpTransportContext context,
        String id) {
    
    // Access transport context if needed
    String data = loadData(id);
    
    return ReadResourceResult.builder(List.of(
        TextResourceContents.builder("stateless://" + id, data).mimeType("text/plain").build()
    )).build();
}

CallToolRequest

为需要访问带动态模式的完整请求的工具提供的特殊参数。

概览

  • 提供对完整工具请求的访问
  • 启用运行时的动态模式处理
  • 自动注入并在模式生成中被排除
  • 适用于随不同输入模式而调整、灵活多变的工具

用法示例

java
@McpTool(name = "dynamic-tool", description = "Tool with dynamic schema support")
public CallToolResult processDynamicSchema(CallToolRequest request) {
    Map<String, Object> args = request.arguments();
    
    // Process based on whatever schema was provided at runtime
    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();
}

混合参数

java
@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) {
    
    // Use typed parameters for known fields
    String result = "Operation: " + operation;
    if (priority != null) {
        result += " (Priority: " + priority + ")";
    }
    
    // Access additional dynamic arguments
    Map<String, Object> allArgs = request.arguments();
    
    // Remove known parameters to get only additional ones
    Map<String, Object> additionalArgs = new HashMap<>(allArgs);
    additionalArgs.remove("operation");
    additionalArgs.remove("priority");
    
    if (!additionalArgs.isEmpty()) {
        result += " with " + additionalArgs.size() + " additional parameters";
    }
    
    return result;
}

带进度令牌

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

    // Process dynamic arguments
    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();
}

参数注入规则

自动注入

以下参数由框架自动注入:

  1. McpMeta - 来自请求 _meta 字段的元数据
  2. @McpProgressToken String - 若可用则为进度令牌
  3. McpSyncRequestContext / McpAsyncRequestContext - 统一的请求上下文(推荐)
  4. McpSyncServerExchange / McpAsyncServerExchange - 底层的服务端 exchange 上下文(仅有状态)
  5. McpTransportContext - 供无状态操作使用的传输上下文
  6. CallToolRequest - 供动态模式使用的完整工具请求(仅工具)

模式生成

特殊参数会从 JSON 模式生成中被排除:

  • 它们不出现在工具的输入模式中
  • 它们不计入参数数量限制
  • 它们对 MCP 客户端不可见

Null 处理

  • McpMeta - 绝不为 null,无元数据时为空对象
  • @McpProgressToken - 若未提供令牌则可为 null
  • 服务端 exchange - 正确配置时绝不为 null
  • CallToolRequest - 对工具方法绝不为 null

最佳实践

用 McpMeta 获取上下文

java
@McpTool(name = "context-aware", description = "Context-aware tool")
public String contextAware(
        @McpToolParam(description = "Data", required = true) String data,
        McpMeta meta) {
    
    // Always check for null values in metadata
    String userId = (String) meta.get("userId");
    if (userId == null) {
        userId = "anonymous";
    }
    
    return processForUser(data, userId);
}

进度令牌的 null 检查

java
@McpTool(name = "safe-progress", description = "Safe progress handling")
public String safeProgress(
        @McpProgressToken String progressToken,
        @McpToolParam(description = "Task", required = true) String task,
        McpSyncServerExchange exchange) {
    
    // Always check if progress token is available
    if (progressToken != null) {
        exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
            .total(1.0).message("Starting").build());
    }

    // Perform work...

    if (progressToken != null) {
        exchange.progressNotification(ProgressNotification.builder(progressToken, 1.0)
            .total(1.0).message("Complete").build());
    }
    
    return "Task completed";
}

选择合适的上下文

  • 用 McpSyncRequestContext / McpAsyncRequestContext 统一访问请求上下文,它以便捷辅助方法同时支持有状态与无状态操作
  • 当你只需传输级上下文时,对简单的无状态操作用 McpTransportContext
  • 对最简单的情况完全省略上下文参数

能力检查

在使用客户端特性之前总是先检查能力支持:

java
@McpTool(name = "capability-aware", description = "Tool that checks capabilities")
public String capabilityAware(
        McpSyncRequestContext context,
        @McpToolParam(description = "Data", required = true) String data) {
    
    // Check if elicitation is supported before using it
    if (context.elicitEnabled()) {
        // Safe to use elicitation
        var result = context.elicit(UserInfo.class);
        // Process result...
    }
    
    // Check if sampling is supported before using it
    if (context.sampleEnabled()) {
        // Safe to use sampling
        var samplingResult = context.sample("Process: " + data);
        // Process result...
    }
    
    // Note: Stateless servers do not support bidirectional operations
    // (roots, elicitation, sampling) and will return false for these checks
    
    return "Processed with capability awareness";
}

其他资源

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