Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -1089,9 +1089,7 @@ private McpRequestHandler<McpSchema.CompleteResult> completionCompleteRequestHan
McpServerFeatures.AsyncCompletionSpecification specification = this.completions.get(request.ref());

if (specification == null) {
return Mono.error(McpError.builder(ErrorCodes.INVALID_PARAMS)
.message("AsyncCompletionSpecification not found: " + request.ref())
.build());
return EMPTY_COMPLETION_RESULT;
}

return Mono.defer(() -> specification.completionHandler().apply(exchange, request));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -813,9 +813,7 @@ private McpStatelessRequestHandler<McpSchema.CompleteResult> completionCompleteR
McpStatelessServerFeatures.AsyncCompletionSpecification specification = this.completions.get(request.ref());

if (specification == null) {
return Mono.error(McpError.builder(ErrorCodes.INVALID_PARAMS)
.message("AsyncCompletionSpecification not found: " + request.ref())
.build());
return EMPTY_COMPLETION_RESULT;
}

return specification.completionHandler().apply(ctx, request);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
import io.modelcontextprotocol.spec.McpSchema.Prompt;
import io.modelcontextprotocol.spec.McpSchema.PromptArgument;
import io.modelcontextprotocol.spec.McpSchema.PromptReference;
import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
import io.modelcontextprotocol.spec.McpSchema.ResourceReference;
import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
import io.modelcontextprotocol.spec.McpSchema.TextContent;
import io.modelcontextprotocol.spec.McpSchema.Tool;
Expand Down Expand Up @@ -230,6 +233,110 @@ void testCompletionShouldReturnExpectedSuggestions(String clientType) {
}
}

@ParameterizedTest(name = "{0} : Completion call without matching handler")
@ValueSource(strings = { "httpclient" })
void testCompletionWithoutMatchingHandlerReturnsEmptyResult(String clientType) {
var clientBuilder = clientBuilders.get(clientType);

BiFunction<McpTransportContext, CompleteRequest, CompleteResult> completionHandler = (transportContext,
request) -> new CompleteResult(new CompleteResult.CompleteCompletion(List.of("java"), 1, false));

var prompt = Prompt.builder("code_review")
.title("Code review")
.description("this is code review prompt")
.arguments(List
.of(PromptArgument.builder("language").title("Language").description("string").required(false).build()))
.build();

var otherPrompt = Prompt.builder("other_prompt")
.title("Other prompt")
.description("this prompt has completions")
.arguments(List
.of(PromptArgument.builder("topic").title("Topic").description("string").required(false).build()))
.build();

var mcpServer = McpServer.sync(mcpStatelessServerTransport)
.capabilities(ServerCapabilities.builder().completions().build())
.prompts(
new McpStatelessServerFeatures.SyncPromptSpecification(prompt,
(transportContext, getPromptRequest) -> null),
new McpStatelessServerFeatures.SyncPromptSpecification(otherPrompt,
(transportContext, getPromptRequest) -> null))
.completions(new McpStatelessServerFeatures.SyncCompletionSpecification(
PromptReference.builder("other_prompt").title("Other prompt").build(), completionHandler))
.build();

try (var mcpClient = clientBuilder.build()) {
InitializeResult initResult = mcpClient.initialize();
assertThat(initResult).isNotNull();

CompleteRequest request = CompleteRequest
.builder(PromptReference.builder("code_review").title("Code review").build(),
new CompleteRequest.CompleteArgument("language", "ja"))
.build();

CompleteResult result = mcpClient.completeCompletion(request);

assertThat(result.completion().values()).isEmpty();
assertThat(result.completion().total()).isZero();
assertThat(result.completion().hasMore()).isFalse();
}
finally {
mcpServer.close();
}
}

@ParameterizedTest(name = "{0} : Resource template completion call without matching handler")
@ValueSource(strings = { "httpclient" })
void testResourceTemplateCompletionWithoutMatchingHandlerReturnsEmptyResult(String clientType) {
var clientBuilder = clientBuilders.get(clientType);

BiFunction<McpTransportContext, CompleteRequest, CompleteResult> completionHandler = (transportContext,
request) -> new CompleteResult(new CompleteResult.CompleteCompletion(List.of("java"), 1, false));

var template = ResourceTemplate.builder("test://resource/{param}", "Test Resource")
.title("Test resource")
.description("A resource template for testing")
.mimeType("text/plain")
.build();

var otherTemplate = ResourceTemplate.builder("test://other/{param}", "Other Resource")
.title("Other resource")
.description("A resource template with completions")
.mimeType("text/plain")
.build();

var mcpServer = McpServer.sync(mcpStatelessServerTransport)
.capabilities(ServerCapabilities.builder().completions().build())
.resourceTemplates(
new McpStatelessServerFeatures.SyncResourceTemplateSpecification(template,
(transportContext, req) -> ReadResourceResult.builder(List.of()).build()),
new McpStatelessServerFeatures.SyncResourceTemplateSpecification(otherTemplate,
(transportContext, req) -> ReadResourceResult.builder(List.of()).build()))
.completions(new McpStatelessServerFeatures.SyncCompletionSpecification(
new ResourceReference("test://other/{param}"), completionHandler))
.build();

try (var mcpClient = clientBuilder.build()) {
InitializeResult initResult = mcpClient.initialize();
assertThat(initResult).isNotNull();

CompleteRequest request = CompleteRequest
.builder(new ResourceReference("test://resource/{param}"),
new CompleteRequest.CompleteArgument("param", "ja"))
.build();

CompleteResult result = mcpClient.completeCompletion(request);

assertThat(result.completion().values()).isEmpty();
assertThat(result.completion().total()).isZero();
assertThat(result.completion().hasMore()).isFalse();
}
finally {
mcpServer.close();
}
}

