Spring AI MCP学习目录
一、MCP 原理详解
二、MCP 客户端
三、MCP 客户端
四、MCP 服务端
五、SpringAI MCP 服务端
六、SpringAI MCP 服务端 STDIO & SSE
七、SpringAI MCP 服务端 Streamable-HTTP
八、SpringAI MCP 服务端 Stateless Streamable-HTTP
九、 MCP 安全(Security)
十、SpringAI MCP 安全(Security)
十一、SpringAI MCP 客户端注解
十二、SpringAI MCP 服务端注解
十三、SpringAI MCP 特殊参数(Special Parameters)


Spring AI MCP注解模块为Java中的模型上下文协议(MCP)服务器和客户端提供了基于注解的方法处理。它通过使用Java注解的简洁声明式方法,简化了MCP服务器方法和客户端处理程序的创建和注册。

MCP注解使开发者能够使用声明式注解来创建和注册MCP操作处理程序。这种方法通过减少样板代码并提高可维护性,简化了MCP服务器和客户端功能的实现。该库构建在MCP Java SDK之上,为实施MCP服务器和客户端提供了更高层次的基于注解的编程模型。

架构概览

MCP注解模块包含两大核心部分:

服务器端注解

针对MCP服务器的开发需求,提供以下注解:

  • @McpTool - 实现MCP工具并自动生成JSON模式
  • @McpResource - 通过URI模板提供资源访问能力
  • @McpPrompt - 生成提示消息
  • @McpComplete - 提供自动补全功能

客户端注解

面向MCP客户端的开发场景,提供以下注解:

  • @McpLogging - 处理日志消息通知
  • @McpSampling - 处理采样请求
  • @McpElicitation - 处理用于收集额外信息的启发请求
  • @McpProgress - 处理长时间运行操作中的进度通知
  • @McpToolListChanged - 处理工具列表变更通知
  • @McpResourceListChanged - 处理资源列表变更通知
  • @McpPromptListChanged - 处理提示列表变更通知

特殊参数与注解说明

  • McpSyncRequestContext‌ - 同步操作专用参数类型,提供统一的MCP请求上下文访问接口,包括原始请求、服务器交换(用于有状态操作)、传输上下文(用于无状态操作),以及日志记录、进度报告、采样和启发式请求等便捷方法。该参数自动注入且不参与JSON模式生成,支持Complete、Prompt、Resource和Tool方法。‌

  • McpAsyncRequestContext‌ - 异步操作专用参数类型,提供与McpSyncRequestContext相同的统一接口,但使用响应式(基于Mono)返回类型。该参数自动注入且不参与JSON模式生成,支持Complete、Prompt、Resource和Tool方法。

  • McpTransportContext‌ - 无状态操作专用参数类型,提供轻量级的传输层上下文访问,无需完整服务器交换功能。该参数自动注入且不参与JSON模式生成。

  • @McpProgressToken‌ - 标记方法参数以接收请求中的进度令牌。该参数自动注入且不参与生成的JSON模式。注意:使用McpSyncRequestContext或McpAsyncRequestContext时,可通过ctx.request().progressToken()获取进度令牌,无需使用此注解。

  • McpMeta‌ - 特殊参数类型,提供对MCP请求、通知和结果中元数据的访问。该参数自动注入且不受参数数量限制和JSON模式生成的影响。注意:使用McpSyncRequestContext或McpAsyncRequestContext时,可通过ctx.requestMeta()获取元数据。

服务端注解

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

@McpTool

基础

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

    // Send logging notification
    context.info("Processing data: " + data);

    // Send progress notification (using convenient method)
    context.progress(p -> p.progress(0.5).total(1.0).message("Processing..."));

    // Ping the client
    context.ping();

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

动态Schema支持
工具可接受CallToolRequest以实现运行时模式处理:

@McpTool(name = "flexible-tool", description = "Process dynamic schema")
public CallToolResult processDynamic(CallToolRequest request) {
    Map<String, Object> args = request.arguments();

    // Process based on runtime schema
    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) {

    // Access progress token from context
    String progressToken = context.request().progressToken();

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

        // Perform work...

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

    // Send logging notification using convenient method
    context.info("Accessing resource: " + id);

    // Ping the client
    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, ");
        // Add age-specific content
    }

    if (interests != null && !interests.isEmpty()) {
        message.append("Your interest in ").append(interests);
        // Add interest-specific content
    }

    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 List<String> completeCityName(String prefix) {
        return cities.stream()
            .filter(city -> city.toLowerCase().startsWith(prefix.toLowerCase()))
            .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();

    // Different completions based on 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 flag
        )
    );
}

Stateless vs Stateful 实现

统一请求上下文(推荐)

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

    // Access request and metadata
    String progressToken = context.request().progressToken();

    // Logging with convenient methods
    context.info("Processing: " + input);

    // Progress notifications (Note client should set a progress token
    // with its request to be able to receive progress updates)
    context.progress(50); // Simple percentage

    // Ping client
    context.ping();

    // Check capabilities before using
    if (context.elicitEnabled()) {
        // Request user input (only in stateful mode)
        StructuredElicitResult<UserInfo> elicitResult = context.elicit(UserInfo.class);
        if (elicitResult.action() == ElicitResult.Action.ACCEPT) {
            // Use elicited data
        }
    }

    if (context.sampleEnabled()) {
        // Request LLM sampling (only in stateful mode)
        CreateMessageResult samplingResult = context.sample("Generate response");
        // Use sampling result
    }

    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) {
    // Access transport-level context only
    // No bidirectional operations (roots, elicitation, sampling)
    return "Processed: " + input;
}

