Add tests for completion requests targeting missing prompt or resource
Polish gh-996 Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
parent
301f8b06c7
commit
d015b4e97c
2 changed files with 118 additions and 4 deletions
|
|
@ -4,8 +4,6 @@
|
||||||
|
|
||||||
package io.modelcontextprotocol.server;
|
package io.modelcontextprotocol.server;
|
||||||
|
|
||||||
import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
@ -19,6 +17,7 @@ import io.modelcontextprotocol.common.McpTransportContext;
|
||||||
import io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport;
|
import io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport;
|
||||||
import io.modelcontextprotocol.server.transport.TomcatTestUtil;
|
import io.modelcontextprotocol.server.transport.TomcatTestUtil;
|
||||||
import io.modelcontextprotocol.spec.HttpHeaders;
|
import io.modelcontextprotocol.spec.HttpHeaders;
|
||||||
|
import io.modelcontextprotocol.spec.McpError;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
|
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.CompleteRequest;
|
import io.modelcontextprotocol.spec.McpSchema.CompleteRequest;
|
||||||
|
|
@ -53,9 +52,12 @@ import org.springframework.web.client.RestClient;
|
||||||
import static io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport.APPLICATION_JSON;
|
import static io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport.APPLICATION_JSON;
|
||||||
import static io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport.TEXT_EVENT_STREAM;
|
import static io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport.TEXT_EVENT_STREAM;
|
||||||
import static io.modelcontextprotocol.util.McpJsonMapperUtils.JSON_MAPPER;
|
import static io.modelcontextprotocol.util.McpJsonMapperUtils.JSON_MAPPER;
|
||||||
|
import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA;
|
||||||
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
|
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
|
||||||
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
|
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||||
import static org.awaitility.Awaitility.await;
|
import static org.awaitility.Awaitility.await;
|
||||||
|
|
||||||
@Timeout(15)
|
@Timeout(15)
|
||||||
|
|
@ -337,6 +339,63 @@ class HttpServletStatelessIntegrationTests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0} : Completion call for non-existent prompt")
|
||||||
|
@ValueSource(strings = { "httpclient" })
|
||||||
|
void testCompletionForNonExistentPromptReturnsInvalidParams(String clientType) {
|
||||||
|
var clientBuilder = clientBuilders.get(clientType);
|
||||||
|
|
||||||
|
var mcpServer = McpServer.sync(mcpStatelessServerTransport)
|
||||||
|
.capabilities(ServerCapabilities.builder().completions().build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (var mcpClient = clientBuilder.build()) {
|
||||||
|
InitializeResult initResult = mcpClient.initialize();
|
||||||
|
assertThat(initResult).isNotNull();
|
||||||
|
|
||||||
|
CompleteRequest request = CompleteRequest
|
||||||
|
.builder(new PromptReference("nonexistent-prompt"), new CompleteRequest.CompleteArgument("arg", "val"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> mcpClient.completeCompletion(request)).isInstanceOf(McpError.class)
|
||||||
|
.asInstanceOf(type(McpError.class))
|
||||||
|
.extracting(McpError::getJsonRpcError)
|
||||||
|
.extracting(McpSchema.JSONRPCResponse.JSONRPCError::code)
|
||||||
|
.isEqualTo(McpSchema.ErrorCodes.RESOURCE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
mcpServer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest(name = "{0} : Completion call for non-existent resource")
|
||||||
|
@ValueSource(strings = { "httpclient" })
|
||||||
|
void testCompletionForNonExistentResourceReturnsResourceNotFound(String clientType) {
|
||||||
|
var clientBuilder = clientBuilders.get(clientType);
|
||||||
|
|
||||||
|
var mcpServer = McpServer.sync(mcpStatelessServerTransport)
|
||||||
|
.capabilities(ServerCapabilities.builder().completions().build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try (var mcpClient = clientBuilder.build()) {
|
||||||
|
InitializeResult initResult = mcpClient.initialize();
|
||||||
|
assertThat(initResult).isNotNull();
|
||||||
|
|
||||||
|
CompleteRequest request = CompleteRequest
|
||||||
|
.builder(new ResourceReference("test://nonexistent/{param}"),
|
||||||
|
new CompleteRequest.CompleteArgument("param", "val"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> mcpClient.completeCompletion(request)).isInstanceOf(McpError.class)
|
||||||
|
.asInstanceOf(type(McpError.class))
|
||||||
|
.extracting(McpError::getJsonRpcError)
|
||||||
|
.extracting(McpSchema.JSONRPCResponse.JSONRPCError::code)
|
||||||
|
.isEqualTo(McpSchema.ErrorCodes.RESOURCE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
mcpServer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
// Tool Structured Output Schema Tests
|
// Tool Structured Output Schema Tests
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import io.modelcontextprotocol.client.McpClient;
|
||||||
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
|
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
|
||||||
import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider;
|
import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider;
|
||||||
import io.modelcontextprotocol.server.transport.TomcatTestUtil;
|
import io.modelcontextprotocol.server.transport.TomcatTestUtil;
|
||||||
|
import io.modelcontextprotocol.spec.McpError;
|
||||||
import io.modelcontextprotocol.spec.McpSchema;
|
import io.modelcontextprotocol.spec.McpSchema;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.CompleteRequest;
|
import io.modelcontextprotocol.spec.McpSchema.CompleteRequest;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.CompleteResult;
|
import io.modelcontextprotocol.spec.McpSchema.CompleteResult;
|
||||||
|
|
@ -28,16 +29,17 @@ import io.modelcontextprotocol.spec.McpSchema.ErrorCodes;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.InitializeResult;
|
import io.modelcontextprotocol.spec.McpSchema.InitializeResult;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.Prompt;
|
import io.modelcontextprotocol.spec.McpSchema.Prompt;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.PromptArgument;
|
import io.modelcontextprotocol.spec.McpSchema.PromptArgument;
|
||||||
|
import io.modelcontextprotocol.spec.McpSchema.PromptReference;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
|
import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.Resource;
|
import io.modelcontextprotocol.spec.McpSchema.Resource;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.ResourceReference;
|
import io.modelcontextprotocol.spec.McpSchema.ResourceReference;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
|
import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.PromptReference;
|
|
||||||
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
|
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
|
||||||
import io.modelcontextprotocol.spec.McpError;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.assertj.core.api.InstanceOfAssertFactories.type;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for completion functionality with context support.
|
* Tests for completion functionality with context support.
|
||||||
|
|
@ -273,6 +275,59 @@ class McpCompletionTests {
|
||||||
mcpServer.close();
|
mcpServer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompletionForNonExistentPromptReturnsInvalidParams() {
|
||||||
|
var mcpServer = McpServer.sync(mcpServerTransportProvider)
|
||||||
|
.capabilities(ServerCapabilities.builder().completions().build())
|
||||||
|
.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("nonexistent-prompt"), new CompleteRequest.CompleteArgument("arg", "val"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> mcpClient.completeCompletion(request)).isInstanceOf(McpError.class)
|
||||||
|
.asInstanceOf(type(McpError.class))
|
||||||
|
.extracting(McpError::getJsonRpcError)
|
||||||
|
.extracting(McpSchema.JSONRPCResponse.JSONRPCError::code)
|
||||||
|
.isEqualTo(ErrorCodes.INVALID_PARAMS);
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpServer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompletionForNonExistentResourceReturnsResourceNotFound() {
|
||||||
|
var mcpServer = McpServer.sync(mcpServerTransportProvider)
|
||||||
|
.capabilities(ServerCapabilities.builder().completions().build())
|
||||||
|
.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://nonexistent/{param}"),
|
||||||
|
new CompleteRequest.CompleteArgument("param", "val"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> mcpClient.completeCompletion(request)).isInstanceOf(McpError.class)
|
||||||
|
.asInstanceOf(type(McpError.class))
|
||||||
|
.extracting(McpError::getJsonRpcError)
|
||||||
|
.extracting(McpSchema.JSONRPCResponse.JSONRPCError::code)
|
||||||
|
.isEqualTo(McpSchema.ErrorCodes.RESOURCE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpServer.close();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testDependentCompletionScenario() {
|
void testDependentCompletionScenario() {
|
||||||
BiFunction<McpSyncServerExchange, CompleteRequest, CompleteResult> completionHandler = (exchange, request) -> {
|
BiFunction<McpSyncServerExchange, CompleteRequest, CompleteResult> completionHandler = (exchange, request) -> {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue