Spring AI

参考文档

模型上下文协议(MCP)

MCP 注解

服务端注解

MCP 服务端注解

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


服务端注解

@McpTool

@McpTool 注解将方法标记为 MCP 工具实现,并支持自动生成 JSON Schema。

基本用法
@Component
public class CalculatorTools {

    @McpTool(name = "add", description = "将两个数字相加")
    public int add(
            @McpToolParam(description = "第一个数字", required = true) int a,
            @McpToolParam(description = "第二个数字", required = true) int b) {
        return a + b;
    }
}
注解属性

@McpTool 注解支持以下属性:

属性 默认值 描述
name 方法名 工具标识符。如果未提供,则默认为方法名。
description 方法名 工具的人类可读描述。
title "" 用于 UI 和终端用户上下文——优化为人类可读。如果未提供,则使用 name 作为显示名称。(优先级:annotations.title > title > name
generateOutputSchema false 如果为 true,则为非基本返回类型自动生成 JSON 输出 Schema。
annotations @McpAnnotations 给客户端的额外提示(参见下面的工具注解)。
metaProvider DefaultMetaProvider.class 实现 MetaProvider 的类,为工具声明中的 _meta 字段提供数据。
工具注解(提示)
@McpTool(name = "calculate-area",
         description = "计算矩形的面积",
         title = "矩形面积计算器",
         generateOutputSchema = true,
         annotations = @McpTool.McpAnnotations(
             title = "矩形面积计算器",
             readOnlyHint = true,
             destructiveHint = false,
             idempotentHint = true
         ))
public AreaResult calculateRectangleArea(
        @McpToolParam(description = "宽度", required = true) double width,
        @McpToolParam(description = "高度", required = true) double height) {

    return new AreaResult(width * height, "平方单位");
}

McpAnnotations 嵌套注解为客户端提供提示:

提示 默认值 描述
title "" 工具的人类可读标题。
readOnlyHint false 如果为 true,该工具不会修改其环境。
destructiveHint true 如果为 true,该工具可能执行破坏性更新(仅当 readOnlyHint == false 时才有意义)。
idempotentHint false 如果为 true,使用相同参数调用不会产生额外影响(仅当 readOnlyHint == false 时才有意义)。
openWorldHint true 如果为 true,该工具可能与外部实体交互(例如网络搜索)。如果为 false,则域是封闭的。
使用请求上下文

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

@McpTool(name = "process-data", description = "使用请求上下文处理数据")
public String processData(
        McpSyncRequestContext context,
        @McpToolParam(description = "要处理的数据", required = true) String data) {

    // 发送日志通知
    context.info("正在处理数据:" + data);

    // 发送进度通知(使用便捷方法)
    context.progress(p -> p.progress(0.5).total(1.0).message("正在处理..."));

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

    return "已处理:" + data.toUpperCase();
}
动态 Schema 支持

工具可以接受 CallToolRequest 以支持运行时 Schema 处理:

@McpTool(name = "flexible-tool", description = "处理动态 Schema")
public CallToolResult processDynamic(CallToolRequest request) {
    Map<String, Object> args = request.arguments();

    // 基于运行时 Schema 进行处理
    String result = "动态处理了 " + args.size() + " 个参数";

    return CallToolResult.builder()
        .addTextContent(result)
        .build();
}
进度跟踪

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

@McpTool(name = "long-task", description = "带进度的长时间运行任务")
public String performLongTask(
        McpSyncRequestContext context,
        @McpToolParam(description = "任务名称", required = true) String taskName) {

    // 从上下文访问进度令牌
    String progressToken = context.request().progressToken();

    if (progressToken != null) {
        context.progress(p -> p.progress(0.0).total(1.0).message("正在启动任务"));

        // 执行工作...

        context.progress(p -> p.progress(1.0).total(1.0).message("任务已完成"));
    }

    return "任务 " + taskName + " 已完成";
}

@McpResource

@McpResource 注解通过 URI 模板提供对资源的访问。

注解属性
属性 默认值 描述
uri "" 资源的 URI(或 URI 模板)。使用 {varName} 表示模板变量。
name "" 编程标识符。当缺少 title 时也用作显示名称。
title "" 可选的人类可读显示名称。
description "" 资源内容的描述。
mimeType "text/plain" 资源内容的 MIME 类型。
metaProvider DefaultMetaProvider.class 实现 MetaProvider 的类,为 _meta 字段提供数据。
annotations @McpAnnotations(…​) 用于受众、优先级和最后修改元数据的客户端注解。

资源的嵌套 McpAnnotations 支持:

属性 默认值 描述
audience {Role.USER} 描述预期的消费者(Role.USERRole.ASSISTANT 或两者)。
priority 0.5 重要性从 0.0(最低)到 1.0(最高)。值为 1.0 表示实际上为必需。
lastModified "" 资源最后修改的 ISO 8601 日期时间。
基本用法
@Component
public class ResourceProvider {

    @McpResource(
        uri = "config://{key}",
        name = "Configuration",
        title = "应用配置",
        description = "提供配置数据")
    public String getConfig(String key) {
        return configData.get(key);
    }
}
使用 ReadResourceResult
@McpResource(
    uri = "user-profile://{username}",
    name = "User Profile",
    description = "提供用户档案信息")
public ReadResourceResult getUserProfile(String username) {
    String profileData = loadUserProfile(username);

    return ReadResourceResult.builder(List.of(
        new TextResourceContents(
            "user-profile://" + username,
            "application/json",
            profileData)
    )).build();
}
使用请求上下文
@McpResource(
    uri = "data://{id}",
    name = "Data Resource",
    description = "带请求上下文的资源")
public ReadResourceResult getData(
        McpSyncRequestContext context,
        String id) {

    // 使用便捷方法发送日志通知
    context.info("正在访问资源:" + id);

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

    String data = fetchData(id);

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

@McpPrompt

@McpPrompt 注解为 AI 交互生成提示消息。

注解属性
属性 默认值 描述
name "" 提示的唯一标识符。
title "" 可选的人类可读显示名称。
description "" 可选的人类可读描述。
metaProvider DefaultMetaProvider.class 实现 MetaProvider 的类,为 _meta 字段提供数据。
基本用法
@Component
public class PromptProvider {

    @McpPrompt(
        name = "greeting",
        description = "生成问候消息")
    public GetPromptResult greeting(
            @McpArg(name = "name", description = "用户姓名", required = true)
            String name) {

        String message = "你好," + name + "!今天我能帮你什么?";

        return GetPromptResult.builder(List.of(new PromptMessage(Role.ASSISTANT, TextContent.builder(message).build())))
            .description("问候")
            .build();
    }
}
使用可选参数
@McpPrompt(
    name = "personalized-message",
    description = "生成个性化消息")
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("你好,").append(name).append("!\n\n");

    if (age != null) {
        message.append("在 ").append(age).append(" 岁这个年龄,");
        // 添加年龄特定内容
    }

    if (interests != null && !interests.isEmpty()) {
        message.append("你对 ").append(interests).append(" 的兴趣");
        // 添加兴趣特定内容
    }

    return GetPromptResult.builder(List.of(new PromptMessage(Role.ASSISTANT, TextContent.builder(message.toString()).build())))
        .description("个性化消息")
        .build();
}

@McpComplete

@McpComplete 注解为提示和资源 URI 模板提供自动补全功能。

使用 prompturi 属性——不能同时使用两者:

  • prompt——补全命名提示的参数
  • uri——补全命名资源 URI 的 URI 模板表达式
提示参数补全
@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();

    // 根据参数名称提供不同的补全
    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 标志
        )
    );
}

