Lzh on GitHub

MCP 注解特殊参数

MCP 注解支持多种特殊参数类型,这些参数为带注解的方法提供额外的上下文与功能。它们由框架自动注入,并且不会包含在 JSON 架构(schema)生成中。

MCP 注解支持多种特殊参数类型,这些参数为带注解的方法提供额外的上下文与功能。它们由框架自动注入,并且不会包含在 JSON 架构(schema)生成中。

特殊参数类型

McpMeta

McpMeta 类可从 MCP 请求、通知和结果中获取元数据。

概览

  • 在方法参数中声明时会自动注入
  • 不计入参数数量限制,也不会出现在 JSON Schema 生成中
  • 可通过 get(String key) 方法方便地访问元数据
  • 如果请求中没有元数据,会注入一个空的 McpMeta 对象

在工具(Tools)中的使用

@McpTool(name = "contextual-tool", description = "Tool with metadata access")
public String processWithContext(
        @McpToolParam(description = "Input data", required = true) String data,
        McpMeta meta) {

    // 从请求中获取元数据
    String userId = (String) meta.get("userId");
    String sessionId = (String) meta.get("sessionId");
    String userRole = (String) meta.get("userRole");

    // 根据元数据定制行为
    if ("admin".equals(userRole)) {
        return processAsAdmin(data, userId);
    } else {
        return processAsUser(data, userId);
    }
}

在资源(Resources)中的使用

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

    // 使用元数据进行权限检查
    if (!"admin".equals(accessLevel)) {
        return new ReadResourceResult(List.of(
            new TextResourceContents("secure-data://" + id,
                "text/plain", "Access denied")
        ));
    }

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

在提示(Prompts)中的使用

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

    // 根据元数据生成本地化内容
    String message = generateLocalizedMessage(topic, language, region);

    return new GetPromptResult("Localized Prompt",
        List.of(new PromptMessage(Role.ASSISTANT, new TextContent(message)))
    );
}

@McpProgressToken

@McpProgressToken 注解用于让参数接收来自 MCP 请求的进度令牌(progress token)。

概览

  • 参数类型必须为 String
  • 框架会自动将请求中的进度令牌注入该参数
  • 不会包含在生成的 JSON Schema 中
  • 如果请求没有携带进度令牌,则注入 null
  • 常用于 跟踪长耗时操作的进度

在工具(Tools)中的使用示例

@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) {
        // 发送初始进度
        exchange.progressNotification(new ProgressNotification(
            progressToken, 0.0, 1.0, "Starting " + operation));

        // 模拟执行任务并更新进度
        for (int i = 1; i <= duration; i++) {
            Thread.sleep(1000);
            double progress = (double) i / duration;

            exchange.progressNotification(new ProgressNotification(
                progressToken, progress, 1.0,
                String.format("Processing... %d%%", (int)(progress * 100))));
        }
    }

    return "Operation " + operation + " completed";
}

在资源(Resources)中的使用示例

@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) {
        // 跟踪文件读取进度
        exchange.progressNotification(new ProgressNotification(
            progressToken, 0.0, fileSize, "Reading file"));
    }

    String content = readFileWithProgress(file, progressToken, exchange);

    if (progressToken != null) {
        exchange.progressNotification(new ProgressNotification(
            progressToken, fileSize, fileSize, "File read complete"));
    }

    return new ReadResourceResult(List.of(
        new TextResourceContents("large-file://" + path, "text/plain", content)
    ));
}

McpSyncRequestContext / McpAsyncRequestContext

请求上下文对象(Request Context)为 MCP 请求信息和服务器端功能提供统一的访问方式。

概览

  • 有状态(stateful)无状态(stateless) 操作提供统一接口
  • 当作为方法参数时将自动注入
  • 不会包含在 JSON Schema 生成中
  • 支持高级功能:日志通知、进度通知、采样(sampling)、引导式提问(elicitation)
  • 兼容 stateful(使用 server exchange)与 stateless(使用 transport context)模式