// ---------------------------------------
// Tool Structured Output Schema Tests
// ---------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
import io.modelcontextprotocol.spec.McpSchema.Resource;
import io.modelcontextprotocol.spec.McpSchema.ResourceReference;
import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
import io.modelcontextprotocol.spec.McpSchema.PromptReference;
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
import io.modelcontextprotocol.spec.McpError;
Expand Down Expand Up @@ -179,6 +180,99 @@ void testCompletionBackwardCompatibility() {
mcpServer.close();
}

@Test
void testCompletionWithoutMatchingHandlerReturnsEmptyResult() {
BiFunction<McpSyncServerExchange, CompleteRequest, CompleteResult> completionHandler = (exchange,
request) -> new CompleteResult(new CompleteResult.CompleteCompletion(List.of("java"), 1, false));

McpSchema.Prompt prompt = Prompt.builder("code_review")
.description("this is a code review prompt")
.arguments(List.of(PromptArgument.builder("language").description("string").required(false).build()))
.build();

McpSchema.Prompt otherPrompt = Prompt.builder("other_prompt")
.description("this prompt has completions")
.arguments(List.of(PromptArgument.builder("topic").description("string").required(false).build()))
.build();

var mcpServer = McpServer.sync(mcpServerTransportProvider)
.capabilities(ServerCapabilities.builder().completions().build())
.prompts(
new McpServerFeatures.SyncPromptSpecification(prompt,
(mcpSyncServerExchange, getPromptRequest) -> null),
new McpServerFeatures.SyncPromptSpecification(otherPrompt,
(mcpSyncServerExchange, getPromptRequest) -> null))
.completions(new McpServerFeatures.SyncCompletionSpecification(new PromptReference("other_prompt"),
completionHandler))
.build();

try (var mcpClient = clientBuilder
.clientInfo(McpSchema.Implementation.builder("Sample " + "client", "0.0.0").build())
.build();) {
InitializeResult initResult = mcpClient.initialize();
assertThat(initResult).isNotNull();

CompleteRequest request = CompleteRequest
.builder(new PromptReference("code_review"), new CompleteRequest.CompleteArgument("language", "ja"))
.build();

CompleteResult result = mcpClient.completeCompletion(request);

assertThat(result.completion().values()).isEmpty();
assertThat(result.completion().total()).isZero();
assertThat(result.completion().hasMore()).isFalse();
}

mcpServer.close();
}

@Test
void testResourceTemplateCompletionWithoutMatchingHandlerReturnsEmptyResult() {
BiFunction<McpSyncServerExchange, CompleteRequest, CompleteResult> completionHandler = (exchange,
request) -> new CompleteResult(new CompleteResult.CompleteCompletion(List.of("java"), 1, false));

ResourceTemplate template = ResourceTemplate.builder("test://resource/{param}", "Test Resource")
.description("A resource template for testing")
.mimeType("text/plain")
.build();

ResourceTemplate otherTemplate = ResourceTemplate.builder("test://other/{param}", "Other Resource")
.description("A resource template with completions")
.mimeType("text/plain")
.build();

var mcpServer = McpServer.sync(mcpServerTransportProvider)
.capabilities(ServerCapabilities.builder().completions().build())
.resourceTemplates(
new McpServerFeatures.SyncResourceTemplateSpecification(template,
(exchange, req) -> ReadResourceResult.builder(List.of()).build()),
new McpServerFeatures.SyncResourceTemplateSpecification(otherTemplate,
(exchange, req) -> ReadResourceResult.builder(List.of()).build()))
.completions(new McpServerFeatures.SyncCompletionSpecification(
new ResourceReference("test://other/{param}"), completionHandler))
.build();

try (var mcpClient = clientBuilder
.clientInfo(McpSchema.Implementation.builder("Sample " + "client", "0.0.0").build())
.build();) {
InitializeResult initResult = mcpClient.initialize();
assertThat(initResult).isNotNull();

CompleteRequest request = CompleteRequest
.builder(new ResourceReference("test://resource/{param}"),
new CompleteRequest.CompleteArgument("param", "ja"))
.build();

CompleteResult result = mcpClient.completeCompletion(request);

assertThat(result.completion().values()).isEmpty();
assertThat(result.completion().total()).isZero();
assertThat(result.completion().hasMore()).isFalse();
}

mcpServer.close();
}

@Test
void testDependentCompletionScenario() {
BiFunction<McpSyncServerExchange, CompleteRequest, CompleteResult> completionHandler = (exchange, request) -> {
Expand Down Expand Up @@ -365,4 +459,4 @@ void testPromptWithoutArgumentsCompletionForArgument() {
mcpServer.close();
}

}
}