收藏本站,收获最前沿的人工智能与编程资讯!!

Spring AI ​MCP 服务端注解

技术文档 34℃ 0

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

服务端注解

@McpTool

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

基础用法

@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(name = "calculate-area",
         description = "Calculate the area of a rectangle",
         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");
}

携带请求上下文

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

@McpTool(name = "process-data", description = "Process data with request context")
public String processData(
        McpSyncRequestContext context,
        @McpToolParam(description = "Data to process", required = true) String data) {

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

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

    // 向客户端发送心跳
    context.ping();

    return "Processed: " + data.toUpperCase();
}

动态模式支持

工具可接收 CallToolRequest 以实现运行时模式处理:

@McpTool(name = "flexible-tool", description = "Process dynamic schema")
public CallToolResult processDynamic(CallToolRequest request) {
    Mapargs = request.arguments();

    // 基于运行时模式处理
    String result = "Processed " + args.size() + " arguments dynamically";

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

进度跟踪

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

@McpTool(name = "long-task", description = "Long-running task with progress")
public String performLongTask(
        McpSyncRequestContext context,
        @McpToolParam(description = "Task name", required = true) String taskName) {

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

    if (progressToken != null) {
        context.progress(p -> p.progress(0.0).total(1.0).message("Starting task"));

        // 执行任务...

        context.progress(p -> p.progress(1.0).total(1.0).message("Task completed"));
    }

    return "Task " + taskName + " completed";
}

@McpResource

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

基础用法

@Component
public class ResourceProvider {

    @McpResource(
        uri = "config://{key}",
        name = "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) {

    // 使用便捷方法发送日志通知
    context.info("Accessing resource: " + id);

    // 向客户端发送心跳
    context.ping();

    String data = fetchData(id);

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

@McpPrompt

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

基础用法

@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, ");
        // 添加年龄专属内容
    }

    if (interests != null && !interests.isEmpty()) {
        message.append("Your interest in ").append(interests);
        // 添加兴趣专属内容
    }

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

@McpComplete

@McpComplete 注解为提示词提供自动补全功能。

基础用法

@Component
public class CompletionProvider {

    @McpComplete(prompt = "city-search")
    public ListcompleteCityName(String prefix) {
        return cities.stream()
            .filter(city -> city.toLowerCase().startsWith(prefix.toLowerCase()))
            .limit(10)
            .toList();
    }
}

携带 CompleteRequest.CompleteArgument

@McpComplete(prompt = "travel-planner")
public ListcompleteTravelDestination(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) {
    Listcompletions = generateCodeCompletions(prefix);

    return new CompleteResult(
        new CompleteResult.CompleteCompletion(
            completions,
            completions.size(),  // 总数
            hasMoreCompletions   // 是否有更多补全标识
        )
    );
}

无状态与有状态实现

统一请求上下文(推荐)

使用 McpSyncRequestContext 或 McpAsyncRequestContext 可获得适用于有状态和无状态操作的统一接口:

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

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

    // 使用便捷方法记录日志
    context.info("Processing: " + input);

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

    // 向客户端发送心跳
    context.ping();

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

    if (context.sampleEnabled()) {
        // 请求大语言模型采样(仅在有状态模式下)
        CreateMessageResult samplingResult = context.sample("Generate response");
        // 使用采样结果
    }

    return "Processed with unified context";
}

简单操作(无上下文)

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

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

适用于需要最小传输上下文的无状态操作:

@McpTool(name = "stateless-tool", description = "Stateless with transport context")
public String statelessTool(
        McpTransportContext context,
        @McpToolParam(description = "Input", required = true) String input) {
    // 仅访问传输级上下文
    // 无双向操作(根目录、诱导、采样)
    return "Processed: " + input;
}

无状态服务端不支持双向操作: 因此在无状态模式下,使用 McpSyncRequestContext 或 McpAsyncRequestContext 的方法会被忽略。

按服务端类型过滤方法

MCP 注解框架会根据服务端类型和方法特征自动过滤注解方法。这确保每个服务端配置仅注册合适的方法。每个被过滤的方法都会记录一条警告日志,便于调试。

同步与异步过滤

同步服务端

同步服务端(配置为 spring.ai.mcp.server.type=SYNC)使用同步提供者,规则如下:

  • 接收非响应式返回类型的方法:

    • 基本类型(int、double、boolean)

    • 对象类型(String、Integer、自定义 POJO)

    • MCP 类型(CallToolResult、ReadResourceResult、GetPromptResult、CompleteResult)

    • 集合(List

      、Map
  • 过滤响应式返回类型的方法:

    • Mono

    • Flux

    • Publisher

@Component
public class SyncTools {

    @McpTool(name = "sync-tool", description = "Synchronous tool")
    public String syncTool(String input) {
        // 该方法会在同步服务端注册
        return "Processed: " + input;
    }

    @McpTool(name = "async-tool", description = "Async tool")
    public MonoasyncTool(String input) {
        // 该方法会在同步服务端被过滤
        // 会记录警告日志
        return Mono.just("Processed: " + input);
    }
}

异步服务端

异步服务端(配置为 spring.ai.mcp.server.type=ASYNC)使用异步提供者,规则如下:

  • 接收响应式返回类型的方法:

    • Mono

      (单个结果)
    • Flux

      (流式结果)
    • Publisher

      (通用响应式类型)
  • 过滤非响应式返回类型的方法:

    • 基本类型

    • 对象类型

    • 集合

    • MCP 结果类型

@Component
public class AsyncTools {

    @McpTool(name = "async-tool", description = "Async tool")
    public MonoasyncTool(String input) {
        // 该方法会在异步服务端注册
        return Mono.just("Processed: " + input);
    }

    @McpTool(name = "sync-tool", description = "Sync tool")
    public String syncTool(String input) {
        // 该方法会在异步服务端被过滤
        // 会记录警告日志
        return "Processed: " + input;
    }
}

有状态与无状态过滤

有状态服务端

有状态服务端支持双向通信,接收包含以下内容的方法:

  • 双向上下文参数:

    • McpSyncRequestContext(同步操作)

    • McpAsyncRequestContext(异步操作)

    • McpSyncServerExchange(旧版,同步操作)

    • McpAsyncServerExchange(旧版,异步操作)

  • 支持双向操作:

    • roots() - 访问根目录

    • elicit() - 请求用户输入

    • sample() - 请求大语言模型采样

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

        // 该方法会在有状态服务端注册
        // 可使用诱导、采样、根目录功能
        if (context.sampleEnabled()) {
            var samplingResult = context.sample("Generate response");
            // 处理采样结果...
        }

        return "Processed with context";
    }
}