McpSyncRequestContext 功能示例

public record UserInfo(String name, String email, int age) {}

@McpTool(name = "advanced-tool", description = "具备完整服务器能力的工具")
public String advancedTool(
        McpSyncRequestContext context,
        @McpToolParam(description = "输入内容", required = true) String input) {

    // 发送日志通知
    context.info("Processing: " + input);

    // Ping 客户端
    context.ping();

    // 发送进度更新
    context.progress(50); // 已完成 50%

    // 使用引导式提问(elicitation)前先检查是否支持
    if (context.elicitEnabled()) {
        // 向用户请求额外信息
        StructuredElicitResult<UserInfo> elicitResult = context.elicit(
            e -> e.message("需要额外信息"),
            UserInfo.class
        );

        if (elicitResult.action() == ElicitResult.Action.ACCEPT) {
            UserInfo userInfo = elicitResult.structuredContent();
            // 使用用户提供的信息
        }
    }

    // 使用 LLM 采样(sampling)前先检查是否支持
    if (context.sampleEnabled()) {
        // 请求 LLM 采样
        CreateMessageResult samplingResult = context.sample(
            s -> s.message("Process: " + input)
                .modelPreferences(pref -> pref.modelHints("gpt-4"))
        );
    }

    return "已使用高级功能处理完成";
}

McpAsyncRequestContext 功能示例(异步)

public record UserInfo(String name, String email, int age) {}

@McpTool(name = "async-advanced-tool", description = "具备服务器能力的异步工具")
public Mono<String> asyncAdvancedTool(
        McpAsyncRequestContext context,
        @McpToolParam(description = "输入内容", required = true) String input) {

    return context.info("Async processing: " + input)
        .then(context.progress(25))
        .then(context.ping())
        .flatMap(v -> {
            // 如果支持引导式提问(elicitation)
            if (context.elicitEnabled()) {
                return context.elicitation(UserInfo.class)
                    .map(userInfo -> "Processing for user: " + userInfo.name());
            }
            return Mono.just("Processing...");
        })
        .flatMap(msg -> {
            // 如果支持 LLM 采样(sampling)
            if (context.sampleEnabled()) {
                return context.sampling("Process: " + input)
                    .map(result -> "Completed: " + result);
            }
            return Mono.just("Completed: " + msg);
        });
}

McpTransportContext

轻量级上下文(Lightweight Context)用于无状态操作

概览

  • 提供最小化的上下文,不包含完整的服务器交互能力
  • 用于 无状态(stateless) 的实现
  • 作为方法参数时会自动注入
  • 不会被包含在 JSON Schema 生成中

使用示例

@McpTool(name = "stateless-tool", description = "带上下文的无状态工具")
public String statelessTool(
        McpTransportContext context,
        @McpToolParam(description = "输入内容", required = true) String input) {

    // 有限的上下文访问
    // 适用于传输层级的操作

    return "以无状态模式处理: " + input;
}

@McpResource(uri = "stateless://{id}", name = "Stateless Resource")
public ReadResourceResult statelessResource(
        McpTransportContext context,
        String id) {

    // 如有需要,可使用传输层上下文
    String data = loadData(id);

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

CallToolRequest

用于处理动态 Schema 的特殊参数(CallToolRequest)

概览

  • 为需要访问 完整请求体 的工具方法提供入口
  • 支持在运行时处理 动态输入 Schema
  • 作为方法参数时会自动注入
  • 不会 被包含进生成的 JSON Schema
  • 适用于需要根据不同输入结构动态适配的灵活工具

使用示例

@McpTool(name = "dynamic-tool", description = "支持动态 Schema 的工具")
public CallToolResult processDynamicSchema(CallToolRequest request) {
    Map<String, Object> args = request.arguments();

    // 按运行时提供的任意 schema 进行处理
    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();
}

混合(静态 + 动态)参数示例

@McpTool(name = "hybrid-tool", description = "同时使用静态和动态参数的工具")
public String processHybrid(
        @McpToolParam(description = "操作类型", required = true) String operation,
        @McpToolParam(description = "优先级", required = false) Integer priority,
        CallToolRequest request) {

    // 已知字段使用静态类型参数
    String result = "Operation: " + operation;
    if (priority != null) {
        result += " (Priority: " + priority + ")";
    }

    // 获取所有动态参数
    Map<String, Object> allArgs = request.arguments();

    // 移除已知字段 → 仅剩额外动态字段
    Map<String, Object> additionalArgs = new HashMap<>(allArgs);
    additionalArgs.remove("operation");
    additionalArgs.remove("priority");

    if (!additionalArgs.isEmpty()) {
        result += ",包含 " + additionalArgs.size() + " 个额外参数";
    }

    return result;
}

结合进度令牌(Progress Token)示例

@McpTool(name = "flexible-with-progress", description = "带进度的灵活动态工具")
public CallToolResult flexibleWithProgress(
        @McpProgressToken String progressToken,
        CallToolRequest request,
        McpSyncServerExchange exchange) {

    Map<String, Object> args = request.arguments();

    if (progressToken != null) {
        exchange.progressNotification(new ProgressNotification(
            progressToken, 0.0, 1.0, "Processing dynamic request"));
    }

    // 处理动态参数
    String result = processDynamicArgs(args);

    if (progressToken != null) {
        exchange.progressNotification(new ProgressNotification(
            progressToken, 1.0, 1.0, "Complete"));
    }

    return CallToolResult.builder()
        .addTextContent(result)
        .build();
}

参数注入规则

自动注入

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

  • McpMeta —— 来自请求的元数据
  • @McpProgressToken String —— 若存在,则提供进度标识
  • McpSyncServerExchange / McpAsyncServerExchange —— 服务器交换上下文
  • McpTransportContext —— 无状态操作的传输上下文
  • CallToolRequest —— 用于动态模式(schema)的完整工具请求

架构(Schema)生成

特殊参数不会参与 JSON 模式(schema)生成:

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

空值处理

  • McpMeta —— 永不为 null;如果没有元数据,则为一个空对象
  • @McpProgressToken —— 如果未提供进度令牌,可能为 null
  • Server exchanges(服务器交换上下文) —— 在正确配置的情况下永不为 null
  • CallToolRequest —— 对于工具方法来说永不为 null

最佳实践

使用 McpMeta 处理上下文

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

对进度令牌(Progress Token)进行空值检查

@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(new ProgressNotification(
            progressToken, 0.0, 1.0, "Starting"));
    }

    // Perform work...

    if (progressToken != null) {
        exchange.progressNotification(new ProgressNotification(
            progressToken, 1.0, 1.0, "Complete"));
    }

    return "Task completed";
}

选择合适的上下文类型

  • 当需要统一访问请求上下文,并希望同时支持有状态与无状态操作时,请使用 McpSyncRequestContext / McpAsyncRequestContext,它们提供了便捷的辅助方法。
  • 当只需要传输层级的上下文、适用于简单的无状态操作时,请使用 McpTransportContext
  • 在最简单的场景中,可以完全省略上下文参数。

进行功能能力检查

在使用客户端功能前,务必先检查能力支持情况:

@McpTool(name = "capability-aware", description = "具备能力检查的工具")
public String capabilityAware(
        McpSyncRequestContext context,
        @McpToolParam(description = "数据", required = true) String data) {

    // 使用引导式提问(elicitation)前先检查是否支持
    if (context.elicitEnabled()) {
        // 安全使用 elicitation
        var result = context.elicit(UserInfo.class);
        // 处理结果...
    }

    // 使用采样(sampling)前先检查是否支持
    if (context.sampleEnabled()) {
        // 安全使用 sampling
        var samplingResult = context.sample("Process: " + data);
        // 处理结果...
    }

    // 注意:无状态服务器不支持双向操作
    // (roots、elicitation、sampling),这些检查会返回 false

    return "已在能力感知模式下处理完成";
}

其他资源