Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 48 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,35 @@ The core module provides a set of annotations and callback implementations for p

Each operation type has both synchronous and asynchronous implementations, allowing for flexible integration with different application architectures.

### Method Filtering by Server Type

The library automatically filters methods based on the server type and method characteristics:

#### Synchronous vs Asynchronous Servers

- **Synchronous Providers** (`SyncMcpToolProvider`, `SyncMcpResourceProvider`, `SyncMcpPromptProvider`, `SyncMcpCompleteProvider`):
- Accept methods with **non-reactive return types** (e.g., `String`, `int`, `CallToolResult`, `ReadResourceResult`)
- **Filter out** methods returning reactive types (`Mono`, `Flux`, `Publisher`)
- Methods with reactive return types are logged as warnings and skipped

- **Asynchronous Providers** (`AsyncMcpToolProvider`, `AsyncMcpResourceProvider`, `AsyncMcpPromptProvider`, `AsyncMcpCompleteProvider`):
- Accept methods with **reactive return types** (`Mono<T>`, `Flux<T>`, `Publisher<T>`)
- **Filter out** methods with non-reactive return types
- Methods with non-reactive return types are logged as warnings and skipped

#### Stateful vs Stateless Servers

- **Stateful Providers** (using `McpSyncServerExchange` or `McpAsyncServerExchange`):
- Accept methods with **bidirectional parameters**: `McpSyncRequestContext`, `McpAsyncRequestContext`, `McpSyncServerExchange`, `McpAsyncServerExchange`
- Support full server exchange functionality including roots, elicitation, and sampling capabilities

- **Stateless Providers** (`SyncStatelessMcpToolProvider`, `AsyncStatelessMcpToolProvider`, `SyncStatelessMcpResourceProvider`, `AsyncStatelessMcpResourceProvider`, `SyncStatelessMcpPromptProvider`, `AsyncStatelessMcpPromptProvider`, `SyncStatelessMcpCompleteProvider`, `AsyncStatelessMcpCompleteProvider`):
- **Filter out** methods with bidirectional parameters (`McpSyncRequestContext`, `McpAsyncRequestContext`, `McpSyncServerExchange`, `McpAsyncServerExchange`)
- Accept methods with `McpTransportContext` or no context parameter
- Methods with bidirectional parameters are logged as warnings and skipped
- Do not support bidirectional operations (roots, elicitation, sampling)


## Key Components

### Annotations
Expand All @@ -120,15 +149,15 @@ Each operation type has both synchronous and asynchronous implementations, allow
- **`@McpToolParam`** - Annotates tool method parameters with descriptions and requirement specifications

#### Special Parameters and Annotations
- **`McpSyncRequestContext`** - Special parameter type for synchronous operations that provides a unified interface for accessing MCP request context, including the original request, server exchange (for stateful operations), transport context (for stateless operations), and convenient methods for logging, progress, sampling, and elicitation. This parameter is automatically injected and excluded from JSON schema generation
- **`McpAsyncRequestContext`** - Special parameter type for asynchronous operations that provides the same unified interface as `McpSyncRequestContext` but with reactive (Mono-based) return types. This parameter is automatically injected and excluded from JSON schema generation
- **(Deprecated and replaced by `McpSyncRequestContext`) `McpSyncServerExchange`** - Special parameter type for stateful synchronous operations that provides access to server exchange functionality including logging notifications, progress updates, and other server-side operations. This parameter is automatically injected and excluded from JSON schema generation.
- **(Deprecated and replaced by `McpAsyncRequestContext`) `McpAsyncServerExchange`** - Special parameter type for stateful asynchronous operations that provides access to server exchange functionality with reactive support. This parameter is automatically injected and excluded from JSON schema generation
- **`McpSyncRequestContext`** - Special parameter type for synchronous operations that provides a unified interface for accessing MCP request context, including the original request, server exchange (for stateful operations), transport context (for stateless operations), and convenient methods for logging, progress, sampling, and elicitation. This parameter is automatically injected and excluded from JSON schema generation. **Supported in Complete, Prompt, Resource, and Tool methods.**
- **`McpAsyncRequestContext`** - Special parameter type for asynchronous operations that provides the same unified interface as `McpSyncRequestContext` but with reactive (Mono-based) return types. This parameter is automatically injected and excluded from JSON schema generation. **Supported in Complete, Prompt, Resource, and Tool methods.**
- **(Deprecated and replaced by `McpSyncRequestContext`) `McpSyncServerExchange`** - Legacy parameter type for stateful synchronous operations. Use `McpSyncRequestContext` instead for a unified interface that works with both stateful and stateless operations.
- **(Deprecated and replaced by `McpAsyncRequestContext`) `McpAsyncServerExchange`** - Legacy parameter type for stateful asynchronous operations. Use `McpAsyncRequestContext` instead for a unified interface that works with both stateful and stateless operations.
- **`McpTransportContext`** - Special parameter type for stateless operations that provides lightweight access to transport-level context without full server exchange functionality. This parameter is automatically injected and excluded from JSON schema generation
- **(Deprecated. Handled internally by `McpSyncRequestContext` and `McpAsyncRequestContext`)`@McpProgressToken`** - Marks a method parameter to receive the progress token from the request. This parameter is automatically injected and excluded from the generated JSON schema
**Note:** if using the `McpSyncRequestContext` or `McpAsyncRequestContext` the progress token is handled internally.
- **`@McpProgressToken`** - Marks a method parameter to receive the progress token from the request. This parameter is automatically injected and excluded from the generated JSON schema. **Supported in Complete, Prompt, Resource, and Tool methods.**
**Note:** When using `McpSyncRequestContext` or `McpAsyncRequestContext`, the progress token can be accessed via `ctx.request().progressToken()` instead of using this annotation.
- **`McpMeta`** - Special parameter type that provides access to metadata from MCP requests, notifications, and results. This parameter is automatically injected and excluded from parameter count limits and JSON schema generation.
**Note:** if using the McpSyncRequestContext or McpAsyncRequestContext the meta can be obatined via `requestMeta()` instead.
**Note:** When using `McpSyncRequestContext` or `McpAsyncRequestContext`, metadata can be obtained via `ctx.requestMeta()` instead.