无状态 vs 有状态实现

统一请求上下文(推荐)

使用 McpSyncRequestContextMcpAsyncRequestContext 获得统一接口,适用于有状态和无状态操作:

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

@McpTool(name = "unified-tool", description = "带统一请求上下文的工具")
public String unifiedTool(
        McpSyncRequestContext context,
        @McpToolParam(description = "输入", required = true) String input) {

    // 访问请求和元数据
    String progressToken = context.request().progressToken();

    // 使用便捷方法进行日志记录
    context.info("正在处理:" + input);

    // 进度通知(注意:客户端应在其请求中设置进度令牌才能接收进度更新)
    context.progress(50); // 简单百分比

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

    // 在使用前检查能力
    if (context.elicitEnabled()) {
        // 请求用户输入(仅在有状态模式下)
        StructuredElicitResult<UserInfo> elicitResult = context.elicit(UserInfo.class);
        if (elicitResult.action() == ElicitResult.Action.ACCEPT) {
            // 使用引导获取的数据
        }
    }

    if (context.sampleEnabled()) {
        // 请求 LLM 采样(仅在有状态模式下)
        CreateMessageResult samplingResult = context.sample("生成响应");
        // 使用采样结果
    }

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

    return "已使用统一上下文处理";
}

简单操作(无上下文)

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

