CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-starter-mcp-server

Spring Boot Starter for building Model Context Protocol (MCP) servers with auto-configuration, annotation-based tool/resource/prompt definitions, and support for STDIO, SSE, and Streamable-HTTP transports

Overview
Eval results
Files

annotations.mddocs/reference/

Server Annotations

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 by automatically generating JSON schemas and registering capabilities with the MCP server.

Annotation Overview

The Spring AI MCP Server starter supports two categories of tool annotations:

  1. Spring AI Tool Annotations (@Tool, @ToolParam) - General-purpose Spring AI tool annotations
  2. MCP Server Annotations (@McpTool, @McpResource, @McpPrompt, etc.) - MCP-specific server annotations

Spring AI Tool Annotations vs MCP Annotations

Spring AI Annotations (org.springframework.ai.tool.annotation):

  • General-purpose annotations for defining Spring AI tools
  • Can be used with any Spring AI model or provider
  • Automatically converted to MCP tool specifications by the MCP server
  • Simpler API with fewer MCP-specific features

MCP Server Annotations (org.springaicommunity.mcp.annotation):

  • Specific to MCP server implementations
  • Provide full access to MCP protocol features (resources, prompts, completions)
  • Support MCP-specific context and metadata
  • Enable advanced MCP capabilities like sampling and elicitation

When to use each:

  • Use @Tool when building general Spring AI tools that may be used outside MCP context
  • Use @McpTool when you need MCP-specific features like request context, metadata, or tool hints
  • Both can be used together in the same application

Capabilities

Spring AI @Tool Annotation

Marks a method as a general Spring AI tool that will be automatically exposed as an MCP tool by the server.

/**
 * Marks a method as a tool in Spring AI
 */
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@interface Tool {
    /**
     * The name of the tool (defaults to method name if not provided)
     * For maximum compatibility, use only alphanumeric characters,
     * underscores, hyphens, and dots
     */
    String name() default "";

    /**
     * Description of what the tool does
     */
    String description() default "";

    /**
     * Whether the tool result should be returned directly or
     * passed back to the model
     */
    boolean returnDirect() default false;

    /**
     * The class to use to convert the tool call result to a String
     */
    Class<? extends ToolCallResultConverter> resultConverter()
        default DefaultToolCallResultConverter.class;
}

Usage Examples:

Basic Spring AI tool:

import org.springframework.ai.tool.annotation.Tool;
import org.springframework.ai.tool.annotation.ToolParam;
import org.springframework.stereotype.Component;

@Component
public class WeatherTools {
    @Tool(
        name = "get_weather",
        description = "Get the current weather for a location"
    )
    public String getWeather(
            @ToolParam(description = "City name", required = true) String city,
            @ToolParam(description = "Country code", required = false) String country) {
        // Implementation
        return "Weather in " + city + ": Sunny, 22°C";
    }
}

Tool with return direct:

@Tool(
    name = "execute_command",
    description = "Execute a system command and return result",
    returnDirect = true
)
public String executeCommand(
        @ToolParam(description = "Command to execute") String command) {
    // When returnDirect is true, the result goes directly
    // to the user without further model processing
    return executeSystemCommand(command);
}

Tool with custom result converter:

@Tool(
    name = "get_user_data",
    description = "Get user data in JSON format",
    resultConverter = JsonResultConverter.class
)
public UserData getUserData(
        @ToolParam(description = "User ID") String userId) {
    return userRepository.findById(userId);
}

Spring AI @ToolParam Annotation

Marks a tool method parameter with metadata for JSON schema generation.

/**
 * Marks a tool argument/parameter
 */
