This version is still in development and is not considered stable yet. For the latest snapshot version, please use Spring AI 1.1.3!spring-doc.cn

MCP Annotations Special Parameters

The MCP Annotations support several special parameter types that provide additional context and functionality to annotated methods. These parameters are automatically injected by the framework and are excluded from JSON schema generation.spring-doc.cn

Special Parameter Types

MetaProvider

The MetaProvider interface supplies data for the _meta field in tool, prompt, and resource declarations.spring-doc.cn

Overview

  • Implemented as a class referenced in @McpTool(metaProvider = …​), @McpPrompt(metaProvider = …​), or @McpResource(metaProvider = …​)spring-doc.cn

  • Allows attaching static or computed metadata to a tool/prompt/resource specification at startupspring-doc.cn

  • The default DefaultMetaProvider returns an empty map (no _meta appended)spring-doc.cn

Custom 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 = "Tool with metadata",
         metaProvider = MyToolMetaProvider.class)
public String myTool(@McpToolParam(description = "Input") String input) {
    return "Processed: " + input;
}

The same pattern applies to @McpPrompt and @McpResource.spring-doc.cn

McpMeta

The McpMeta class provides access to metadata from MCP requests, notifications, and results.spring-doc.cn

Overview

  • Automatically injected when used as a method parameterspring-doc.cn

  • Excluded from parameter count limits and JSON schema generationspring-doc.cn

  • Provides convenient access to metadata through the get(String key) methodspring-doc.cn

  • If no metadata is present in the request, an empty McpMeta object is injectedspring-doc.cn

Usage in Tools

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

    // Access metadata from the request
    String userId = (String) meta.get("userId");
    String sessionId = (String) meta.get("sessionId");
    String userRole = (String) meta.get("userRole");

    // Use metadata to customize behavior
    if ("admin".equals(userRole)) {
        return processAsAdmin(data, userId);
    } else {
        return processAsUser(data, userId);
    }
}

Usage in 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");

    // Check access permissions using metadata
    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)
    ));
}

Usage in 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");

    // Generate localized content based on metadata
    String message = generateLocalizedMessage(topic, language, region);

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

@McpProgressToken

The @McpProgressToken annotation marks a parameter to receive progress tokens from MCP requests.spring-doc.cn

Overview

Usage in 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) {
        // Send initial progress
        exchange.progressNotification(new ProgressNotification(
            progressToken, 0.0, 1.0, "Starting " + operation));

        // Simulate work with progress updates
        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";
}