@McpTool(name = "simple-add", description = "简单加法")
public int simpleAdd(
        @McpToolParam(description = "第一个数字", required = true) int a,
        @McpToolParam(description = "第二个数字", required = true) int b) {
    return a + b;
}

轻量级无状态(使用 McpTransportContext)

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

@McpTool(name = "stateless-tool", description = "带传输上下文的无状态工具")
public String statelessTool(
        McpTransportContext context,
        @McpToolParam(description = "输入", required = true) String input) {
    // 仅访问传输级别的上下文
    // 不支持双向操作(roots、elicitation、sampling)
    return "已处理:" + input;
}

注意:无状态服务器不支持双向操作。因此,在无状态模式下使用 McpSyncRequestContextMcpAsyncRequestContext 的方法会被忽略。


按服务器类型过滤方法

MCP 注解框架会根据服务器类型和方法特征自动过滤带注解的方法。这确保只为每种服务器配置注册适当的方法。每个被过滤的方法会记录警告以帮助调试。

同步 vs 异步过滤

同步服务器

同步服务器(配置为 spring.ai.mcp.server.type=SYNC)使用同步提供者,接受:

  • 具有非反应式返回类型的方法:
    • 基本类型(intdoubleboolean
    • 对象类型(StringInteger、自定义 POJO)
    • MCP 类型(CallToolResultReadResourceResultGetPromptResultCompleteResult
    • 集合(List<String>Map<String, Object>

过滤掉具有反应式返回类型的方法:

  • Mono<T>
  • Flux<T>
  • Publisher<T>
@Component
public class SyncTools {

    @McpTool(name = "sync-tool", description = "同步工具")
    public String syncTool(String input) {
        // 此方法将在同步服务器上被注册
        return "已处理:" + input;
    }

    @McpTool(name = "async-tool", description = "异步工具")
    public Mono<String> asyncTool(String input) {
        // 此方法将在同步服务器上被过滤掉
        // 会记录警告
        return Mono.just("已处理:" + input);
    }
}
异步服务器

异步服务器(配置为 spring.ai.mcp.server.type=ASYNC)使用异步提供者,接受:

  • 具有反应式返回类型的方法:
    • Mono<T>(用于单个结果)
    • Flux<T>(用于流式结果)
    • Publisher<T>(通用反应式类型)

过滤掉具有非反应式返回类型的方法:

  • 基本类型
  • 对象类型
  • 集合
  • MCP 结果类型
@Component
public class AsyncTools {

    @McpTool(name = "async-tool", description = "异步工具")
    public Mono<String> asyncTool(String input) {
        // 此方法将在异步服务器上被注册
        return Mono.just("已处理:" + input);
    }

    @McpTool(name = "sync-tool", description = "同步工具")
    public String syncTool(String input) {
        // 此方法将在异步服务器上被过滤掉
        // 会记录警告
        return "已处理:" + input;
    }
}

有状态 vs 无状态过滤

有状态服务器

有状态服务器支持双向通信,接受具有以下特征的方法:

  • 双向上下文参数:
    • McpSyncRequestContext(用于同步操作)
    • McpAsyncRequestContext(用于异步操作)
    • McpSyncServerExchange(传统,用于同步操作)
    • McpAsyncServerExchange(传统,用于异步操作)

支持双向操作:

  • roots() - 访问根目录
  • elicit() - 请求用户输入
  • sample() - 请求 LLM 采样
@Component
public class StatefulTools {

    @McpTool(name = "interactive-tool", description = "带双向操作的工具")
    public String interactiveTool(
            McpSyncRequestContext context,
            @McpToolParam(description = "输入", required = true) String input) {

        // 此方法将在有状态服务器上被注册
        // 可以使用 elicitation、sampling、roots
        if (context.sampleEnabled()) {
            var samplingResult = context.sample("生成响应");
            // 处理采样结果...
        }

        return "已使用上下文处理";
    }
}
无状态服务器

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

  • 过滤掉具有双向上下文参数的方法:
    • 带有 McpSyncRequestContext 的方法被跳过
    • 带有 McpAsyncRequestContext 的方法被跳过
    • 带有 McpSyncServerExchange 的方法被跳过
    • 带有 McpAsyncServerExchange 的方法被跳过
  • 为每个被过滤的方法记录警告

接受具有以下特征的方法:

  • McpTransportContext(轻量级无状态上下文)
  • 完全没有上下文参数
  • 只有常规的 @McpToolParam 参数

不支持双向操作:

  • roots() - 不可用
  • elicit() - 不可用
  • sample() - 不可用
@Component
public class StatelessTools {

    @McpTool(name = "simple-tool", description = "简单的无状态工具")
    public String simpleTool(@McpToolParam(description = "输入") String input) {
        // 此方法将在无状态服务器上被注册
        return "已处理:" + input;
    }

    @McpTool(name = "context-tool", description = "带传输上下文的工具")
    public String contextTool(
            McpTransportContext context,
            @McpToolParam(description = "输入") String input) {
        // 此方法将在无状态服务器上被注册
        return "已处理:" + input;
    }

    @McpTool(name = "bidirectional-tool", description = "带双向上下文的工具")
    public String bidirectionalTool(
            McpSyncRequestContext context,
            @McpToolParam(description = "输入") String input) {
        // 此方法将在无状态服务器上被过滤掉
        // 会记录警告
        return "已使用采样处理";
    }
}

过滤总结

服务器类型 接受的方法 被过滤的方法
同步有状态 非反应式返回 + 双向上下文 反应式返回(Mono/Flux)
异步有状态 反应式返回(Mono/Flux)+ 双向上下文 非反应式返回
同步无状态 非反应式返回 + 无双向上下文 反应式返回 或 双向上下文参数
异步无状态 反应式返回(Mono/Flux)+ 无双向上下文 非反应式返回 或 双向上下文参数

方法过滤最佳实践:

  • 保持方法与服务器类型对齐——对同步服务器使用同步方法,对异步服务器使用异步方法
  • 将有状态和无状态实现分离到不同的类中以提高清晰度
  • 在启动时检查日志以查看被过滤方法的警告
  • 使用正确的上下文——有状态使用 McpSyncRequestContext/McpAsyncRequestContext,无状态使用 McpTransportContext
  • 如果同时支持有状态和无状态部署,请对两种模式进行测试

异步支持

所有服务端注解都支持使用 Reactor 的异步实现:

@Component
public class AsyncTools {

    @McpTool(name = "async-fetch", description = "异步获取数据")
    public Mono<String> asyncFetch(
            @McpToolParam(description = "URL", required = true) String url) {

        return Mono.fromCallable(() -> {
            // 模拟异步操作
            return fetchFromUrl(url);
        }).subscribeOn(Schedulers.boundedElastic());
    }

    @McpResource(uri = "async-data://{id}", name = "异步数据")
    public Mono<ReadResourceResult> asyncResource(String id) {
        return Mono.fromCallable(() -> {
            String data = loadData(id);
            return ReadResourceResult.builder(List.of(
                new TextResourceContents("async-data://" + id, "text/plain", data)
            )).build();
        }).delayElements(Duration.ofMillis(100));
    }
}

Spring Boot 集成

使用 Spring Boot 自动配置,带注解的 Bean 会被自动检测和注册:

@SpringBootApplication
public class McpServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(McpServerApplication.class, args);
    }
}

@Component
public class MyMcpTools {
    // 你的 @McpTool 注解方法
}

@Component
public class MyMcpResources {
    // 你的 @McpResource 注解方法
}

自动配置会:

  • 扫描带有 MCP 注解的 Bean
  • 创建适当的规格说明
  • 向 MCP 服务器注册它们
  • 基于配置处理同步和异步实现

配置属性

配置服务端注解扫描器:

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

其他资源

Logo

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

更多推荐