@Target({ ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@interface ToolParam {
    /**
     * Whether the tool argument is required (default: true)
     */
    boolean required() default true;

    /**
     * Description of the parameter for the AI model
     */
    String description() default "";
}

Usage Examples:

Required and optional parameters:

@Tool(name = "search_products", description = "Search for products")
public List<Product> searchProducts(
        @ToolParam(description = "Search query", required = true) String query,
        @ToolParam(description = "Maximum results", required = false) Integer maxResults,
        @ToolParam(description = "Category filter", required = false) String category) {

    int limit = maxResults != null ? maxResults : 10;
    return productService.search(query, category, limit);
}

Complex parameter types:

@Tool(name = "create_user", description = "Create a new user account")
public User createUser(
        @ToolParam(description = "User details including name, email, and preferences")
        UserCreateRequest request) {
    // Spring AI automatically generates JSON schema for complex types
    return userService.create(request);
}

@McpTool

Marks a method as an MCP tool implementation with automatic JSON schema generation.

/**
 * Marks a method as an MCP tool that can be invoked by AI models
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface McpTool {
    /**
     * The name of the tool (must be unique)
     */
    String name();

    /**
     * Description of what the tool does
     */
    String description();

    /**
     * Optional metadata annotations for the tool
     */
    McpAnnotations annotations() default @McpAnnotations;

    @interface McpAnnotations {
        String title() default "";
        boolean readOnlyHint() default false;
        boolean destructiveHint() default false;
        boolean idempotentHint() default false;
    }
}

Usage Examples:

Basic tool:

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

Tool with metadata:

@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 double calculateRectangleArea(
        @McpToolParam(description = "Width in meters", required = true) double width,
        @McpToolParam(description = "Height in meters", required = true) double height) {
    return width * height;
}

Tool with request context:

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

    context.info("Processing data: " + data);
    context.progress(50);

    String result = data.toUpperCase();

    context.progress(100);
    return "Processed: " + result;
}

Tool with dynamic schema (CallToolRequest):

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

    String result = "Processed " + args.size() + " arguments";

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

Return Types:

  • Primitives: int, double, boolean, etc.
  • Objects: String, custom POJOs (automatically serialized to JSON)
  • CallToolResult: Full control over result content
  • Reactive types: Mono<T>, Flux<T> (for async servers)

Special Parameters:

  • McpSyncRequestContext / McpAsyncRequestContext: Access to request context
  • McpTransportContext: Lightweight transport context (stateless)
  • McpMeta: Access to MCP metadata
  • CallToolRequest: Dynamic schema handling

@McpToolParam

Describes a tool parameter for JSON schema generation.

/**
 * Describes a parameter for an @McpTool method
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface McpToolParam {
    /**
     * Description of the parameter
     */
    String description();

    /**
     * Whether the parameter is required
     */
    boolean required() default true;
}

Usage Example:

@McpTool(name = "search", description = "Search for items")
public List<String> search(
        @McpToolParam(description = "Search query", required = true) String query,
        @McpToolParam(description = "Maximum results", required = false) Integer limit) {

    int maxResults = (limit != null) ? limit : 10;
    // Perform search...
    return results;
}

@McpResource

Provides access to resources via URI templates.

/**
 * Marks a method as an MCP resource provider
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface McpResource {
    /**
     * URI template with placeholders (e.g., "config://{key}")
     */
    String uri();

    /**
     * Resource name for identification
     */
    String name();

    /**
     * Description of the resource
     */
    String description();

    /**
     * Optional MIME type for the resource content
     */
    String mimeType() default "";
}

Usage Examples:

Basic resource:

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

Resource with ReadResourceResult:

@McpResource(
    uri = "user-profile://{username}",
    name = "User Profile",
    description = "Provides user profile information",
    mimeType = "application/json")
public ReadResourceResult getUserProfile(String username) {
    String profileJson = loadUserProfile(username);

    return new ReadResourceResult(List.of(
        new TextResourceContents(
            "user-profile://" + username,
            "application/json",
            profileJson)
    ));
}

Resource with request context:

@McpResource(
    uri = "data://{id}",
    name = "Data Resource",
    description = "Fetch data by ID")
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)
    ));
}

