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

Annotation Attributes

The @McpTool annotation supports the following attributes:spring-doc.cn

Attribute Default Description

namespring-doc.cn

method namespring-doc.cn

The tool identifier. Defaults to the method name if not provided.spring-doc.cn

descriptionspring-doc.cn

method namespring-doc.cn

Human-readable description of the tool.spring-doc.cn

titlespring-doc.cn

""spring-doc.cn

Intended for UI and end-user contexts — optimized to be human-readable. If not provided, name is used for display. (Precedence: annotations.title > title > name)spring-doc.cn

generateOutputSchemaspring-doc.cn

falsespring-doc.cn

If true, automatically generates a JSON output schema for non-primitive return types.spring-doc.cn

annotationsspring-doc.cn

@McpAnnotationsspring-doc.cn

Additional hints for clients (see tool annotations below).spring-doc.cn

metaProviderspring-doc.cn

DefaultMetaProvider.classspring-doc.cn

Class implementing MetaProvider that supplies data for the _meta field in the tool declaration.spring-doc.cn

Tool Annotations (Hints)

@McpTool(name = "calculate-area",
         description = "Calculate the area of a rectangle",
         title = "Rectangle Area Calculator",
         generateOutputSchema = true,
         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");
}

The McpAnnotations nested annotation provides client hints:spring-doc.cn

Hint Default Description

titlespring-doc.cn

""spring-doc.cn

Human-readable title for the tool.spring-doc.cn

readOnlyHintspring-doc.cn

falsespring-doc.cn

If true, the tool does not modify its environment.spring-doc.cn

destructiveHintspring-doc.cn

truespring-doc.cn

If true, the tool may perform destructive updates (meaningful only when readOnlyHint == false).spring-doc.cn

idempotentHintspring-doc.cn

falsespring-doc.cn

If true, calling with the same arguments has no additional effect (meaningful only when readOnlyHint == false).spring-doc.cn

openWorldHintspring-doc.cn

truespring-doc.cn

If true, the tool may interact with external entities (e.g., web search). If false, the domain is closed.spring-doc.cn

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

Annotation Attributes

Attribute Default Description

urispring-doc.cn

""spring-doc.cn

The URI (or URI template) of the resource. Use {varName} for template variables.spring-doc.cn

namespring-doc.cn

""spring-doc.cn

Programmatic identifier. Also used as display name when title is absent.spring-doc.cn

titlespring-doc.cn

""spring-doc.cn

Optional human-readable name for display purposes.spring-doc.cn

descriptionspring-doc.cn

""spring-doc.cn

Description of what the resource represents.spring-doc.cn

mimeTypespring-doc.cn

"text/plain"spring-doc.cn

The MIME type of the resource content.spring-doc.cn

metaProviderspring-doc.cn

DefaultMetaProvider.classspring-doc.cn

Class implementing MetaProvider that supplies data for the _meta field.spring-doc.cn

annotationsspring-doc.cn

@McpAnnotations(…​)spring-doc.cn

Client annotations for audience, priority, and last-modified metadata.spring-doc.cn

The nested McpAnnotations for resources supports:spring-doc.cn

Attribute Default Description

audiencespring-doc.cn

{Role.USER}spring-doc.cn

Describes intended consumers (Role.USER, Role.ASSISTANT, or both).spring-doc.cn

priorityspring-doc.cn

0.5spring-doc.cn

Importance from 0.0 (least) to 1.0 (most). A value of 1.0 indicates effectively required.spring-doc.cn

lastModifiedspring-doc.cn

""spring-doc.cn

ISO 8601 date-time when the resource was last modified.spring-doc.cn

Basic Usage

@Component
public class ResourceProvider {

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

Annotation Attributes

Attribute Default Description

namespring-doc.cn

""spring-doc.cn

Unique identifier for the prompt.spring-doc.cn

titlespring-doc.cn

""spring-doc.cn

Optional human-readable name for display purposes.spring-doc.cn

descriptionspring-doc.cn

""spring-doc.cn

Optional human-readable description.spring-doc.cn

metaProviderspring-doc.cn

DefaultMetaProvider.classspring-doc.cn

Class implementing MetaProvider that supplies data for the _meta field.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 and resource URI templates.spring-doc.cn

Use either the prompt or uri attribute — not both simultaneously:spring-doc.cn

  • prompt — completes an argument of the named promptspring-doc.cn

  • uri — completes a URI template expression of the named resource URIspring-doc.cn

Prompt Argument Completion

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

Resource URI Completion

@McpComplete(uri = "config://{key}")
public List<String> completeConfigKey(String prefix) {
    return configKeys.stream()
        .filter(key -> key.startsWith(prefix))
        .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
    }

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

    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