fix: Return empty prompt completion result when prompt has no arguments (#934)
Recent changes don't coerce null completion arguments to empty lists so we have to check for null when handling prompt completions. Resolves #932 Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
This commit is contained in:
parent
4c8596306d
commit
5895b2edd2
4 changed files with 45 additions and 19 deletions
|
|
@ -999,12 +999,9 @@ public class McpAsyncServer {
|
||||||
.message("Prompt not found: " + promptReference.name())
|
.message("Prompt not found: " + promptReference.name())
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
if (!promptSpec.prompt()
|
List<McpSchema.PromptArgument> arguments = promptSpec.prompt().arguments();
|
||||||
.arguments()
|
if (arguments == null
|
||||||
.stream()
|
|| !arguments.stream().filter(arg -> arg.name().equals(argumentName)).findFirst().isPresent()) {
|
||||||
.filter(arg -> arg.name().equals(argumentName))
|
|
||||||
.findFirst()
|
|
||||||
.isPresent()) {
|
|
||||||
|
|
||||||
logger.warn("Argument not found: {} in prompt: {}", argumentName, promptReference.name());
|
logger.warn("Argument not found: {} in prompt: {}", argumentName, promptReference.name());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -744,12 +744,9 @@ public class McpStatelessAsyncServer {
|
||||||
.message("Prompt not found: " + promptReference.name())
|
.message("Prompt not found: " + promptReference.name())
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
if (!promptSpec.prompt()
|
List<McpSchema.PromptArgument> arguments = promptSpec.prompt().arguments();
|
||||||
.arguments()
|
if (arguments == null
|
||||||
.stream()
|
|| !arguments.stream().filter(arg -> arg.name().equals(argumentName)).findFirst().isPresent()) {
|
||||||
.filter(arg -> arg.name().equals(argumentName))
|
|
||||||
.findFirst()
|
|
||||||
.isPresent()) {
|
|
||||||
|
|
||||||
logger.warn("Argument not found: {} in prompt: {}", argumentName, promptReference.name());
|
logger.warn("Argument not found: {} in prompt: {}", argumentName, promptReference.name());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -184,10 +184,10 @@ class HttpServletStatelessIntegrationTests {
|
||||||
true // hasMore
|
true // hasMore
|
||||||
));
|
));
|
||||||
|
|
||||||
AtomicReference<CompleteRequest> samplingRequest = new AtomicReference<>();
|
AtomicReference<CompleteRequest> completeRequest = new AtomicReference<>();
|
||||||
BiFunction<McpTransportContext, CompleteRequest, CompleteResult> completionHandler = (transportContext,
|
BiFunction<McpTransportContext, CompleteRequest, CompleteResult> completionHandler = (transportContext,
|
||||||
request) -> {
|
request) -> {
|
||||||
samplingRequest.set(request);
|
completeRequest.set(request);
|
||||||
return completionResponse;
|
return completionResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -214,9 +214,9 @@ class HttpServletStatelessIntegrationTests {
|
||||||
|
|
||||||
assertThat(result).isNotNull();
|
assertThat(result).isNotNull();
|
||||||
|
|
||||||
assertThat(samplingRequest.get().argument().name()).isEqualTo("language");
|
assertThat(completeRequest.get().argument().name()).isEqualTo("language");
|
||||||
assertThat(samplingRequest.get().argument().value()).isEqualTo("py");
|
assertThat(completeRequest.get().argument().value()).isEqualTo("py");
|
||||||
assertThat(samplingRequest.get().ref().type()).isEqualTo(PromptReference.TYPE);
|
assertThat(completeRequest.get().ref().type()).isEqualTo(PromptReference.TYPE);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
mcpServer.close();
|
mcpServer.close();
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,9 @@ import org.apache.catalina.LifecycleException;
|
||||||
import org.apache.catalina.LifecycleState;
|
import org.apache.catalina.LifecycleState;
|
||||||
import org.apache.catalina.startup.Tomcat;
|
import org.apache.catalina.startup.Tomcat;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|
||||||
|
|
||||||
import io.modelcontextprotocol.client.McpClient;
|
import io.modelcontextprotocol.client.McpClient;
|
||||||
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
|
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
|
||||||
|
|
@ -37,6 +35,9 @@ import io.modelcontextprotocol.spec.McpSchema.PromptReference;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
|
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
|
||||||
import io.modelcontextprotocol.spec.McpError;
|
import io.modelcontextprotocol.spec.McpError;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for completion functionality with context support.
|
* Tests for completion functionality with context support.
|
||||||
*
|
*
|
||||||
|
|
@ -324,4 +325,35 @@ class McpCompletionTests {
|
||||||
mcpServer.close();
|
mcpServer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPromptWithoutArgumentsCompletionForArgument() {
|
||||||
|
BiFunction<McpSyncServerExchange, CompleteRequest, CompleteResult> completionHandler = (exchange,
|
||||||
|
request) -> new CompleteResult(new CompleteResult.CompleteCompletion(List.of("test"), 1, false));
|
||||||
|
|
||||||
|
McpSchema.Prompt prompt = new Prompt("test-prompt", "this is a test prompt", null);
|
||||||
|
|
||||||
|
var mcpServer = McpServer.sync(mcpServerTransportProvider)
|
||||||
|
.capabilities(ServerCapabilities.builder().completions().build())
|
||||||
|
.prompts(new McpServerFeatures.SyncPromptSpecification(prompt,
|
||||||
|
(mcpSyncServerExchange, getPromptRequest) -> null))
|
||||||
|
.completions(new McpServerFeatures.SyncCompletionSpecification(
|
||||||
|
new PromptReference(PromptReference.TYPE, "test-prompt"), completionHandler))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (var mcpClient = clientBuilder.clientInfo(new McpSchema.Implementation("Sample " + "client", "0.0.0"))
|
||||||
|
.build()) {
|
||||||
|
InitializeResult initResult = mcpClient.initialize();
|
||||||
|
assertThat(initResult).isNotNull();
|
||||||
|
|
||||||
|
// try completing an argument knowing that the prompt is not parameterized
|
||||||
|
CompleteRequest request = new CompleteRequest(new PromptReference(PromptReference.TYPE, "test-prompt"),
|
||||||
|
new CompleteRequest.CompleteArgument("arg", "val"));
|
||||||
|
|
||||||
|
CompleteResult completeResult = mcpClient.completeCompletion(request);
|
||||||
|
assertThat(completeResult.completion().values()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpServer.close();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue