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
66 changes: 42 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -920,18 +920,21 @@ public String processWithContext(
// Use exchange for additional operations...
}

// Perform elicitation with default message - returns StructuredElicitResult
Optional<StructuredElicitResult<UserInfo>> result = context.elicit(new TypeReference<UserInfo>() {});

// Or perform elicitation with custom configuration - returns StructuredElicitResult
Optional<StructuredElicitResult<UserInfo>> structuredResult = context.elicit(
e -> e.message("Please provide your information").meta("context", "user-registration"),
new TypeReference<UserInfo>() {}
);

if (structuredResult.isPresent() && structuredResult.get().action() == ElicitResult.Action.ACCEPT) {
UserInfo info = structuredResult.get().structuredContent();
return "Processed: " + data + " for user " + info.name();
// Check if elicitation is supported before using it
if (context.elicitEnabled()) {
// Perform elicitation with default message - returns StructuredElicitResult
StructuredElicitResult<UserInfo> result = context.elicit(new TypeReference<UserInfo>() {});

// Or perform elicitation with custom configuration - returns StructuredElicitResult
StructuredElicitResult<UserInfo> structuredResult = context.elicit(
e -> e.message("Please provide your information").meta("context", "user-registration"),
new TypeReference<UserInfo>() {}
);

if (structuredResult.action() == ElicitResult.Action.ACCEPT) {
UserInfo info = structuredResult.structuredContent();
return "Processed: " + data + " for user " + info.name();
}
}

return "Processed: " + data;
Expand Down Expand Up @@ -962,10 +965,14 @@ public GetPromptResult generateWithContext(
// Log prompt generation
context.info("Generating prompt for topic: " + topic);

// Perform sampling if needed
Optional<CreateMessageResult> samplingResult = context.sample(
"What are the key points about " + topic + "?"
);
// Check if sampling is supported before using it
if (context.sampleEnabled()) {
// Perform sampling if needed
CreateMessageResult samplingResult = context.sample(
"What are the key points about " + topic + "?"
);
// Use sampling result...
}

String message = "Let's discuss " + topic;
return new GetPromptResult("Generated Prompt",
Expand Down Expand Up @@ -1059,16 +1066,24 @@ public Mono<GetPromptResult> asyncGenerateWithContext(
- `log(Consumer<LoggingSpec>)` - Send log messages with custom configuration
- `debug(String)`, `info(String)`, `warn(String)`, `error(String)` - Convenience logging methods
- `progress(int)`, `progress(Consumer<ProgressSpec>)` - Send progress updates
- `elicit(TypeReference<T>)` - Request user input with default message, returns `StructuredElicitResult<T>` with action, typed content, and metadata
- `elicit(Class<T>)` - Request user input with default message using Class type, returns `StructuredElicitResult<T>`
- `elicit(Consumer<ElicitationSpec>, TypeReference<T>)` - Request user input with custom configuration, returns `StructuredElicitResult<T>`
- `elicit(Consumer<ElicitationSpec>, Class<T>)` - Request user input with custom configuration using Class type, returns `StructuredElicitResult<T>`
- `elicit(ElicitRequest)` - Request user input with full control over the elicitation request
- `sample(...)` - Request LLM sampling with various configuration options
- `roots()` - Access root directories (returns `Optional<ListRootsResult>`)
- `rootsEnabled()` - Check if roots capability is supported by the client
- `roots()` - Access root directories (throws `IllegalStateException` if not supported)
- `elicitEnabled()` - Check if elicitation capability is supported by the client
- `elicit(TypeReference<T>)` - Request user input with default message, returns `StructuredElicitResult<T>` with action, typed content, and metadata (throws `IllegalStateException` if not supported)
- `elicit(Class<T>)` - Request user input with default message using Class type, returns `StructuredElicitResult<T>` (throws `IllegalStateException` if not supported)
- `elicit(Consumer<ElicitationSpec>, TypeReference<T>)` - Request user input with custom configuration, returns `StructuredElicitResult<T>` (throws `IllegalStateException` if not supported)
- `elicit(Consumer<ElicitationSpec>, Class<T>)` - Request user input with custom configuration using Class type, returns `StructuredElicitResult<T>` (throws `IllegalStateException` if not supported)
- `elicit(ElicitRequest)` - Request user input with full control over the elicitation request (throws `IllegalStateException` if not supported)
- `sampleEnabled()` - Check if sampling capability is supported by the client
- `sample(...)` - Request LLM sampling with various configuration options (throws `IllegalStateException` if not supported)
- `ping()` - Send ping to check connection

`McpAsyncRequestContext` provides the same methods but with reactive return types (`Mono<T>` instead of `T` or `Optional<T>`).
`McpAsyncRequestContext` provides the same methods but with reactive return types (`Mono<T>` instead of `T`). Methods that throw `IllegalStateException` in sync context return `Mono.error(IllegalStateException)` in async context.

**Important Notes on Capability Checking:**
- Always check capability support using `rootsEnabled()`, `elicitEnabled()`, or `sampleEnabled()` before calling the corresponding methods
- Calling capability methods when not supported will throw `IllegalStateException` (sync) or return `Mono.error()` (async)
- Stateless servers do not support bidirectional operations (roots, elicitation, sampling) and will always return `false` for capability checks

This unified context approach simplifies method signatures and provides a consistent API across different operation types and execution modes (stateful vs stateless, sync vs async).

Expand Down Expand Up @@ -2104,6 +2119,9 @@ 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 Tool Example

```java
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,17 +59,31 @@ private DefaultMcpAsyncRequestContext(McpSchema.Request request, McpAsyncServerE

// Roots

@Override
public Mono<Boolean> rootsEnabled() {
return Mono.just(!(this.exchange.getClientCapabilities() == null
|| this.exchange.getClientCapabilities().roots() == null));
}

@Override
public Mono<ListRootsResult> roots() {
if (this.exchange.getClientCapabilities() == null || this.exchange.getClientCapabilities().roots() == null) {
logger.warn("Roots not supported by the client! Ignoring the roots request for request:" + this.request);
return Mono.empty();
}
return this.exchange.listRoots();
return this.rootsEnabled().flatMap(enabled -> {
if (!enabled) {
return Mono.error(new IllegalStateException(
"Roots not supported by the client: " + this.exchange.getClientInfo()));
}
return this.exchange.listRoots();
});
}

// Elicitation

@Override
public Mono<Boolean> elicitEnabled() {
return Mono.just(!(this.exchange.getClientCapabilities() == null
|| this.exchange.getClientCapabilities().elicitation() == null));
}

@Override
public <T> Mono<StructuredElicitResult<T>> elicit(Consumer<ElicitationSpec> spec, TypeReference<T> type) {
Assert.notNull(type, "Elicitation response type must not be null");
Expand Down Expand Up @@ -112,14 +126,13 @@ public <T> Mono<StructuredElicitResult<T>> elicit(Class<T> type) {
public Mono<ElicitResult> elicit(ElicitRequest elicitRequest) {
Assert.notNull(elicitRequest, "Elicit request must not be null");

if (this.exchange.getClientCapabilities() == null
|| this.exchange.getClientCapabilities().elicitation() == null) {
logger.warn("Elicitation not supported by the client! Ignoring the elicitation request for request:"
+ elicitRequest);
return Mono.empty();
}

return this.exchange.createElicitation(elicitRequest);
return this.elicitEnabled().flatMap(enabled -> {
if (!enabled) {
return Mono.error(new IllegalStateException(
"Elicitation not supported by the client: " + this.exchange.getClientInfo()));
}
return this.exchange.createElicitation(elicitRequest);
});
}

public Mono<ElicitResult> elicitationInternal(String message, Type type, Map<String, Object> meta) {
Expand All @@ -143,6 +156,12 @@ private Map<String, Object> generateElicitSchema(Type type) {

// Sampling

@Override
public Mono<Boolean> sampleEnabled() {
return Mono.just(!(this.exchange.getClientCapabilities() == null
|| this.exchange.getClientCapabilities().sampling() == null));
}

@Override
public Mono<CreateMessageResult> sample(String... messages) {
return this.sample(s -> s.message(messages));
Expand Down Expand Up @@ -176,14 +195,13 @@ public Mono<CreateMessageResult> sample(Consumer<SamplingSpec> samplingSpec) {
@Override
public Mono<CreateMessageResult> sample(CreateMessageRequest createMessageRequest) {

// check if supported
if (this.exchange.getClientCapabilities() == null || this.exchange.getClientCapabilities().sampling() == null) {
logger.warn("Sampling not supported by the client! Ignoring the sampling request for messages:"
+ createMessageRequest);
return Mono.empty();
}

return this.exchange.createMessage(createMessageRequest);
return this.sampleEnabled().flatMap(enabled -> {
if (!enabled) {
return Mono.error(new IllegalStateException(
"Sampling not supported by the client: " + this.exchange.getClientInfo()));
}
return this.exchange.createMessage(createMessageRequest);
});
}

// Progress
Expand Down Expand Up @@ -317,10 +335,6 @@ public static class Builder {

private McpAsyncServerExchange exchange;

private boolean isStateless = false;

private McpTransportContext transportContext;

private Builder() {
}

Expand All @@ -334,178 +348,10 @@ public Builder exchange(McpAsyncServerExchange exchange) {
return this;
}

public Builder stateless(boolean isStateless) {
this.isStateless = isStateless;
return this;
}

public Builder transportContext(McpTransportContext transportContext) {
this.transportContext = transportContext;
return this;
}

public McpAsyncRequestContext build() {
if (this.isStateless) {
return new StatelessAsyncRequestContext(this.request, this.transportContext);
}
return new DefaultMcpAsyncRequestContext(this.request, this.exchange);
}

}

private static class StatelessAsyncRequestContext implements McpAsyncRequestContext {

private final McpSchema.Request request;

private McpTransportContext transportContext;

public StatelessAsyncRequestContext(McpSchema.Request request, McpTransportContext transportContext) {
this.request = request;
this.transportContext = transportContext;
}

@Override
public Mono<ListRootsResult> roots() {
logger.warn("Roots not supported by the client! Ignoring the roots request");
return Mono.empty();
}

@Override
public <T> Mono<StructuredElicitResult<T>> elicit(Consumer<ElicitationSpec> spec, TypeReference<T> returnType) {
logger.warn("Elicitation not supported by the client! Ignoring the elicitation request");
return Mono.empty();
}

@Override
public <T> Mono<StructuredElicitResult<T>> elicit(TypeReference<T> type) {
logger.warn("Elicitation not supported by the client! Ignoring the elicitation request");
return Mono.empty();
}

@Override
public <T> Mono<StructuredElicitResult<T>> elicit(Consumer<ElicitationSpec> spec, Class<T> returnType) {
logger.warn("Elicitation not supported by the client! Ignoring the elicitation request");
return Mono.empty();
}

@Override
public <T> Mono<StructuredElicitResult<T>> elicit(Class<T> type) {
logger.warn("Elicitation not supported by the client! Ignoring the elicitation request");
return Mono.empty();
}

@Override
public Mono<ElicitResult> elicit(ElicitRequest elicitRequest) {
logger.warn("Elicitation not supported by the client! Ignoring the elicitation request");
return Mono.empty();
}

@Override
public Mono<CreateMessageResult> sample(String... messages) {
logger.warn("Sampling not supported by the client! Ignoring the sampling request");
return Mono.empty();
}

@Override
public Mono<CreateMessageResult> sample(Consumer<SamplingSpec> samplingSpec) {
logger.warn("Sampling not supported by the client! Ignoring the sampling request");
return Mono.empty();
}

@Override
public Mono<CreateMessageResult> sample(CreateMessageRequest createMessageRequest) {
logger.warn("Sampling not supported by the client! Ignoring the sampling request");
return Mono.empty();
}

@Override
public Mono<Void> progress(int progress) {
logger.warn("Progress not supported by the client! Ignoring the progress request");
return Mono.empty();
}

@Override
public Mono<Void> progress(Consumer<ProgressSpec> progressSpec) {
logger.warn("Progress not supported by the client! Ignoring the progress request");
return Mono.empty();
}

@Override
public Mono<Void> progress(ProgressNotification progressNotification) {
logger.warn("Progress not supported by the client! Ignoring the progress request");
return Mono.empty();
}

@Override
public Mono<Object> ping() {
logger.warn("Ping not supported by the client! Ignoring the ping request");
return Mono.empty();
}

@Override
public Mono<Void> log(Consumer<LoggingSpec> logSpec) {
logger.warn("Logging not supported by the client! Ignoring the logging request");
return Mono.empty();
}

@Override
public Mono<Void> debug(String message) {
logger.warn("Debug not supported by the client! Ignoring the debug request");
return Mono.empty();
}

@Override
public Mono<Void> info(String message) {
logger.warn("Info not supported by the client! Ignoring the info request");
return Mono.empty();
}

@Override
public Mono<Void> warn(String message) {
logger.warn("Warn not supported by the client! Ignoring the warn request");
return Mono.empty();
}

@Override
public Mono<Void> error(String message) {
logger.warn("Error not supported by the client! Ignoring the error request");
return Mono.empty();
}

// Getters

public McpSchema.Request request() {
return this.request;
}

public McpAsyncServerExchange exchange() {
logger.warn("Stateless servers do not support exchange! Returning null");
return null;
}

public String sessionId() {
logger.warn("Stateless servers do not support session ID! Returning null");
return null;
}

public Implementation clientInfo() {
logger.warn("Stateless servers do not support client info! Returning null");
return null;
}

public ClientCapabilities clientCapabilities() {
logger.warn("Stateless servers do not support client capabilities! Returning null");
return null;
}

public Map<String, Object> requestMeta() {
return this.request.meta();
}

public McpTransportContext transportContext() {
return transportContext;
}

}

}
Loading