Usage in 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) {
        // Track file reading progress
        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 objects provide unified access to MCP request information and server-side operations.spring-doc.cn

Overview

  • Provides unified interface for both stateful and stateless operationsspring-doc.cn

  • Automatically injected when used as a parameterspring-doc.cn

  • Excluded from JSON schema generationspring-doc.cn

  • Enables advanced features like logging, progress notifications, sampling, elicitation, and roots accessspring-doc.cn

  • Works with both stateful (server exchange) and stateless (transport context) modesspring-doc.cn

Context Getters

Both McpSyncRequestContext and McpAsyncRequestContext expose the following read-only context:spring-doc.cn

Method Description

request()spring-doc.cn

The original MCP request (e.g., CallToolRequest, ReadResourceRequest). Use request().progressToken() to access the progress token.spring-doc.cn

exchange()spring-doc.cn

The underlying server exchange (McpSyncServerExchange / McpAsyncServerExchange). Available in stateful mode only; null in stateless mode.spring-doc.cn

sessionId()spring-doc.cn

The current session identifier.spring-doc.cn

clientInfo()spring-doc.cn

The client implementation info (Implementation).spring-doc.cn

clientCapabilities()spring-doc.cn

The capabilities declared by the client.spring-doc.cn

requestMeta()spring-doc.cn

Metadata map from the _meta field of the request. Prefer this over injecting McpMeta when already using a context object.spring-doc.cn

transportContext()spring-doc.cn

The transport-level context (McpTransportContext).spring-doc.cn

McpSyncRequestContext Features

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

@McpTool(name = "advanced-tool", description = "Tool with full server capabilities")
public String advancedTool(
        McpSyncRequestContext context,
        @McpToolParam(description = "Input", required = true) String input) {

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

    // Ping the client
    context.ping();

    // Send progress updates
    context.progress(50); // 50% complete

    // Check if elicitation is supported before using it
    if (context.elicitEnabled()) {
        // Request additional information from user
        StructuredElicitResult<UserInfo> elicitResult = context.elicit(
            e -> e.message("Need additional information"),
            UserInfo.class
        );

        if (elicitResult.action() == ElicitResult.Action.ACCEPT) {
            UserInfo userInfo = elicitResult.structuredContent();
            // Use the user information
        }
    }

    // Check if sampling is supported before using it
    if (context.sampleEnabled()) {
        // Request LLM sampling
        CreateMessageResult samplingResult = context.sample(
            s -> s.message("Process: " + input)
                .modelPreferences(pref -> pref.modelHints("gpt-4"))
        );
    }

    // Access client root directories (only available in stateful mode)
    if (context.rootsEnabled()) {
        ListRootsResult roots = context.roots();
        roots.roots().forEach(root -> context.info("Client root: " + root.uri()));
    }

    return "Processed with advanced features";
}

McpAsyncRequestContext Features

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

@McpTool(name = "async-advanced-tool", description = "Async tool with server capabilities")
public Mono<String> asyncAdvancedTool(
        McpAsyncRequestContext context,
        @McpToolParam(description = "Input", required = true) String input) {

    return context.info("Async processing: " + input)
        .then(context.progress(25))
        .then(context.ping())
        .flatMap(v -> {
            // Perform elicitation if supported
            if (context.elicitEnabled()) {
                return context.elicitation(UserInfo.class)
                    .map(userInfo -> "Processing for user: " + userInfo.name());
            }
            return Mono.just("Processing...");
        })
        .flatMap(msg -> {
            // Perform sampling if supported
            if (context.sampleEnabled()) {
                return context.sampling("Process: " + input)
                    .map(result -> "Completed: " + result);
            }
            return Mono.just("Completed: " + msg);
        });
}

McpTransportContext

Lightweight context for stateless operations.spring-doc.cn

Overview

Usage Example

@McpTool(name = "stateless-tool", description = "Stateless tool with context")
public String statelessTool(
        McpTransportContext context,
        @McpToolParam(description = "Input", required = true) String input) {

    // Limited context access
    // Useful for transport-level operations

    return "Processed in stateless mode: " + input;
}

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

    // Access transport context if needed
    String data = loadData(id);

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

CallToolRequest

Special parameter for tools that need access to the full request with dynamic schema.spring-doc.cn

Overview

  • Provides access to the complete tool requestspring-doc.cn

  • Enables dynamic schema handling at runtimespring-doc.cn

  • Automatically injected and excluded from schema generationspring-doc.cn

  • Useful for flexible tools that adapt to different input schemasspring-doc.cn

Usage Examples

@McpTool(name = "dynamic-tool", description = "Tool with dynamic schema support")
public CallToolResult processDynamicSchema(CallToolRequest request) {
    Map<String, Object> args = request.arguments();

    // Process based on whatever schema was provided at runtime
    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();
}

Mixed Parameters

@McpTool(name = "hybrid-tool", description = "Tool with typed and dynamic parameters")
public String processHybrid(
        @McpToolParam(description = "Operation", required = true) String operation,
        @McpToolParam(description = "Priority", required = false) Integer priority,
        CallToolRequest request) {

    // Use typed parameters for known fields
    String result = "Operation: " + operation;
    if (priority != null) {
        result += " (Priority: " + priority + ")";
    }

    // Access additional dynamic arguments
    Map<String, Object> allArgs = request.arguments();

    // Remove known parameters to get only additional ones
    Map<String, Object> additionalArgs = new HashMap<>(allArgs);
    additionalArgs.remove("operation");
    additionalArgs.remove("priority");

    if (!additionalArgs.isEmpty()) {
        result += " with " + additionalArgs.size() + " additional parameters";
    }

    return result;
}

With Progress Token

@McpTool(name = "flexible-with-progress", description = "Flexible tool with progress")
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"));
    }

    // Process dynamic arguments
    String result = processDynamicArgs(args);

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

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

Parameter Injection Rules

Automatic Injection

The following parameters are automatically injected by the framework:spring-doc.cn

  1. McpMeta - Metadata from the _meta field of the requestspring-doc.cn

  2. @McpProgressToken String - Progress token if availablespring-doc.cn

  3. McpSyncRequestContext / McpAsyncRequestContext - Unified request context (recommended)spring-doc.cn

  4. McpSyncServerExchange / McpAsyncServerExchange - Low-level server exchange context (stateful only)spring-doc.cn

  5. McpTransportContext - Transport context for stateless operationsspring-doc.cn

  6. CallToolRequest - Full tool request for dynamic schema (tools only)spring-doc.cn

Schema Generation

Special parameters are excluded from JSON schema generation:spring-doc.cn

Null Handling

Best Practices

Use McpMeta for Context

@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 Null Checks

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

Choose the Right Context

  • Use McpSyncRequestContext / McpAsyncRequestContext for unified access to request context, supporting both stateful and stateless operations with convenient helper methodsspring-doc.cn

  • Use McpTransportContext for simple stateless operations when you only need transport-level contextspring-doc.cn

  • Omit context parameters entirely for the simplest casesspring-doc.cn

Capability Checking

Always check capability support before using client features:spring-doc.cn

@McpTool(name = "capability-aware", description = "Tool that checks capabilities")
public String capabilityAware(
        McpSyncRequestContext context,
        @McpToolParam(description = "Data", required = true) String data) {

    // Check if elicitation is supported before using it
    if (context.elicitEnabled()) {
        // Safe to use elicitation
        var result = context.elicit(UserInfo.class);
        // Process result...
    }

    // Check if sampling is supported before using it
    if (context.sampleEnabled()) {
        // Safe to use sampling
        var samplingResult = context.sample("Process: " + data);
        // Process result...
    }

    // Note: Stateless servers do not support bidirectional operations
    // (roots, elicitation, sampling) and will return false for these checks

    return "Processed with capability awareness";
}