feat(completion): implement completion API support (#141)
Add completion API support to the MCP protocol implementation: - Add CompleteRequest and CompleteResult schema classes - Implement completion capabilities in ServerCapabilities - Add completion handlers in McpAsyncServer and McpServer - Add completion client methods in McpAsyncClient and McpSyncClient - Add CompletionRefKey and completion specifications in McpServerFeatures - Add integration test for completion functionality - Fix isPresent() check to use isEmpty() in WebMvcSseServerTransportProvider - Replace McpServerFeatures.CompletionRefKey by McpSchemaCompleteReference Co-authored-by: Christian Tzolov <christian.tzolov@broadcom.com> Signed-off-by: jitokim <pigberger70@gmail.com>
This commit is contained in:
parent
84adde16a5
commit
7853efefda
9 changed files with 340 additions and 33 deletions
|
|
@ -10,6 +10,7 @@ import java.util.Map;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
@ -20,19 +21,12 @@ import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
|
|||
import io.modelcontextprotocol.server.McpServer;
|
||||
import io.modelcontextprotocol.server.McpServerFeatures;
|
||||
import io.modelcontextprotocol.server.TestUtil;
|
||||
import io.modelcontextprotocol.server.McpSyncServerExchange;
|
||||
import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider;
|
||||
import io.modelcontextprotocol.spec.McpError;
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
|
||||
import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities;
|
||||
import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest;
|
||||
import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult;
|
||||
import io.modelcontextprotocol.spec.McpSchema.InitializeResult;
|
||||
import io.modelcontextprotocol.spec.McpSchema.ModelPreferences;
|
||||
import io.modelcontextprotocol.spec.McpSchema.Role;
|
||||
import io.modelcontextprotocol.spec.McpSchema.Root;
|
||||
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
|
||||
import io.modelcontextprotocol.spec.McpSchema.Tool;
|
||||
import io.modelcontextprotocol.spec.McpSchema.*;
|
||||
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities.CompletionCapabilities;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
|
|
@ -759,4 +753,53 @@ class WebFluxSseIntegrationTests {
|
|||
mcpServer.close();
|
||||
}
|
||||
|
||||
}
|
||||
// ---------------------------------------
|
||||
// Completion Tests
|
||||
// ---------------------------------------
|
||||
@ParameterizedTest(name = "{0} : Completion call")
|
||||
@ValueSource(strings = { "httpclient", "webflux" })
|
||||
void testCompletionShouldReturnExpectedSuggestions(String clientType) {
|
||||
var clientBuilder = clientBuilders.get(clientType);
|
||||
|
||||
var expectedValues = List.of("python", "pytorch", "pyside");
|
||||
var completionResponse = new McpSchema.CompleteResult(new CompleteResult.CompleteCompletion(expectedValues, 10, // total
|
||||
true // hasMore
|
||||
));
|
||||
|
||||
AtomicReference<CompleteRequest> samplingRequest = new AtomicReference<>();
|
||||
BiFunction<McpSyncServerExchange, CompleteRequest, CompleteResult> completionHandler = (mcpSyncServerExchange,
|
||||
request) -> {
|
||||
samplingRequest.set(request);
|
||||
return completionResponse;
|
||||
};
|
||||
|
||||
var mcpServer = McpServer.sync(mcpServerTransportProvider)
|
||||
.capabilities(ServerCapabilities.builder().completions(new CompletionCapabilities()).build())
|
||||
.prompts(new McpServerFeatures.SyncPromptSpecification(
|
||||
new Prompt("code_review", "this is code review prompt", List.of()),
|
||||
(mcpSyncServerExchange, getPromptRequest) -> null))
|
||||
.completions(new McpServerFeatures.SyncCompletionSpecification(
|
||||
new McpSchema.PromptReference("ref/prompt", "code_review"), completionHandler))
|
||||
.build();
|
||||
|
||||
try (var mcpClient = clientBuilder.build()) {
|
||||
|
||||
InitializeResult initResult = mcpClient.initialize();
|
||||
assertThat(initResult).isNotNull();
|
||||
|
||||
CompleteRequest request = new CompleteRequest(new PromptReference("ref/prompt", "code_review"),
|
||||
new CompleteRequest.CompleteArgument("language", "py"));
|
||||
|
||||
CompleteResult result = mcpClient.completeCompletion(request);
|
||||
|
||||
assertThat(result).isNotNull();
|
||||
|
||||
assertThat(samplingRequest.get().argument().name()).isEqualTo("language");
|
||||
assertThat(samplingRequest.get().argument().value()).isEqualTo("py");
|
||||
assertThat(samplingRequest.get().ref().type()).isEqualTo("ref/prompt");
|
||||
}
|
||||
|
||||
mcpServer.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -300,7 +300,7 @@ public class WebMvcSseServerTransportProvider implements McpServerTransportProvi
|
|||
return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).body("Server is shutting down");
|
||||
}
|
||||
|
||||
if (!request.param("sessionId").isPresent()) {
|
||||
if (request.param("sessionId").isEmpty()) {
|
||||
return ServerResponse.badRequest().body(new McpError("Session ID missing in message endpoint"));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ import reactor.core.publisher.Sinks;
|
|||
*
|
||||
* @author Dariusz Jędrzejczyk
|
||||
* @author Christian Tzolov
|
||||
* @author Jihoon Kim
|
||||
* @see McpClient
|
||||
* @see McpSchema
|
||||
* @see McpClientSession
|
||||
|
|
@ -816,4 +817,25 @@ public class McpAsyncClient {
|
|||
this.protocolVersions = protocolVersions;
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Completions
|
||||
// --------------------------
|
||||
private static final TypeReference<McpSchema.CompleteResult> COMPLETION_COMPLETE_RESULT_TYPE_REF = new TypeReference<>() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a completion/complete request to generate value suggestions based on a given
|
||||
* reference and argument. This is typically used to provide auto-completion options
|
||||
* for user input fields.
|
||||
* @param completeRequest The request containing the prompt or resource reference and
|
||||
* argument for which to generate completions.
|
||||
* @return A Mono that completes with the result containing completion suggestions.
|
||||
* @see McpSchema.CompleteRequest
|
||||
* @see McpSchema.CompleteResult
|
||||
*/
|
||||
public Mono<McpSchema.CompleteResult> completeCompletion(McpSchema.CompleteRequest completeRequest) {
|
||||
return this.withInitializationCheck("complete completions", initializedResult -> this.mcpSession
|
||||
.sendRequest(McpSchema.METHOD_COMPLETION_COMPLETE, completeRequest, COMPLETION_COMPLETE_RESULT_TYPE_REF));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import org.slf4j.LoggerFactory;
|
|||
*
|
||||
* @author Dariusz Jędrzejczyk
|
||||
* @author Christian Tzolov
|
||||
* @author Jihoon Kim
|
||||
* @see McpClient
|
||||
* @see McpAsyncClient
|
||||
* @see McpSchema
|
||||
|
|
@ -334,4 +335,14 @@ public class McpSyncClient implements AutoCloseable {
|
|||
this.delegate.setLoggingLevel(loggingLevel).block();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a completion/complete request.
|
||||
* @param completeRequest the completion request contains the prompt or resource
|
||||
* reference and arguments for generating suggestions.
|
||||
* @return the completion result containing suggested values.
|
||||
*/
|
||||
public McpSchema.CompleteResult completeCompletion(McpSchema.CompleteRequest completeRequest) {
|
||||
return this.delegate.completeCompletion(completeRequest).block();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ import reactor.core.publisher.Mono;
|
|||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Dariusz Jędrzejczyk
|
||||
* @author Jihoon Kim
|
||||
* @see McpServer
|
||||
* @see McpSchema
|
||||
* @see McpClientSession
|
||||
|
|
@ -269,6 +270,8 @@ public class McpAsyncServer {
|
|||
// broadcasting loggingNotification.
|
||||
private LoggingLevel minLoggingLevel = LoggingLevel.DEBUG;
|
||||
|
||||
private final ConcurrentHashMap<McpSchema.CompleteReference, McpServerFeatures.AsyncCompletionSpecification> completions = new ConcurrentHashMap<>();
|
||||
|
||||
private List<String> protocolVersions = List.of(McpSchema.LATEST_PROTOCOL_VERSION);
|
||||
|
||||
AsyncServerImpl(McpServerTransportProvider mcpTransportProvider, ObjectMapper objectMapper,
|
||||
|
|
@ -282,6 +285,7 @@ public class McpAsyncServer {
|
|||
this.resources.putAll(features.resources());
|
||||
this.resourceTemplates.addAll(features.resourceTemplates());
|
||||
this.prompts.putAll(features.prompts());
|
||||
this.completions.putAll(features.completions());
|
||||
|
||||
Map<String, McpServerSession.RequestHandler<?>> requestHandlers = new HashMap<>();
|
||||
|
||||
|
|
@ -314,6 +318,11 @@ public class McpAsyncServer {
|
|||
requestHandlers.put(McpSchema.METHOD_LOGGING_SET_LEVEL, setLoggerRequestHandler());
|
||||
}
|
||||
|
||||
// Add completion API handlers if the completion capability is enabled
|
||||
if (this.serverCapabilities.completions() != null) {
|
||||
requestHandlers.put(McpSchema.METHOD_COMPLETION_COMPLETE, completionCompleteRequestHandler());
|
||||
}
|
||||
|
||||
Map<String, McpServerSession.NotificationHandler> notificationHandlers = new HashMap<>();
|
||||
|
||||
notificationHandlers.put(McpSchema.METHOD_NOTIFICATION_INITIALIZED, (exchange, params) -> Mono.empty());
|
||||
|
|
@ -706,6 +715,81 @@ public class McpAsyncServer {
|
|||
};
|
||||
}
|
||||
|
||||
private McpServerSession.RequestHandler<McpSchema.CompleteResult> completionCompleteRequestHandler() {
|
||||
return (exchange, params) -> {
|
||||
McpSchema.CompleteRequest request = parseCompletionParams(params);
|
||||
|
||||
if (request.ref() == null) {
|
||||
return Mono.error(new McpError("ref must not be null"));
|
||||
}
|
||||
|
||||
if (request.ref().type() == null) {
|
||||
return Mono.error(new McpError("type must not be null"));
|
||||
}
|
||||
|
||||
String type = request.ref().type();
|
||||
|
||||
// check if the referenced resource exists
|
||||
if (type.equals("ref/prompt") && request.ref() instanceof McpSchema.PromptReference promptReference) {
|
||||
McpServerFeatures.AsyncPromptSpecification prompt = this.prompts.get(promptReference.name());
|
||||
if (prompt == null) {
|
||||
return Mono.error(new McpError("Prompt not found: " + promptReference.name()));
|
||||
}
|
||||
}
|
||||
|
||||
if (type.equals("ref/resource")
|
||||
&& request.ref() instanceof McpSchema.ResourceReference resourceReference) {
|
||||
McpServerFeatures.AsyncResourceSpecification resource = this.resources.get(resourceReference.uri());
|
||||
if (resource == null) {
|
||||
return Mono.error(new McpError("Resource not found: " + resourceReference.uri()));
|
||||
}
|
||||
}
|
||||
|
||||
McpServerFeatures.AsyncCompletionSpecification specification = this.completions.get(request.ref());
|
||||
|
||||
if (specification == null) {
|
||||
return Mono.error(new McpError("AsyncCompletionSpecification not found: " + request.ref()));
|
||||
}
|
||||
|
||||
return specification.completionHandler().apply(exchange, request);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the raw JSON-RPC request parameters into a
|
||||
* {@link McpSchema.CompleteRequest} object.
|
||||
* <p>
|
||||
* This method manually extracts the `ref` and `argument` fields from the input
|
||||
* map, determines the correct reference type (either prompt or resource), and
|
||||
* constructs a fully-typed {@code CompleteRequest} instance.
|
||||
* @param object the raw request parameters, expected to be a Map containing "ref"
|
||||
* and "argument" entries.
|
||||
* @return a {@link McpSchema.CompleteRequest} representing the structured
|
||||
* completion request.
|
||||
* @throws IllegalArgumentException if the "ref" type is not recognized.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private McpSchema.CompleteRequest parseCompletionParams(Object object) {
|
||||
Map<String, Object> params = (Map<String, Object>) object;
|
||||
Map<String, Object> refMap = (Map<String, Object>) params.get("ref");
|
||||
Map<String, Object> argMap = (Map<String, Object>) params.get("argument");
|
||||
|
||||
String refType = (String) refMap.get("type");
|
||||
|
||||
McpSchema.CompleteReference ref = switch (refType) {
|
||||
case "ref/prompt" -> new McpSchema.PromptReference(refType, (String) refMap.get("name"));
|
||||
case "ref/resource" -> new McpSchema.ResourceReference(refType, (String) refMap.get("uri"));
|
||||
default -> throw new IllegalArgumentException("Invalid ref type: " + refType);
|
||||
};
|
||||
|
||||
String argName = (String) argMap.get("name");
|
||||
String argValue = (String) argMap.get("value");
|
||||
McpSchema.CompleteRequest.CompleteArgument argument = new McpSchema.CompleteRequest.CompleteArgument(
|
||||
argName, argValue);
|
||||
|
||||
return new McpSchema.CompleteRequest(ref, argument);
|
||||
}
|
||||
|
||||
// ---------------------------------------
|
||||
// Sampling
|
||||
// ---------------------------------------
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ import reactor.core.publisher.Mono;
|
|||
*
|
||||
* @author Christian Tzolov
|
||||
* @author Dariusz Jędrzejczyk
|
||||
* @author Jihoon Kim
|
||||
* @see McpAsyncServer
|
||||
* @see McpSyncServer
|
||||
* @see McpServerTransportProvider
|
||||
|
|
@ -192,6 +193,8 @@ public interface McpServer {
|
|||
*/
|
||||
private final Map<String, McpServerFeatures.AsyncPromptSpecification> prompts = new HashMap<>();
|
||||
|
||||
private final Map<McpSchema.CompleteReference, McpServerFeatures.AsyncCompletionSpecification> completions = new HashMap<>();
|
||||
|
||||
private final List<BiFunction<McpAsyncServerExchange, List<McpSchema.Root>, Mono<Void>>> rootsChangeHandlers = new ArrayList<>();
|
||||
|
||||
private Duration requestTimeout = Duration.ofSeconds(10); // Default timeout
|
||||
|
|
@ -581,7 +584,8 @@ public interface McpServer {
|
|||
*/
|
||||
public McpAsyncServer build() {
|
||||
var features = new McpServerFeatures.Async(this.serverInfo, this.serverCapabilities, this.tools,
|
||||
this.resources, this.resourceTemplates, this.prompts, this.rootsChangeHandlers, this.instructions);
|
||||
this.resources, this.resourceTemplates, this.prompts, this.completions, this.rootsChangeHandlers,
|
||||
this.instructions);
|
||||
var mapper = this.objectMapper != null ? this.objectMapper : new ObjectMapper();
|
||||
return new McpAsyncServer(this.transportProvider, mapper, features, this.requestTimeout);
|
||||
}
|
||||
|
|
@ -635,6 +639,8 @@ public interface McpServer {
|
|||
*/
|
||||
private final Map<String, McpServerFeatures.SyncPromptSpecification> prompts = new HashMap<>();
|
||||
|
||||
private final Map<McpSchema.CompleteReference, McpServerFeatures.SyncCompletionSpecification> completions = new HashMap<>();
|
||||
|
||||
private final List<BiConsumer<McpSyncServerExchange, List<McpSchema.Root>>> rootsChangeHandlers = new ArrayList<>();
|
||||
|
||||
private Duration requestTimeout = Duration.ofSeconds(10); // Default timeout
|
||||
|
|
@ -957,6 +963,37 @@ public interface McpServer {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers multiple completions with their handlers using a List. This method is
|
||||
* useful when completions need to be added in bulk from a collection.
|
||||
* @param completions List of completion specifications. Must not be null.
|
||||
* @return This builder instance for method chaining
|
||||
* @throws IllegalArgumentException if completions is null
|
||||
* @see #completions(McpServerFeatures.SyncCompletionSpecification...)
|
||||
*/
|
||||
public SyncSpecification completions(List<McpServerFeatures.SyncCompletionSpecification> completions) {
|
||||
Assert.notNull(completions, "Completions list must not be null");
|
||||
for (McpServerFeatures.SyncCompletionSpecification completion : completions) {
|
||||
this.completions.put(completion.referenceKey(), completion);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers multiple completions with their handlers using varargs. This method
|
||||
* is useful when completions are defined inline and added directly.
|
||||
* @param completions Array of completion specifications. Must not be null.
|
||||
* @return This builder instance for method chaining
|
||||
* @throws IllegalArgumentException if completions is null
|
||||
*/
|
||||
public SyncSpecification completions(McpServerFeatures.SyncCompletionSpecification... completions) {
|
||||
Assert.notNull(completions, "Completions list must not be null");
|
||||
for (McpServerFeatures.SyncCompletionSpecification completion : completions) {
|
||||
this.completions.put(completion.referenceKey(), completion);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a consumer that will be notified when the list of roots changes. This
|
||||
* is useful for updating resource availability dynamically, such as when new
|
||||
|
|
@ -1023,8 +1060,8 @@ public interface McpServer {
|
|||
*/
|
||||
public McpSyncServer build() {
|
||||
McpServerFeatures.Sync syncFeatures = new McpServerFeatures.Sync(this.serverInfo, this.serverCapabilities,
|
||||
this.tools, this.resources, this.resourceTemplates, this.prompts, this.rootsChangeHandlers,
|
||||
this.instructions);
|
||||
this.tools, this.resources, this.resourceTemplates, this.prompts, this.completions,
|
||||
this.rootsChangeHandlers, this.instructions);
|
||||
McpServerFeatures.Async asyncFeatures = McpServerFeatures.Async.fromSync(syncFeatures);
|
||||
var mapper = this.objectMapper != null ? this.objectMapper : new ObjectMapper();
|
||||
var asyncServer = new McpAsyncServer(this.transportProvider, mapper, asyncFeatures, this.requestTimeout);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import reactor.core.scheduler.Schedulers;
|
|||
* MCP server features specification that a particular server can choose to support.
|
||||
*
|
||||
* @author Dariusz Jędrzejczyk
|
||||
* @author Jihoon Kim
|
||||
*/
|
||||
public class McpServerFeatures {
|
||||
|
||||
|
|
@ -41,6 +42,7 @@ public class McpServerFeatures {
|
|||
List<McpServerFeatures.AsyncToolSpecification> tools, Map<String, AsyncResourceSpecification> resources,
|
||||
List<McpSchema.ResourceTemplate> resourceTemplates,
|
||||
Map<String, McpServerFeatures.AsyncPromptSpecification> prompts,
|
||||
Map<McpSchema.CompleteReference, McpServerFeatures.AsyncCompletionSpecification> completions,
|
||||
List<BiFunction<McpAsyncServerExchange, List<McpSchema.Root>, Mono<Void>>> rootsChangeConsumers,
|
||||
String instructions) {
|
||||
|
||||
|
|
@ -60,6 +62,7 @@ public class McpServerFeatures {
|
|||
List<McpServerFeatures.AsyncToolSpecification> tools, Map<String, AsyncResourceSpecification> resources,
|
||||
List<McpSchema.ResourceTemplate> resourceTemplates,
|
||||
Map<String, McpServerFeatures.AsyncPromptSpecification> prompts,
|
||||
Map<McpSchema.CompleteReference, McpServerFeatures.AsyncCompletionSpecification> completions,
|
||||
List<BiFunction<McpAsyncServerExchange, List<McpSchema.Root>, Mono<Void>>> rootsChangeConsumers,
|
||||
String instructions) {
|
||||
|
||||
|
|
@ -67,7 +70,8 @@ public class McpServerFeatures {
|
|||
|
||||
this.serverInfo = serverInfo;
|
||||
this.serverCapabilities = (serverCapabilities != null) ? serverCapabilities
|
||||
: new McpSchema.ServerCapabilities(null, // experimental
|
||||
: new McpSchema.ServerCapabilities(null, // completions
|
||||
null, // experimental
|
||||
new McpSchema.ServerCapabilities.LoggingCapabilities(), // Enable
|
||||
// logging
|
||||
// by
|
||||
|
|
@ -81,6 +85,7 @@ public class McpServerFeatures {
|
|||
this.resources = (resources != null) ? resources : Map.of();
|
||||
this.resourceTemplates = (resourceTemplates != null) ? resourceTemplates : List.of();
|
||||
this.prompts = (prompts != null) ? prompts : Map.of();
|
||||
this.completions = (completions != null) ? completions : Map.of();
|
||||
this.rootsChangeConsumers = (rootsChangeConsumers != null) ? rootsChangeConsumers : List.of();
|
||||
this.instructions = instructions;
|
||||
}
|
||||
|
|
@ -109,6 +114,11 @@ public class McpServerFeatures {
|
|||
prompts.put(key, AsyncPromptSpecification.fromSync(prompt));
|
||||
});
|
||||
|
||||
Map<McpSchema.CompleteReference, McpServerFeatures.AsyncCompletionSpecification> completions = new HashMap<>();
|
||||
syncSpec.completions().forEach((key, completion) -> {
|
||||
completions.put(key, AsyncCompletionSpecification.fromSync(completion));
|
||||
});
|
||||
|
||||
List<BiFunction<McpAsyncServerExchange, List<McpSchema.Root>, Mono<Void>>> rootChangeConsumers = new ArrayList<>();
|
||||
|
||||
for (var rootChangeConsumer : syncSpec.rootsChangeConsumers()) {
|
||||
|
|
@ -118,7 +128,7 @@ public class McpServerFeatures {
|
|||
}
|
||||
|
||||
return new Async(syncSpec.serverInfo(), syncSpec.serverCapabilities(), tools, resources,
|
||||
syncSpec.resourceTemplates(), prompts, rootChangeConsumers, syncSpec.instructions());
|
||||
syncSpec.resourceTemplates(), prompts, completions, rootChangeConsumers, syncSpec.instructions());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,6 +150,7 @@ public class McpServerFeatures {
|
|||
Map<String, McpServerFeatures.SyncResourceSpecification> resources,
|
||||
List<McpSchema.ResourceTemplate> resourceTemplates,
|
||||
Map<String, McpServerFeatures.SyncPromptSpecification> prompts,
|
||||
Map<McpSchema.CompleteReference, McpServerFeatures.SyncCompletionSpecification> completions,
|
||||
List<BiConsumer<McpSyncServerExchange, List<McpSchema.Root>>> rootsChangeConsumers, String instructions) {
|
||||
|
||||
/**
|
||||
|
|
@ -159,6 +170,7 @@ public class McpServerFeatures {
|
|||
Map<String, McpServerFeatures.SyncResourceSpecification> resources,
|
||||
List<McpSchema.ResourceTemplate> resourceTemplates,
|
||||
Map<String, McpServerFeatures.SyncPromptSpecification> prompts,
|
||||
Map<McpSchema.CompleteReference, McpServerFeatures.SyncCompletionSpecification> completions,
|
||||
List<BiConsumer<McpSyncServerExchange, List<McpSchema.Root>>> rootsChangeConsumers,
|
||||
String instructions) {
|
||||
|
||||
|
|
@ -166,7 +178,8 @@ public class McpServerFeatures {
|
|||
|
||||
this.serverInfo = serverInfo;
|
||||
this.serverCapabilities = (serverCapabilities != null) ? serverCapabilities
|
||||
: new McpSchema.ServerCapabilities(null, // experimental
|
||||
: new McpSchema.ServerCapabilities(null, // completions
|
||||
null, // experimental
|
||||
new McpSchema.ServerCapabilities.LoggingCapabilities(), // Enable
|
||||
// logging
|
||||
// by
|
||||
|
|
@ -180,6 +193,7 @@ public class McpServerFeatures {
|
|||
this.resources = (resources != null) ? resources : new HashMap<>();
|
||||
this.resourceTemplates = (resourceTemplates != null) ? resourceTemplates : new ArrayList<>();
|
||||
this.prompts = (prompts != null) ? prompts : new HashMap<>();
|
||||
this.completions = (completions != null) ? completions : new HashMap<>();
|
||||
this.rootsChangeConsumers = (rootsChangeConsumers != null) ? rootsChangeConsumers : new ArrayList<>();
|
||||
this.instructions = instructions;
|
||||
}
|
||||
|
|
@ -325,6 +339,44 @@ public class McpServerFeatures {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specification of a completion handler function with asynchronous execution support.
|
||||
* Completions generate AI model outputs based on prompt or resource references and
|
||||
* user-provided arguments. This abstraction enables:
|
||||
* <ul>
|
||||
* <li>Customizable response generation logic
|
||||
* <li>Parameter-driven template expansion
|
||||
* <li>Dynamic interaction with connected clients
|
||||
* </ul>
|
||||
*
|
||||
* @param referenceKey The unique key representing the completion reference.
|
||||
* @param completionHandler The asynchronous function that processes completion
|
||||
* requests and returns results. The first argument is an
|
||||
* {@link McpAsyncServerExchange} used to interact with the client. The second
|
||||
* argument is a {@link io.modelcontextprotocol.spec.McpSchema.CompleteRequest}.
|
||||
*/
|
||||
public record AsyncCompletionSpecification(McpSchema.CompleteReference referenceKey,
|
||||
BiFunction<McpAsyncServerExchange, McpSchema.CompleteRequest, Mono<McpSchema.CompleteResult>> completionHandler) {
|
||||
|
||||
/**
|
||||
* Converts a synchronous {@link SyncCompletionSpecification} into an
|
||||
* {@link AsyncCompletionSpecification} by wrapping the handler in a bounded
|
||||
* elastic scheduler for safe non-blocking execution.
|
||||
* @param completion the synchronous completion specification
|
||||
* @return an asynchronous wrapper of the provided sync specification, or
|
||||
* {@code null} if input is null
|
||||
*/
|
||||
static AsyncCompletionSpecification fromSync(SyncCompletionSpecification completion) {
|
||||
if (completion == null) {
|
||||
return null;
|
||||
}
|
||||
return new AsyncCompletionSpecification(completion.referenceKey(),
|
||||
(exchange, request) -> Mono.fromCallable(
|
||||
() -> completion.completionHandler().apply(new McpSyncServerExchange(exchange), request))
|
||||
.subscribeOn(Schedulers.boundedElastic()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Specification of a tool with its synchronous handler function. Tools are the
|
||||
* primary way for MCP servers to expose functionality to AI models. Each tool
|
||||
|
|
@ -431,4 +483,17 @@ public class McpServerFeatures {
|
|||
BiFunction<McpSyncServerExchange, McpSchema.GetPromptRequest, McpSchema.GetPromptResult> promptHandler) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Specification of a completion handler function with synchronous execution support.
|
||||
*
|
||||
* @param referenceKey The unique key representing the completion reference.
|
||||
* @param completionHandler The synchronous function that processes completion
|
||||
* requests and returns results. The first argument is an
|
||||
* {@link McpSyncServerExchange} used to interact with the client. The second argument
|
||||
* is a {@link io.modelcontextprotocol.spec.McpSchema.CompleteRequest}.
|
||||
*/
|
||||
public record SyncCompletionSpecification(McpSchema.CompleteReference referenceKey,
|
||||
BiFunction<McpSyncServerExchange, McpSchema.CompleteRequest, McpSchema.CompleteResult> completionHandler) {
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,6 +238,7 @@ public class McpClientSession implements McpSession {
|
|||
});
|
||||
}).timeout(this.requestTimeout).handle((jsonRpcResponse, sink) -> {
|
||||
if (jsonRpcResponse.error() != null) {
|
||||
logger.error("Error handling request: {}", jsonRpcResponse.error());
|
||||
sink.error(new McpError(jsonRpcResponse.error()));
|
||||
}
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ public final class McpSchema {
|
|||
|
||||
public static final String METHOD_NOTIFICATION_PROMPTS_LIST_CHANGED = "notifications/prompts/list_changed";
|
||||
|
||||
public static final String METHOD_COMPLETION_COMPLETE = "completion/complete";
|
||||
|
||||
// Logging Methods
|
||||
public static final String METHOD_LOGGING_SET_LEVEL = "logging/setLevel";
|
||||
|
||||
|
|
@ -314,12 +316,16 @@ public final class McpSchema {
|
|||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record ServerCapabilities( // @formatter:off
|
||||
@JsonProperty("completions") CompletionCapabilities completions,
|
||||
@JsonProperty("experimental") Map<String, Object> experimental,
|
||||
@JsonProperty("logging") LoggingCapabilities logging,
|
||||
@JsonProperty("prompts") PromptCapabilities prompts,
|
||||
@JsonProperty("resources") ResourceCapabilities resources,
|
||||
@JsonProperty("tools") ToolCapabilities tools) {
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||
public record CompletionCapabilities() {
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||
public record LoggingCapabilities() {
|
||||
|
|
@ -347,12 +353,18 @@ public final class McpSchema {
|
|||
|
||||
public static class Builder {
|
||||
|
||||
private CompletionCapabilities completions;
|
||||
private Map<String, Object> experimental;
|
||||
private LoggingCapabilities logging = new LoggingCapabilities();
|
||||
private PromptCapabilities prompts;
|
||||
private ResourceCapabilities resources;
|
||||
private ToolCapabilities tools;
|
||||
|
||||
public Builder completions(CompletionCapabilities completions) {
|
||||
this.completions = completions;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder experimental(Map<String, Object> experimental) {
|
||||
this.experimental = experimental;
|
||||
return this;
|
||||
|
|
@ -379,7 +391,7 @@ public final class McpSchema {
|
|||
}
|
||||
|
||||
public ServerCapabilities build() {
|
||||
return new ServerCapabilities(experimental, logging, prompts, resources, tools);
|
||||
return new ServerCapabilities(completions, experimental, logging, prompts, resources, tools);
|
||||
}
|
||||
}
|
||||
} // @formatter:on
|
||||
|
|
@ -1173,31 +1185,63 @@ public final class McpSchema {
|
|||
// ---------------------------
|
||||
// Autocomplete
|
||||
// ---------------------------
|
||||
public record CompleteRequest(PromptOrResourceReference ref, CompleteArgument argument) implements Request {
|
||||
public sealed interface PromptOrResourceReference permits PromptReference, ResourceReference {
|
||||
public sealed interface CompleteReference permits PromptReference, ResourceReference {
|
||||
|
||||
String type();
|
||||
String type();
|
||||
|
||||
String identifier();
|
||||
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record PromptReference(// @formatter:off
|
||||
@JsonProperty("type") String type,
|
||||
@JsonProperty("name") String name) implements McpSchema.CompleteReference {
|
||||
|
||||
public PromptReference(String name) {
|
||||
this("ref/prompt", name);
|
||||
}
|
||||
|
||||
public record PromptReference(// @formatter:off
|
||||
@JsonProperty("type") String type,
|
||||
@JsonProperty("name") String name) implements PromptOrResourceReference {
|
||||
}// @formatter:on
|
||||
@Override
|
||||
public String identifier() {
|
||||
return name();
|
||||
}
|
||||
}// @formatter:on
|
||||
|
||||
public record ResourceReference(// @formatter:off
|
||||
@JsonProperty("type") String type,
|
||||
@JsonProperty("uri") String uri) implements PromptOrResourceReference {
|
||||
}// @formatter:on
|
||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record ResourceReference(// @formatter:off
|
||||
@JsonProperty("type") String type,
|
||||
@JsonProperty("uri") String uri) implements McpSchema.CompleteReference {
|
||||
|
||||
public record CompleteArgument(// @formatter:off
|
||||
public ResourceReference(String uri) {
|
||||
this("ref/resource", uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String identifier() {
|
||||
return uri();
|
||||
}
|
||||
}// @formatter:on
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record CompleteRequest(// @formatter:off
|
||||
@JsonProperty("ref") McpSchema.CompleteReference ref,
|
||||
@JsonProperty("argument") CompleteArgument argument) implements Request {
|
||||
|
||||
public record CompleteArgument(
|
||||
@JsonProperty("name") String name,
|
||||
@JsonProperty("value") String value) {
|
||||
}// @formatter:on
|
||||
}
|
||||
|
||||
public record CompleteResult(CompleteCompletion completion) {
|
||||
public record CompleteCompletion(// @formatter:off
|
||||
@JsonInclude(JsonInclude.Include.NON_ABSENT)
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public record CompleteResult(@JsonProperty("values") CompleteCompletion completion) { // @formatter:off
|
||||
|
||||
public record CompleteCompletion(
|
||||
@JsonProperty("values") List<String> values,
|
||||
@JsonProperty("total") Integer total,
|
||||
@JsonProperty("hasMore") Boolean hasMore) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue