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

request-context.mddocs/reference/

Request Context

Request context objects provide access to MCP request metadata, logging, progress notifications, and advanced features like elicitation and sampling.

Capabilities

McpSyncRequestContext

Unified request context for synchronous MCP operations.

/**
 * Request context for synchronous MCP operations
 * Provides access to request metadata, logging, progress, and advanced features
 */
interface McpSyncRequestContext {
    /**
     * Access the MCP request
     */
    CallToolRequest request();

    /**
     * Access MCP metadata provided by the client
     */
    McpMeta meta();

    /**
     * Send info-level log message to client
     */
    void info(String message);

    /**
     * Send debug-level log message to client
     */
    void debug(String message);

    /**
     * Send warning-level log message to client
     */
    void warn(String message);

    /**
     * Send error-level log message to client
     */
    void error(String message);

    /**
     * Send progress notification with simple percentage (0-100)
     */
    void progress(int percentage);

    /**
     * Send custom progress notification using builder
     */
    void progress(Consumer<ProgressNotification.Builder> configurer);

    /**
     * Ping the client to keep connection alive
     */
    void ping();

    /**
     * Check if elicitation is enabled (only available in stateful mode)
     */
    boolean elicitEnabled();

    /**
     * Check if sampling is enabled (only available in stateful mode)
     */
    boolean sampleEnabled();

    /**
     * Request structured user input (stateful mode only)
     * @param type The class representing the expected structure
     * @return StructuredElicitResult containing user input or rejection
     */
    <T> StructuredElicitResult<T> elicit(Class<T> type);

    /**
     * Request structured user input with custom configuration (stateful mode only)
     * @param configurer Builder consumer to configure the elicitation request
     * @param type The class representing the expected structure
     * @return StructuredElicitResult containing user input or rejection
     */
    <T> StructuredElicitResult<T> elicit(Consumer<ElicitRequest.Builder> configurer, Class<T> type);

    /**
     * Request LLM sampling with a prompt (stateful mode only)
     * @param prompt The prompt to send to the LLM
     * @return CreateMessageResult containing the LLM response
     */
    CreateMessageResult sample(String prompt);

    /**
     * Request LLM sampling with custom configuration (stateful mode only)
     * @param configurer Builder consumer to configure the sampling request
     * @return CreateMessageResult containing the LLM response
     */
    CreateMessageResult sample(Consumer<CreateMessageRequest.Builder> configurer);
}

Usage Examples:

Basic logging and progress:

@McpTool(name = "process", description = "Process data with progress tracking")
public String process(
        McpSyncRequestContext context,
        @McpToolParam(description = "Data", required = true) String data) {

    context.info("Starting processing");
    context.progress(0);

    // Step 1
    context.debug("Step 1: Validate data");
    validateData(data);
    context.progress(33);

    // Step 2
    context.info("Step 2: Transform data");
    String transformed = transform(data);
    context.progress(66);

    // Step 3
    context.info("Step 3: Save result");
    save(transformed);
    context.progress(100);

    return "Processing complete";
}

Custom progress with builder:

@McpTool(name = "long-task", description = "Long-running task")
public String longTask(
        McpSyncRequestContext context,
        @McpToolParam(description = "Task name", required = true) String taskName) {

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

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

        // Perform work...
        processTask(taskName);

        context.progress(p -> p
            .progress(0.5)
            .total(1.0)
            .message("Halfway through task"));

        // More work...
        finalizeTask(taskName);

        context.progress(p -> p
            .progress(1.0)
            .total(1.0)
            .message("Task completed"));
    }

    return "Task " + taskName + " completed";
}

Elicitation (stateful only):

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

@McpTool(name = "collect-user-info", description = "Collect user information")
public String collectUserInfo(McpSyncRequestContext context) {

    if (!context.elicitEnabled()) {
        return "Elicitation not available in this mode";
    }

    context.info("Requesting user information");

    StructuredElicitResult<UserInfo> result = context.elicit(UserInfo.class);

    if (result.action() == ElicitResult.Action.ACCEPT) {
        UserInfo info = result.data();
        context.info("Received user info: " + info.name());
        return "User info collected: " + info;
    } else {
        context.warn("User rejected elicitation");
        return "User declined to provide information";
    }
}

Sampling (stateful only):

@McpTool(name = "enhanced-search", description = "Search with LLM enhancement")
public String enhancedSearch(
        McpSyncRequestContext context,
        @McpToolParam(description = "Query", required = true) String query) {

    if (!context.sampleEnabled()) {
        return "Sampling not available";
    }

    context.info("Enhancing query with LLM");

    CreateMessageResult llmResult = context.sample(
        "Enhance this search query to be more specific: " + query);

    String enhancedQuery = extractText(llmResult);

    context.info("Enhanced query: " + enhancedQuery);

    return performSearch(enhancedQuery);
}

Accessing request metadata:

@McpTool(name = "inspect-request", description = "Inspect request details")
public String inspectRequest(McpSyncRequestContext context) {
    CallToolRequest request = context.request();
    McpMeta meta = context.meta();

    StringBuilder info = new StringBuilder();
    info.append("Tool: ").append(request.name()).append("\n");
    info.append("Progress Token: ").append(request.progressToken()).append("\n");
    info.append("Arguments: ").append(request.arguments()).append("\n");

    return info.toString();
}

McpAsyncRequestContext

Unified request context for asynchronous MCP operations (reactive).

/**
 * Request context for asynchronous MCP operations
 * All methods return Mono for reactive composition
 */
interface McpAsyncRequestContext {
    /**
     * Access the MCP request
     */
    CallToolRequest request();

    /**
     * Access MCP metadata
     */
    McpMeta meta();

    /**
     * Send info-level log message
     */
    Mono<Void> info(String message);

    /**
     * Send debug-level log message
     */
    Mono<Void> debug(String message);

    /**
     * Send warning-level log message
     */
    Mono<Void> warn(String message);

    /**
     * Send error-level log message
     */
    Mono<Void> error(String message);

    /**
     * Send progress notification with percentage
     */
    Mono<Void> progress(int percentage);

    /**
     * Send custom progress notification
     */
    Mono<Void> progress(Consumer<ProgressNotification.Builder> configurer);

    /**
     * Ping the client
     */
    Mono<Void> ping();

    /**
     * Check if elicitation is enabled
     */
    boolean elicitEnabled();

    /**
     * Check if sampling is enabled
     */
    boolean sampleEnabled();

    /**
     * Request structured user input (reactive)
     */
    <T> Mono<StructuredElicitResult<T>> elicit(Class<T> type);

    /**
     * Request structured user input with custom configuration (reactive)
     * @param configurer Builder consumer to configure the elicitation request
     * @param type The class representing the expected structure
     * @return Mono of StructuredElicitResult containing user input or rejection
     */
    <T> Mono<StructuredElicitResult<T>> elicit(Consumer<ElicitRequest.Builder> configurer, Class<T> type);

    /**
     * Request LLM sampling (reactive)
     */
    Mono<CreateMessageResult> sample(String prompt);

    /**
     * Request LLM sampling with custom configuration (reactive)
     * @param configurer Builder consumer to configure the sampling request
     * @return Mono of CreateMessageResult containing the LLM response
     */
    Mono<CreateMessageResult> sample(Consumer<CreateMessageRequest.Builder> configurer);
}

Usage Example:

@McpTool(name = "async-process", description = "Async processing")
public Mono<String> asyncProcess(
        McpAsyncRequestContext context,
        @McpToolParam(description = "Data", required = true) String data) {

    return context.info("Starting async processing")
        .then(context.progress(0))
        .then(Mono.defer(() -> processDataAsync(data)))
        .flatMap(result -> context.progress(50).thenReturn(result))
        .flatMap(result -> context.info("Processing complete").thenReturn(result))
        .flatMap(result -> context.progress(100).thenReturn(result))
        .map(result -> "Result: " + result);
}

McpTransportContext

Lightweight transport context for stateless operations.

/**
 * Lightweight context for stateless operations
 * Provides access to transport-level information only
 * No bidirectional operations (logging, progress, elicitation, sampling)
 */
interface McpTransportContext {
    /**
     * Access transport metadata
     */
    Map<String, Object> getTransportMetadata();

    /**
     * Get request identifier
     */
    String getRequestId();
}

Usage Example:

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

    String requestId = context.getRequestId();
    // Process without bidirectional operations
    return "Processed: " + input + " (request: " + requestId + ")";
}

McpMeta

Access to MCP metadata provided by the client.

/**
 * Record providing access to MCP metadata map
 */
record McpMeta(Map<String, Object> map) {
    /**
     * Get metadata as a map
     */
    Map<String, Object> asMap() {
        return map;
    }

    /**
     * Get a specific metadata value
     */
    Object get(String key) {
        return map.get(key);
    }

    /**
     * Check if metadata key exists
     */
    boolean contains(String key) {
        return map.containsKey(key);
    }
}

Usage Example:

@McpTool(name = "check-metadata", description = "Check client metadata")
public String checkMetadata(McpSyncRequestContext context) {
    McpMeta meta = context.meta();

    if (meta.contains("client-version")) {
        String version = (String) meta.get("client-version");
        context.info("Client version: " + version);
    }

    return "Metadata keys: " + String.join(", ", meta.asMap().keySet());
}

Supporting Types

ProgressNotification

/**
 * Progress notification sent to client
 */
class ProgressNotification {
    static Builder builder() { ... }

    interface Builder {
        Builder progressToken(String token);
        Builder progress(double progress);
        Builder total(double total);
        Builder message(String message);
        ProgressNotification build();
    }
}

StructuredElicitResult

/**
 * Result from elicitation request
 */
class StructuredElicitResult<T> {
    /**
     * The action taken by the user
     */
    ElicitResult.Action action();

    /**
     * The structured data if accepted
     */
    T data();
}

enum ElicitResult.Action {
    ACCEPT,
    REJECT
}

CreateMessageResult

/**
 * Result from LLM sampling request
 */
class CreateMessageResult {
    /**
     * The message content from the LLM
     */
    String content();

    /**
     * The role of the message
     */
    Role role();

    /**
     * The model used for sampling
     */
    String model();

    /**
     * Stop reason
     */
    String stopReason();
}

Context Usage Patterns

Simple Operations (No Context)

For simple operations that don't need request 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;
}

Logging and Progress (Sync Context)

For operations that need logging and progress tracking:

@McpTool(name = "with-progress", description = "Operation with progress")
public String withProgress(
        McpSyncRequestContext context,
        @McpToolParam(description = "Input", required = true) String input) {

    context.info("Starting");
    context.progress(0);
    // Work...
    context.progress(50);
    // More work...
    context.progress(100);
    return "Done";
}

Stateless Operations (Transport Context)

For stateless operations with minimal context:

@McpTool(name = "stateless", description = "Stateless operation")
public String stateless(
        McpTransportContext context,
        @McpToolParam(description = "Input", required = true) String input) {
    // Access transport metadata only
    return "Processed";
}

Advanced Operations (Full Context with Elicitation/Sampling)

For operations that need user input or LLM assistance:

@McpTool(name = "interactive", description = "Interactive operation")
public String interactive(McpSyncRequestContext context) {
    if (context.elicitEnabled()) {
        var result = context.elicit(UserInput.class);
        // Use elicited data
    }

    if (context.sampleEnabled()) {
        var llmResult = context.sample("Generate suggestion");
        // Use LLM output
    }

    return "Done";
}

Stateful vs Stateless Comparison

FeatureStateful (SSE/Streamable)Stateless
Logging✓ Supported✗ Not supported
Progress✓ Supported✗ Not supported
Ping✓ Supported✗ Not supported
Elicitation✓ Supported✗ Not supported
Sampling✓ Supported✗ Not supported
Context TypeMcpSyncRequestContext / McpAsyncRequestContextMcpTransportContext

Note: Methods using full request context (McpSyncRequestContext/McpAsyncRequestContext) are automatically filtered out in stateless mode. Use McpTransportContext or no context for stateless compatibility.

Core Imports

// Request context interfaces
import org.springaicommunity.mcp.server.McpSyncRequestContext;
import org.springaicommunity.mcp.server.McpAsyncRequestContext;
import org.springaicommunity.mcp.server.McpTransportContext;

// Schema types used in context
import org.springaicommunity.mcp.schema.McpSchema.CallToolRequest;
import org.springaicommunity.mcp.schema.McpSchema.ProgressNotification;
import org.springaicommunity.mcp.schema.McpSchema.LoggingLevel;
import org.springaicommunity.mcp.schema.McpSchema.CreateMessageResult;
import org.springaicommunity.mcp.schema.McpSchema.ElicitResult;
import org.springaicommunity.mcp.schema.McpSchema.StructuredElicitResult;

// Supporting types
import org.springaicommunity.mcp.schema.McpSchema.McpMeta;

// Reactive types (for async context)
import reactor.core.publisher.Mono;

// Java standard library
import java.util.Map;
import java.util.function.Consumer;
tessl i tessl/maven-org-springframework-ai--spring-ai-starter-mcp-server@1.1.0

docs

index.md

tile.json