### Method Callbacks

Expand Down Expand Up @@ -197,7 +226,7 @@ The modules provide callback implementations for each operation type:

The project includes provider classes that scan for annotated methods and create appropriate callbacks:

#### Stateful Providers (using McpSyncServerExchange/McpAsyncServerExchange)
#### Stateful Providers (using McpSyncRequestContext/McpAsyncRequestContext)
- `SyncMcpCompleteProvider` - Processes `@McpComplete` annotations for synchronous operations
- `AsyncMcpCompleteProvider` - Processes `@McpComplete` annotations for asynchronous operations
- `SyncMcpPromptProvider` - Processes `@McpPrompt` annotations for synchronous operations
Expand Down Expand Up @@ -241,14 +270,12 @@ public class PromptProvider {

@McpPrompt(name = "personalized-message",
description = "Generates a personalized message based on user information")
public GetPromptResult personalizedMessage(McpSyncServerExchange exchange,
public GetPromptResult personalizedMessage(McpSyncRequestContext context,
@McpArg(name = "name", description = "The user's name", required = true) String name,
@McpArg(name = "age", description = "The user's age", required = false) Integer age,
@McpArg(name = "interests", description = "The user's interests", required = false) String interests) {

exchange.loggingNotification(LoggingMessageNotification.builder()
.level(LoggingLevel.INFO)
.data("personalized-message event").build());
context.info("personalized-message event");

StringBuilder message = new StringBuilder();
message.append("Hello, ").append(name).append("!\n\n");
Expand Down Expand Up @@ -384,13 +411,10 @@ public class MyResourceProvider {

@McpResource(uri = "user-profile-exchange://{username}",
name = "User Profile with Exchange",
description = "Provides user profile information with server exchange context")
public ReadResourceResult getProfileWithExchange(McpSyncServerExchange exchange, String username) {
description = "Provides user profile information with request context")
public ReadResourceResult getProfileWithContext(McpSyncRequestContext context, String username) {

exchange.loggingNotification(LoggingMessageNotification.builder()
.level(LoggingLevel.INFO)
.data("user-profile-exchange")
.build());
context.info("user-profile-exchange");

String profileInfo = formatProfileInfo(userProfiles.getOrDefault(username.toLowerCase(), new HashMap<>()));

Expand Down Expand Up @@ -435,15 +459,12 @@ public class CalculatorToolProvider {
return new AreaResult(area, "square units");
}

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

exchange.loggingNotification(LoggingMessageNotification.builder()
.level(LoggingLevel.INFO)
.data("Processing data: " + data)
.build());
context.info("Processing data: " + data);

return "Processed: " + data.toUpperCase();
}
Expand Down Expand Up @@ -916,7 +937,7 @@ public String processWithContext(
// Check if running in stateful mode
if (!context.isStateless()) {
// Access server exchange for stateful operations
McpSyncServerExchange exchange = context.exchange().orElseThrow();
McpSyncServerExchange exchange = context.exchange();
// Use exchange for additional operations...
}

Expand Down Expand Up @@ -2120,7 +2141,7 @@ public class StatelessResourceProvider {
```

**Important Note on Stateless Operations:**
Stateless server methods cannot use bidirectional parameters like `McpSyncRequestContext`, `McpAsyncRequestContext`, `McpSyncServerExchange`, or `McpAsyncServerExchange`. These parameters require client capabilities (roots, elicitation, sampling) that are not available in stateless mode. Methods with these parameters will be automatically filtered out and not registered as stateless operations.
Stateless server methods can use `McpSyncRequestContext` and `McpAsyncRequestContext`, but bidirectional operations (roots, elicitation, sampling) will not be available. The legacy `McpSyncServerExchange` and `McpAsyncServerExchange` parameters are not supported in stateless mode. Methods using the legacy parameters will be automatically filtered out and not registered as stateless operations.

#### Stateless Tool Example

Expand Down Expand Up @@ -2242,7 +2263,7 @@ Override `AbstractMcpToolProvider#doGetToolCallException()` to customize the exc

- **Annotation-based method handling** - Simplifies the creation and registration of MCP methods
- **Support for both synchronous and asynchronous operations** - Flexible integration with different application architectures
- **Stateful and stateless implementations** - Choose between full server exchange context (`McpSyncServerExchange`/`McpAsyncServerExchange`) or lightweight transport context (`McpTransportContext`) for all MCP operations
- **Stateful and stateless implementations** - Choose between unified request context (`McpSyncRequestContext`/`McpAsyncRequestContext`) or lightweight transport context (`McpTransportContext`) for all MCP operations
- **Comprehensive stateless support** - All MCP operations (Complete, Prompt, Resource, Tool) support stateless implementations for scenarios where full server context is not needed
- **Builder pattern for callback creation** - Clean and fluent API for creating method callbacks
- **Comprehensive validation** - Ensures method signatures are compatible with MCP operations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package org.springaicommunity.mcp.provider;
package org.springaicommunity.mcp;

import java.lang.reflect.Method;
import java.util.function.Predicate;
Expand All @@ -30,9 +30,9 @@
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class McpProviderUtils {
public class McpPredicates {

private static final Logger logger = LoggerFactory.getLogger(McpProviderUtils.class);
private static final Logger logger = LoggerFactory.getLogger(McpPredicates.class);

private static final Pattern URI_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)\\}");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

package org.springaicommunity.mcp.context;

import java.util.HashMap;
import java.util.Map;

import io.modelcontextprotocol.spec.McpSchema.ElicitResult.Action;
import io.modelcontextprotocol.util.Assert;

/**
* A record representing the result of a structured elicit action.
Expand All @@ -16,4 +18,78 @@
*/
public record StructuredElicitResult<T>(Action action, T structuredContent, Map<String, Object> meta) {

public static Builder<?> builder() {
return new Builder<>();
}

public static class Builder<T> {

private Action action = Action.ACCEPT;

private T structuredContent;

private Map<String, Object> meta = new HashMap<>();

/**
* Private constructor to enforce builder pattern usage.
*/
private Builder() {
this.meta = new HashMap<>();
}

/**
* Sets the action.
* @param action the action to set
* @return this builder instance
*/
public Builder<T> action(Action action) {
Assert.notNull(action, "Action must not be null");
this.action = action;
return this;
}

/**
* Sets the structured content.
* @param <U> the type of the structured content
* @param structuredContent the structured content to set
* @return this builder instance with the correct type
*/
@SuppressWarnings("unchecked")
public <U> Builder<U> structuredContent(U structuredContent) {
Builder<U> typedBuilder = (Builder<U>) this;
typedBuilder.structuredContent = structuredContent;
return typedBuilder;
}

/**
* Sets the meta map.
* @param meta the meta map to set
* @return this builder instance
*/
public Builder<T> meta(Map<String, Object> meta) {
this.meta = meta != null ? new HashMap<>(meta) : new HashMap<>();
return this;
}

/**
* Adds a single meta entry.
* @param key the meta key
* @param value the meta value
* @return this builder instance
*/
public Builder<T> addMeta(String key, Object value) {
this.meta.put(key, value);
return this;
}

/**
* Builds the {@link StructuredElicitResult} instance.
* @return a new StructuredElicitResult instance
*/
public StructuredElicitResult<T> build() {
return new StructuredElicitResult<>(this.action, this.structuredContent, this.meta);
}

}

}

This file was deleted.

Loading