Spring AI

参考文档

模型上下文协议(MCP)

MCP 注解

特殊参数

MCP 注解特殊参数

MCP注解支持几种特殊参数类型,这些类型为注解方法提供额外的上下文和功能。这些参数由框架自动注入,并且会从JSON模式生成中排除。


特殊参数类型

MetaProvider

MetaProvider接口为工具(tool)、提示(prompt)和资源(resource)声明中的_meta字段提供数据。

概述

  • 实现为一个类,在@McpTool(metaProvider = …​)@McpPrompt(metaProvider = …​)@McpResource(metaProvider = …​)中引用
  • 允许在启动时向工具/提示/资源规范附加静态或计算出的元数据
  • 默认的DefaultMetaProvider返回空映射(不附加_meta

自定义MetaProvider

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 = "带有元数据的工具",
         metaProvider = MyToolMetaProvider.class)
public String myTool(@McpToolParam(description = "输入") String input) {
    return "处理结果: " + input;
}

同样的模式也适用于@McpPrompt@McpResource


McpMeta

McpMeta类提供对MCP请求、通知和结果中元数据的访问。

概述

  • 作为方法参数使用时自动注入
  • 从参数计数限制和JSON模式生成中排除
  • 通过get(String key)方法提供便捷的元数据访问
  • 如果请求中没有元数据,则注入一个空的McpMeta对象

在工具中使用

@McpTool(name = "contextual-tool", description = "具有元数据访问能力的工具")
public String processWithContext(
        @McpToolParam(description = "输入数据", 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);
    }
}

在资源中使用

@McpResource(uri = "secure-data://{id}", name = "安全数据")
public ReadResourceResult getSecureData(String id, McpMeta meta) {

    String requestingUser = (String) meta.get("requestingUser");
    String accessLevel = (String) meta.get("accessLevel");

    // 使用元数据检查访问权限
    if (!"admin".equals(accessLevel)) {
        return ReadResourceResult.builder(List.of(
            new TextResourceContents("secure-data://" + id,
                "text/plain", "访问被拒绝")
        )).build();
    }

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

在提示中使用

@McpPrompt(name = "localized-prompt", description = "本地化提示生成")
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 GetPromptResult.builder(List.of(new PromptMessage(Role.ASSISTANT, TextContent.builder(message).build())))
        .description("本地化提示")
        .build();
}

@McpProgressToken

@McpProgressToken注解标记一个参数,用于从MCP请求接收进度令牌。

概述

  • 参数类型应为String
  • 自动从请求中接收进度令牌值
  • 从生成的JSON模式中排除
  • 如果没有提供进度令牌,则注入null
  • 用于跟踪长时间运行的操作

在工具中使用

@McpTool(name = "long-operation", description = "带进度报告的长时间运行操作")
public String performLongOperation(
        @McpProgressToken String progressToken,
        @McpToolParam(description = "操作名称", required = true) String operation,
        @McpToolParam(description = "持续时间(秒)", required = true) int duration,
        McpSyncServerExchange exchange) {

    if (progressToken != null) {
        // 发送初始进度
        exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
            .total(1.0).message("开始 " + operation).build());

        // 模拟带进度更新的工作
        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("处理中... %d%%", (int)(progress * 100))).build());
        }
    }

    return "操作 " + operation + " 已完成";
}

在资源中使用

@McpResource(uri = "large-file://{path}", name = "大文件资源")
public ReadResourceResult getLargeFile(
        @McpProgressToken String progressToken,
        String path,
        McpSyncServerExchange exchange) {

    File file = new File(path);
    long fileSize = file.length();

    if (progressToken != null) {
        // 跟踪文件读取进度
        exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
            .total(fileSize).message("正在读取文件").build());
    }

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

    if (progressToken != null) {
        exchange.progressNotification(ProgressNotification.builder(progressToken, fileSize)
            .total(fileSize).message("文件读取完成").build());
    }

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

McpSyncRequestContext / McpAsyncRequestContext

请求上下文对象提供对MCP请求信息和服务器端操作的统一访问。

概述

  • 为有状态和无状态操作提供统一接口
  • 作为参数使用时自动注入
  • 从JSON模式生成中排除
  • 支持日志记录、进度通知、采样(sampling)、启发(elicitation)和根目录(roots)访问等高级功能
  • 适用于有状态(服务器交换)和无状态(传输上下文)两种模式

上下文Getter方法
McpSyncRequestContextMcpAsyncRequestContext都暴露以下只读上下文:

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

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("处理中: " + 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();
            // 使用用户信息
        }
    }

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

    // 访问客户端根目录(仅在有状态模式下可用)
    if (context.rootsEnabled()) {
        ListRootsResult roots = context.roots();
        roots.roots().forEach(root -> context.info("客户端根目录: " + root.uri()));
    }

    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("异步处理中: " + input)
        .then(context.progress(25))
        .then(context.ping())
        .flatMap(v -> {
            // 如果支持则执行启发
            if (context.elicitEnabled()) {
                return context.elicitation(UserInfo.class)
                    .map(userInfo -> "正在为用户处理: " + userInfo.name());
            }
            return Mono.just("处理中...");
        })
        .flatMap(msg -> {
            // 如果支持则执行采样
            if (context.sampleEnabled()) {
                return context.sampling("处理: " + input)
                    .map(result -> "已完成: " + result);
            }
            return Mono.just("已完成: " + msg);
        });
}

McpTransportContext

用于无状态操作的轻量级上下文。

概述

  • 提供最小上下文,不包含完整的服务器交换
  • 用于无状态实现
  • 作为参数使用时自动注入
  • 从JSON模式生成中排除

使用示例

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

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

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

@McpResource(uri = "stateless://{id}", name = "无状态资源")
public ReadResourceResult statelessResource(
        McpTransportContext context,
        String id) {

    // 如果需要则访问传输上下文
    String data = loadData(id);

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

CallToolRequest

工具的特殊参数,需要访问完整请求并支持动态模式。

概述

  • 提供对完整工具请求的访问
  • 支持运行时动态模式处理
  • 自动注入并从模式生成中排除
  • 适用于适应不同输入模式的灵活工具

使用示例

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

    // 根据运行时提供的任何模式进行处理
    StringBuilder result = new StringBuilder("已处理:\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;
    if (priority != null) {
        result += " (优先级: " + 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;
}

配合进度令牌使用

@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(ProgressNotification.builder(progressToken, 0.0)
            .total(1.0).message("正在处理动态请求").build());
    }

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

    if (progressToken != null) {
        exchange.progressNotification(ProgressNotification.builder(progressToken, 1.0)
            .total(1.0).message("完成").build());
    }

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

参数注入规则

自动注入

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

  • McpMeta - 来自请求_meta字段的元数据
  • @McpProgressToken String - 可用的进度令牌
  • McpSyncRequestContext / McpAsyncRequestContext - 统一请求上下文(推荐)
  • McpSyncServerExchange / McpAsyncServerExchange - 底层服务器交换上下文(仅限有状态)
  • McpTransportContext - 用于无状态操作的传输上下文
  • CallToolRequest - 用于动态模式的完整工具请求(仅限工具)

模式生成

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

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

Null处理

  • McpMeta - 永不为null,没有元数据时为空对象
  • @McpProgressToken - 如果没有提供令牌,可以为null
  • 服务器交换 - 配置正确时永不为null
  • CallToolRequest - 对于工具方法永不为null

最佳实践

使用McpMeta获取上下文

@McpTool(name = "context-aware", description = "感知上下文的工具")
public String contextAware(
        @McpToolParam(description = "数据", required = true) String data,
        McpMeta meta) {

    // 始终检查元数据中的null值
    String userId = (String) meta.get("userId");
    if (userId == null) {
        userId = "anonymous";
    }

    return processForUser(data, userId);
}

进度令牌空值检查

@McpTool(name = "safe-progress", description = "安全的进度处理")
public String safeProgress(
        @McpProgressToken String progressToken,
        @McpToolParam(description = "任务", required = true) String task,
        McpSyncServerExchange exchange) {

    // 始终检查进度令牌是否可用
    if (progressToken != null) {
        exchange.progressNotification(ProgressNotification.builder(progressToken, 0.0)
            .total(1.0).message("开始").build());
    }

    // 执行工作...

    if (progressToken != null) {
        exchange.progressNotification(ProgressNotification.builder(progressToken, 1.0)
            .total(1.0).message("完成").build());
    }

    return "任务已完成";
}

选择合适的上下文

  • 使用McpSyncRequestContext / McpAsyncRequestContext统一访问请求上下文,支持有状态和无状态操作,并提供便捷的辅助方法
  • 使用McpTransportContext进行简单的无状态操作,当只需要传输层上下文时
  • 在最简单的情况下完全省略上下文参数

能力检查

在使用客户端功能之前,始终检查能力支持:

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

    // 在使用前检查是否支持启发
    if (context.elicitEnabled()) {
        // 可以安全地使用启发
        var result = context.elicit(UserInfo.class);
        // 处理结果...
    }

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

    // 注意:无状态服务器不支持双向操作
    // (根目录、启发、采样),这些检查将返回false

    return "已处理(具备能力感知)";
}

其他资源

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