URI Templates:

  • Use {placeholder} syntax for dynamic segments
  • Method parameters must match placeholder names
  • URI scheme is arbitrary (e.g., config://, file://, db://)

Return Types:

  • String: Automatically wrapped in TextResourceContents
  • ReadResourceResult: Full control over resource contents
  • Reactive types: Mono<ReadResourceResult>, Flux<ResourceContents>

@McpPrompt

Generates prompt messages for AI interactions.

/**
 * Marks a method as an MCP prompt provider
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface McpPrompt {
    /**
     * Unique prompt name
     */
    String name();

    /**
     * Description of the prompt's purpose
     */
    String description();
}

Usage Examples:

Basic prompt:

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

Prompt with optional arguments:

@McpPrompt(
    name = "personalized-message",
    description = "Generate a personalized message")
public GetPromptResult personalizedMessage(
        @McpArg(name = "name", description = "User's name", required = true) String name,
        @McpArg(name = "age", description = "User's age", required = false) Integer age,
        @McpArg(name = "interests", description = "User 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("With interests in ").append(interests).append(", ");
    }

    message.append("I can help you with many tasks!");

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

Return Types:

  • GetPromptResult: Contains description and list of prompt messages

@McpArg

Describes prompt arguments.

/**
 * Describes an argument for an @McpPrompt method
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface McpArg {
    /**
     * Argument name
     */
    String name();

    /**
     * Optional description of the argument
     */
    String description() default "";

    /**
     * Whether the argument is required
     */
    boolean required() default true;
}

@McpComplete

Provides auto-completion functionality for prompts.

/**
 * Marks a method as a completion provider for a prompt
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface McpComplete {
    /**
     * The prompt name to provide completions for
     */
    String prompt();
}

Usage Examples:

Basic completion:

@Component
public class CompletionProvider {
    private final List<String> cities = List.of("New York", "Los Angeles", "Chicago", "Houston");

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

Completion with CompleteRequest.CompleteArgument:

@McpComplete(prompt = "travel-planner")
public List<String> completeTravelDestination(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();
}

Completion 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(),
            hasMoreCompletions
        )
    );
}

Return Types:

  • List<String>: Simple list of completion suggestions
  • CompleteResult: Full control over completion metadata

Annotation Scanning

Annotation scanning is enabled by default and controlled via configuration:

# Enable/disable annotation scanning
spring.ai.mcp.server.annotation-scanner.enabled=true

The starter automatically scans all Spring beans for MCP annotations and registers them with the MCP server. Annotated methods are filtered based on:

  • Server Type: Synchronous servers accept non-reactive return types; async servers accept reactive types
  • Protocol: Stateless servers ignore methods with bidirectional context (elicitation, sampling)
  • Return Type: Methods must return appropriate types for their annotation

Filtered methods log warnings to help with debugging.

Stateful vs Stateless

Stateful Servers (SSE, Streamable-HTTP)

Support bidirectional operations including:

  • Elicitation (requesting user input)
  • Sampling (requesting LLM completions)
  • Roots management

Use McpSyncRequestContext or McpAsyncRequestContext for full access.

Stateless Servers

Do not support bidirectional operations. Methods using full request context are ignored in stateless mode.

Use McpTransportContext for lightweight operations or no context parameter for simple operations.

Server Type Filtering

Synchronous Servers (type=SYNC)

Accept methods with:

  • Primitive types (int, double, boolean)
  • Object types (String, Integer, custom POJOs)
  • MCP types (CallToolResult, ReadResourceResult, GetPromptResult, CompleteResult)
  • Collections (List<String>, Map<String, Object>)

Asynchronous Servers (type=ASYNC)

Accept methods with:

  • Reactive types (Mono<T>, Flux<T>)
  • All synchronous return types (automatically wrapped)

Additional Annotations