方法按服务器类型过滤

MCP注解框架会根据服务器类型和方法特征自动过滤已注解的方法,确保仅将适用的方法注册到每个服务器配置中。系统会为每个被过滤的方法记录警告信息,以辅助调试。

同步与异步过滤

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

  • Accept methods with non-reactive return types:

    • Primitive types (int, double, boolean)
    • Object types (String, Integer, custom POJOs)
    • MCP types (CallToolResult, ReadResourceResult, GetPromptResult, CompleteResult)
    • Collections (List, Map<String, Object>)
  • Filter out methods with reactive return types:

    • Mono
    • Flux
    • Publisher
@Component
public class SyncTools {

    @McpTool(name = "sync-tool", description = "Synchronous tool")
    public String syncTool(String input) {
        // This method WILL be registered on sync servers
        return "Processed: " + input;
    }

    @McpTool(name = "async-tool", description = "Async tool")
    public Mono<String> asyncTool(String input) {
        // This method will be FILTERED OUT on sync servers
        // A warning will be logged
        return Mono.just("Processed: " + input);
    }
}

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

  • Accept methods with reactive return types:
    • Mono (for single results)
    • Flux (for streaming results)
    • Publisher (generic reactive type)
  • Filter out methods with non-reactive return types:
    • Primitive types
    • Object types
    • Collections
    • MCP result types
@Component
public class AsyncTools {

    @McpTool(name = "async-tool", description = "Async tool")
    public Mono<String> asyncTool(String input) {
        // This method WILL be registered on async servers
        return Mono.just("Processed: " + input);
    }

    @McpTool(name = "sync-tool", description = "Sync tool")
    public String syncTool(String input) {
        // This method will be FILTERED OUT on async servers
        // A warning will be logged
        return "Processed: " + input;
    }
}

有状态与无状态过滤

有状态服务器

有状态服务器支持双向通信,可接受以下类型的方法:

  • 双向上下文参数:

    • McpSyncRequestContext (for sync operations)
    • McpAsyncRequestContext (for async operations)
    • McpSyncServerExchange (legacy, for sync operations)
    • McpAsyncServerExchange (legacy, for async operations)
  • 双向操作支持:

    • roots() - Access root directories
    • elicit() - Request user input
    • sample() - Request LLM sampling
@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) {

        // This method WILL be registered on stateful servers
        // Can use elicitation, sampling, roots
        if (context.sampleEnabled()) {
            var samplingResult = context.sample("Generate response");
            // Process sampling result...
        }

        return "Processed with context";
    }
}

无状态服务器

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

  • 排除方法‌:含双向上下文参数的方法
    • Methods with McpSyncRequestContext are skipped
    • Methods with McpAsyncRequestContext are skipped
    • Methods with McpSyncServerExchange are skipped
    • Methods with McpAsyncServerExchange are skipped
    • A warning is logged for each filtered method
      ‌- 接受方法‌:
    • McpTransportContext (lightweight stateless context)
    • No context parameter at all
    • Only regular @McpToolParam parameters
  • ‌不支持操作‌:
    • roots() - Not available
    • elicit() - Not available
    • sample() - Not available
@Component
public class StatelessTools {

    @McpTool(name = "simple-tool", description = "Simple stateless tool")
    public String simpleTool(@McpToolParam(description = "Input") String input) {
        // This method WILL be registered on stateless servers
        return "Processed: " + input;
    }

    @McpTool(name = "context-tool", description = "Tool with transport context")
    public String contextTool(
            McpTransportContext context,
            @McpToolParam(description = "Input") String input) {
        // This method WILL be registered on stateless servers
        return "Processed: " + input;
    }

    @McpTool(name = "bidirectional-tool", description = "Tool with bidirectional context")
    public String bidirectionalTool(
            McpSyncRequestContext context,
            @McpToolParam(description = "Input") String input) {
        // This method will be FILTERED OUT on stateless servers
        // A warning will be logged
        return "Processed with sampling";
    }
}

过滤总结

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

方法过滤最佳实践:

  1. 保持方法类型与服务器一致 - 同步服务器使用同步方法,异步服务器使用异步方法
  2. 将有状态和无状态实现分离到不同类中以保持清晰
  3. 启动时检查过滤方法警告日志
  4. 使用正确的上下文 - 有状态使用McpSyncRequestContext/McpAsyncRequestContext,无状态使用McpTransportContext
  5. 如果同时支持有状态和无状态部署,请测试两种模式

异步支持

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

@Component
public class AsyncTools {

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

        return Mono.fromCallable(() -> {
            // Simulate async operation
            return fetchFromUrl(url);
        }).subscribeOn(Schedulers.boundedElastic());
    }

    @McpResource(uri = "async-data://{id}", name = "Async Data")
    public Mono<ReadResourceResult> asyncResource(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 {
    // Your @McpTool annotated methods
}

@Component
public class MyMcpResources {
    // Your @McpResource annotated methods
}

自动配置将执行以下操作:

  1. 扫描带有MCP注解的Bean
  2. 创建相应的规范
  3. 将其注册到MCP服务器
  4. 根据配置处理同步和异步实现

配置

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

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