无状态服务端

无状态服务端针对简单请求-响应模式优化,规则如下:

  • 过滤包含双向上下文参数的方法:

    • 携带 McpSyncRequestContext 的方法会被跳过

    • 携带 McpAsyncRequestContext 的方法会被跳过

    • 携带 McpSyncServerExchange 的方法会被跳过

    • 携带 McpAsyncServerExchange 的方法会被跳过

  • 每个被过滤的方法都会记录警告日志

  • 接收包含以下内容的方法:

    • McpTransportContext(轻量级无状态上下文)

    • 无任何上下文参数

    • 仅常规 @McpToolParam 参数

  • 不支持双向操作:

    • roots() - 不可用

    • elicit() - 不可用

    • sample() - 不可用

@Component
public class StatelessTools {

    @McpTool(name = "simple-tool", description = "Simple stateless tool")
    public String simpleTool(@McpToolParam(description = "Input") String input) {
        // 该方法会在无状态服务端注册
        return "Processed: " + input;
    }

    @McpTool(name = "context-tool", description = "Tool with transport context")
    public String contextTool(
            McpTransportContext context,
            @McpToolParam(description = "Input") String input) {
        // 该方法会在无状态服务端注册
        return "Processed: " + input;
    }

    @McpTool(name = "bidirectional-tool", description = "Tool with bidirectional context")
    public String bidirectionalTool(
            McpSyncRequestContext context,
            @McpToolParam(description = "Input") String input) {
        // 该方法会在无状态服务端被过滤
        // 会记录警告日志
        return "Processed with sampling";
    }
}

过滤总结

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

方法过滤最佳实践:

  • 方法与服务端类型保持一致——同步服务端使用同步方法,异步服务端使用异步方法

  • 将有状态和无状态实现分离到不同类中,提升代码清晰度

  • 启动时检查日志,查看被过滤的方法警告

  • 使用合适的上下文——有状态使用 McpSyncRequestContext/McpAsyncRequestContext,无状态使用 McpTransportContext

  • 若同时支持有状态和无状态部署,需对两种模式都进行测试

异步支持

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

@Component
public class AsyncTools {

    @McpTool(name = "async-fetch", description = "Fetch data asynchronously")
    public MonoasyncFetch(
            @McpToolParam(description = "URL", required = true) String url) {

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

    @McpResource(uri = "async-data://{id}", name = "Async Data")
    public MonoasyncResource(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 会被自动检测并注册:

@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

附加资源

  • MCP 注解概述

  • 客户端注解

  • 特殊参数

  • MCP 服务端启动器

相关推荐