MCP Server Annotations

The MCP Server Annotations provide a declarative way to implement MCP server functionality using Java annotations. These annotations simplify the creation of tools, resources, prompts, and completion handlers.spring-doc.cn

Server Annotations

@McpTool

The @McpTool annotation marks a method as an MCP tool implementation with automatic JSON schema generation.spring-doc.cn

Basic Usage

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

Advanced Features

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

With Request Context

Tools can access the request context for advanced operations:spring-doc.cn

@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();
}

Dynamic Schema Support

Tools can accept CallToolRequest for runtime schema handling:spring-doc.cn

@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();
}

Progress Tracking

Tools can receive progress tokens for tracking long-running operations:spring-doc.cn

@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

The @McpResource annotation provides access to resources via URI templates.spring-doc.cn

Basic Usage

@Component
public class ResourceProvider {

    @McpResource(
        uri = "config://{key}",
        name = "Configuration",
        description = "Provides configuration data")
    public String getConfig(String key) {
        return configData.get(key);
    }
}

With 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)
    ));
}

With Request Context

@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

The @McpPrompt annotation generates prompt messages for AI interactions.spring-doc.cn

Basic Usage

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

With Optional Arguments

@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

The @McpComplete annotation provides auto-completion functionality for prompts.spring-doc.cn

Basic Usage

@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();
    }
}

With 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();
}

With 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 Implementations

Use McpSyncRequestContext or McpAsyncRequestContext for a unified interface that works with both stateful and stateless operations:spring-doc.cn

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

Simple Operations (No Context)

For simple operations, you can omit context parameters entirely:spring-doc.cn

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

Lightweight Stateless (with McpTransportContext)

For stateless operations where you need minimal transport context:spring-doc.cn

@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;
}
Stateless servers do not support bidirectional operations:

Therefore methods using McpSyncRequestContext or McpAsyncRequestContext in stateless mode are ignored.spring-doc.cn

Method Filtering by Server Type

The MCP annotations framework automatically filters annotated methods based on the server type and method characteristics. This ensures that only appropriate methods are registered for each server configuration. A warning is logged for each filtered method to help with debugging.spring-doc.cn

Synchronous vs Asynchronous Filtering

Synchronous Servers

Synchronous servers (configured with spring.ai.mcp.server.type=SYNC) use synchronous providers that:spring-doc.cn

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

Asynchronous Servers

Asynchronous servers (configured with spring.ai.mcp.server.type=ASYNC) use asynchronous providers that:spring-doc.cn

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

Stateful vs Stateless Filtering

Stateful Servers

Stateful servers support bidirectional communication and accept methods with:spring-doc.cn

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

Stateless Servers

Stateless servers are optimized for simple request-response patterns and:spring-doc.cn

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

Filtering Summary

Server Type Accepted Methods Filtered Methods

Sync Statefulspring-doc.cn

Non-reactive returns + bidirectional contextspring-doc.cn

Reactive returns (Mono/Flux)spring-doc.cn

Async Statefulspring-doc.cn

Reactive returns (Mono/Flux) + bidirectional contextspring-doc.cn

Non-reactive returnsspring-doc.cn

Sync Statelessspring-doc.cn

Non-reactive returns + no bidirectional contextspring-doc.cn

Reactive returns OR bidirectional context parametersspring-doc.cn

Async Statelessspring-doc.cn

Reactive returns (Mono/Flux) + no bidirectional contextspring-doc.cn

Non-reactive returns OR bidirectional context parametersspring-doc.cn

Best Practices for Method Filtering:
  1. Keep methods aligned with your server type - use sync methods for sync servers, async for async serversspring-doc.cn

  2. Separate stateful and stateless implementations into different classes for clarityspring-doc.cn

  3. Check logs during startup for filtered method warningsspring-doc.cn

  4. Use the right context - McpSyncRequestContext/McpAsyncRequestContext for stateful, McpTransportContext for statelessspring-doc.cn

  5. Test both modes if you support both stateful and stateless deploymentsspring-doc.cn

Async Support

All server annotations support asynchronous implementations using Reactor:spring-doc.cn

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

With Spring Boot auto-configuration, annotated beans are automatically detected and registered:spring-doc.cn

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

The auto-configuration will:spring-doc.cn

  1. Scan for beans with MCP annotationsspring-doc.cn

  2. Create appropriate specificationsspring-doc.cn

  3. Register them with the MCP serverspring-doc.cn

  4. Handle both sync and async implementations based on configurationspring-doc.cn

Configuration Properties

Configure the server annotation scanner:spring-doc.cn

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