The following annotations are provided by the external MCP Java SDK (org.springaicommunity:mcp-annotations) for advanced functionality:

@McpMeta

Injects MCP metadata into method parameters.

/**
 * Injects MCP metadata provided by the client
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface McpMeta {}

Usage Example:

@McpTool(name = "inspect-metadata", description = "Inspect client metadata")
public String inspectMetadata(@McpMeta Map<String, Object> metadata) {
    return "Received metadata: " + metadata.toString();
}

@McpProgress

Marks a method parameter to receive progress notification callback.

/**
 * Marks a parameter to receive progress notification functionality
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface McpProgress {}

Usage Example:

@McpTool(name = "long-task", description = "Long-running task with progress")
public String longTask(
        @McpProgress Consumer<ProgressNotification> progressCallback,
        @McpToolParam(description = "Data", required = true) String data) {

    progressCallback.accept(ProgressNotification.builder()
        .progress(0.0).total(1.0).message("Starting").build());

    // Perform work
    processData(data);

    progressCallback.accept(ProgressNotification.builder()
        .progress(1.0).total(1.0).message("Complete").build());

    return "Done";
}

@McpProgressToken

Injects the progress token from the request.

/**
 * Injects the progress token from the tool call request
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface McpProgressToken {}

Usage Example:

@McpTool(name = "trackable-task", description = "Task with progress tracking")
public String trackableTask(
        @McpProgressToken String progressToken,
        @McpToolParam(description = "Input", required = true) String input) {

    if (progressToken != null) {
        // Use progress token for tracking
        trackProgress(progressToken, 0);
        performWork(input);
        trackProgress(progressToken, 100);
    }

    return "Complete";
}

@McpLogging

Marks a method parameter to receive logging callback.

/**
 * Marks a parameter to receive logging functionality
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface McpLogging {}

Usage Example:

@McpTool(name = "logged-operation", description = "Operation with logging")
public String loggedOperation(
        @McpLogging BiConsumer<LoggingLevel, String> logger,
        @McpToolParam(description = "Input", required = true) String input) {

    logger.accept(LoggingLevel.INFO, "Starting operation");
    String result = processInput(input);
    logger.accept(LoggingLevel.INFO, "Operation complete");

    return result;
}

@McpElicitation

Marks a method parameter to receive elicitation functionality (stateful mode only).

/**
 * Marks a parameter to receive elicitation functionality
 * Only available in stateful server modes (SSE, Streamable)
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface McpElicitation {}

Usage Example:

public record UserInput(String name, String email) {}

@McpTool(name = "collect-input", description = "Collect user input via elicitation")
public String collectInput(
        @McpElicitation Function<Class<?>, StructuredElicitResult<?>> elicit) {

    StructuredElicitResult<UserInput> result =
        (StructuredElicitResult<UserInput>) elicit.apply(UserInput.class);

    if (result.action() == ElicitResult.Action.ACCEPT) {
        UserInput input = result.data();
        return "Received: " + input.name() + ", " + input.email();
    }

    return "User declined";
}

@McpSampling

Marks a method parameter to receive LLM sampling functionality (stateful mode only).

/**
 * Marks a parameter to receive sampling functionality
 * Only available in stateful server modes (SSE, Streamable)
 */
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@interface McpSampling {}

Usage Example:

@McpTool(name = "enhanced-query", description = "Enhance query with LLM")
public String enhancedQuery(
        @McpSampling Function<String, CreateMessageResult> sample,
        @McpToolParam(description = "Query", required = true) String query) {

    CreateMessageResult llmResult = sample.apply(
        "Enhance this query: " + query);

    String enhancedQuery = llmResult.content();
    return performSearch(enhancedQuery);
}

These advanced annotations provide fine-grained control over MCP features without requiring full request context injection.

tessl i tessl/maven-org-springframework-ai--spring-ai-starter-mcp-server@1.1.0

docs

index.md

tile.json