Skip to content

Commit 2ef64aa

Browse files
authored
feat: Add McpRequestContext support to Complete, Prompt, and Resource methods (spring-ai-community#74)
Add support for McpSyncRequestContext and McpAsyncRequestContext parameters in Complete, Prompt, and Resource method callbacks, providing a unified interface for accessing MCP request context. - Add McpSyncRequestContext and McpAsyncRequestContext parameter support to AbstractMcpCompleteMethodCallback, AbstractMcpPromptMethodCallback, and AbstractMcpResourceMethodCallback - Implement validation to ensure correct context type matches method synchronicity (sync methods use McpSyncRequestContext, async methods use McpAsyncRequestContext) - Add validation to prevent duplicate request context parameters - Fix @McpProgressToken to properly inject progress token from requests (previously returned null for CompleteRequest) - Update README.md documentation to clarify that McpSyncRequestContext, McpAsyncRequestContext, and @McpProgressToken are supported in Complete, Prompt, Resource, and Tool methods - Rename McpProviderUtils into McpPredicates - Add structured elicit result builder - Add test coverage for request context parameters in sync and async Complete, Prompt, and Resource methods - Add tests for request context with URI variables and arguments - Add validation tests for duplicate and mismatched context types - Add tests for non-null progress token handling Signed-off-by: Christian Tzolov <[email protected]>
1 parent b5d3011 commit 2ef64aa

File tree

45 files changed

+1190
-271
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1190
-271
lines changed

README.md

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,35 @@ The core module provides a set of annotations and callback implementations for p
9898

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

101+
### Method Filtering by Server Type
102+
103+
The library automatically filters methods based on the server type and method characteristics:
104+
105+
#### Synchronous vs Asynchronous Servers
106+
107+
- **Synchronous Providers** (`SyncMcpToolProvider`, `SyncMcpResourceProvider`, `SyncMcpPromptProvider`, `SyncMcpCompleteProvider`):
108+
- Accept methods with **non-reactive return types** (e.g., `String`, `int`, `CallToolResult`, `ReadResourceResult`)
109+
- **Filter out** methods returning reactive types (`Mono`, `Flux`, `Publisher`)
110+
- Methods with reactive return types are logged as warnings and skipped
111+
112+
- **Asynchronous Providers** (`AsyncMcpToolProvider`, `AsyncMcpResourceProvider`, `AsyncMcpPromptProvider`, `AsyncMcpCompleteProvider`):
113+
- Accept methods with **reactive return types** (`Mono<T>`, `Flux<T>`, `Publisher<T>`)
114+
- **Filter out** methods with non-reactive return types
115+
- Methods with non-reactive return types are logged as warnings and skipped
116+
117+
#### Stateful vs Stateless Servers
118+
119+
- **Stateful Providers** (using `McpSyncServerExchange` or `McpAsyncServerExchange`):
120+
- Accept methods with **bidirectional parameters**: `McpSyncRequestContext`, `McpAsyncRequestContext`, `McpSyncServerExchange`, `McpAsyncServerExchange`
121+
- Support full server exchange functionality including roots, elicitation, and sampling capabilities
122+
123+
- **Stateless Providers** (`SyncStatelessMcpToolProvider`, `AsyncStatelessMcpToolProvider`, `SyncStatelessMcpResourceProvider`, `AsyncStatelessMcpResourceProvider`, `SyncStatelessMcpPromptProvider`, `AsyncStatelessMcpPromptProvider`, `SyncStatelessMcpCompleteProvider`, `AsyncStatelessMcpCompleteProvider`):
124+
- **Filter out** methods with bidirectional parameters (`McpSyncRequestContext`, `McpAsyncRequestContext`, `McpSyncServerExchange`, `McpAsyncServerExchange`)
125+
- Accept methods with `McpTransportContext` or no context parameter
126+
- Methods with bidirectional parameters are logged as warnings and skipped
127+
- Do not support bidirectional operations (roots, elicitation, sampling)
128+
129+
101130
## Key Components
102131

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

122151
#### Special Parameters and Annotations
123-
- **`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
124-
- **`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
125-
- **(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.
126-
- **(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
152+
- **`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.**
153+
- **`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.**
154+
- **(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.
155+
- **(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.
127156
- **`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
128-
- **(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
129-
**Note:** if using the `McpSyncRequestContext` or `McpAsyncRequestContext` the progress token is handled internally.
157+
- **`@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.**
158+
**Note:** When using `McpSyncRequestContext` or `McpAsyncRequestContext`, the progress token can be accessed via `ctx.request().progressToken()` instead of using this annotation.
130159
- **`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.
131-
**Note:** if using the McpSyncRequestContext or McpAsyncRequestContext the meta can be obatined via `requestMeta()` instead.
160+
**Note:** When using `McpSyncRequestContext` or `McpAsyncRequestContext`, metadata can be obtained via `ctx.requestMeta()` instead.
132161

133162
### Method Callbacks
134163

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

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

200-
#### Stateful Providers (using McpSyncServerExchange/McpAsyncServerExchange)
229+
#### Stateful Providers (using McpSyncRequestContext/McpAsyncRequestContext)
201230
- `SyncMcpCompleteProvider` - Processes `@McpComplete` annotations for synchronous operations
202231
- `AsyncMcpCompleteProvider` - Processes `@McpComplete` annotations for asynchronous operations
203232
- `SyncMcpPromptProvider` - Processes `@McpPrompt` annotations for synchronous operations
@@ -241,14 +270,12 @@ public class PromptProvider {
241270

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

249-
exchange.loggingNotification(LoggingMessageNotification.builder()
250-
.level(LoggingLevel.INFO)
251-
.data("personalized-message event").build());
278+
context.info("personalized-message event");
252279

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

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

390-
exchange.loggingNotification(LoggingMessageNotification.builder()
391-
.level(LoggingLevel.INFO)
392-
.data("user-profile-exchange")
393-
.build());
417+
context.info("user-profile-exchange");
394418

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

@@ -435,15 +459,12 @@ public class CalculatorToolProvider {
435459
return new AreaResult(area, "square units");
436460
}
437461

438-
@McpTool(name = "process-data", description = "Process data with exchange context")
462+
@McpTool(name = "process-data", description = "Process data with request context")
439463
public String processData(
440-
McpSyncServerExchange exchange,
464+
McpSyncRequestContext context,
441465
@McpToolParam(description = "Data to process", required = true) String data) {
442466

443-
exchange.loggingNotification(LoggingMessageNotification.builder()
444-
.level(LoggingLevel.INFO)
445-
.data("Processing data: " + data)
446-
.build());
467+
context.info("Processing data: " + data);
447468

448469
return "Processed: " + data.toUpperCase();
449470
}
@@ -916,7 +937,7 @@ public String processWithContext(
916937
// Check if running in stateful mode
917938
if (!context.isStateless()) {
918939
// Access server exchange for stateful operations
919-
McpSyncServerExchange exchange = context.exchange().orElseThrow();
940+
McpSyncServerExchange exchange = context.exchange();
920941
// Use exchange for additional operations...
921942
}
922943

@@ -2120,7 +2141,7 @@ public class StatelessResourceProvider {
21202141
```
21212142

21222143
**Important Note on Stateless Operations:**
2123-
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.
2144+
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.
21242145

21252146
#### Stateless Tool Example
21262147

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

22432264
- **Annotation-based method handling** - Simplifies the creation and registration of MCP methods
22442265
- **Support for both synchronous and asynchronous operations** - Flexible integration with different application architectures
2245-
- **Stateful and stateless implementations** - Choose between full server exchange context (`McpSyncServerExchange`/`McpAsyncServerExchange`) or lightweight transport context (`McpTransportContext`) for all MCP operations
2266+
- **Stateful and stateless implementations** - Choose between unified request context (`McpSyncRequestContext`/`McpAsyncRequestContext`) or lightweight transport context (`McpTransportContext`) for all MCP operations
22462267
- **Comprehensive stateless support** - All MCP operations (Complete, Prompt, Resource, Tool) support stateless implementations for scenarios where full server context is not needed
22472268
- **Builder pattern for callback creation** - Clean and fluent API for creating method callbacks
22482269
- **Comprehensive validation** - Ensures method signatures are compatible with MCP operations
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springaicommunity.mcp.provider;
17+
package org.springaicommunity.mcp;
1818

1919
import java.lang.reflect.Method;
2020
import java.util.function.Predicate;
@@ -30,9 +30,9 @@
3030
import reactor.core.publisher.Flux;
3131
import reactor.core.publisher.Mono;
3232

33-
public class McpProviderUtils {
33+
public class McpPredicates {
3434

35-
private static final Logger logger = LoggerFactory.getLogger(McpProviderUtils.class);
35+
private static final Logger logger = LoggerFactory.getLogger(McpPredicates.class);
3636

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

mcp-annotations/src/main/java/org/springaicommunity/mcp/context/StructuredElicitResult.java

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44

55
package org.springaicommunity.mcp.context;
66

7+
import java.util.HashMap;
78
import java.util.Map;
89

910
import io.modelcontextprotocol.spec.McpSchema.ElicitResult.Action;
11+
import io.modelcontextprotocol.util.Assert;
1012

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

21+
public static Builder<?> builder() {
22+
return new Builder<>();
23+
}
24+
25+
public static class Builder<T> {
26+
27+
private Action action = Action.ACCEPT;
28+
29+
private T structuredContent;
30+
31+
private Map<String, Object> meta = new HashMap<>();
32+
33+
/**
34+
* Private constructor to enforce builder pattern usage.
35+
*/
36+
private Builder() {
37+
this.meta = new HashMap<>();
38+
}
39+
40+
/**
41+
* Sets the action.
42+
* @param action the action to set
43+
* @return this builder instance
44+
*/
45+
public Builder<T> action(Action action) {
46+
Assert.notNull(action, "Action must not be null");
47+
this.action = action;
48+
return this;
49+
}
50+
51+
/**
52+
* Sets the structured content.
53+
* @param <U> the type of the structured content
54+
* @param structuredContent the structured content to set
55+
* @return this builder instance with the correct type
56+
*/
57+
@SuppressWarnings("unchecked")
58+
public <U> Builder<U> structuredContent(U structuredContent) {
59+
Builder<U> typedBuilder = (Builder<U>) this;
60+
typedBuilder.structuredContent = structuredContent;
61+
return typedBuilder;
62+
}
63+
64+
/**
65+
* Sets the meta map.
66+
* @param meta the meta map to set
67+
* @return this builder instance
68+
*/
69+
public Builder<T> meta(Map<String, Object> meta) {
70+
this.meta = meta != null ? new HashMap<>(meta) : new HashMap<>();
71+
return this;
72+
}
73+
74+
/**
75+
* Adds a single meta entry.
76+
* @param key the meta key
77+
* @param value the meta value
78+
* @return this builder instance
79+
*/
80+
public Builder<T> addMeta(String key, Object value) {
81+
this.meta.put(key, value);
82+
return this;
83+
}
84+
85+
/**
86+
* Builds the {@link StructuredElicitResult} instance.
87+
* @return a new StructuredElicitResult instance
88+
*/
89+
public StructuredElicitResult<T> build() {
90+
return new StructuredElicitResult<>(this.action, this.structuredContent, this.meta);
91+
}
92+
93+
}
94+
1995
}

mcp-annotations/src/main/java/org/springaicommunity/mcp/context/StructuredElicitResultBuilder.java

Lines changed: 0 additions & 93 deletions
This file was deleted.

0 commit comments

Comments
 (0)