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:
Daniel Garnier-Moiroux 2026-06-24 15:35:41 +02:00
commit d015b4e97c
2 changed files with 118 additions and 4 deletions

View file

@ -4,8 +4,6 @@
package io.modelcontextprotocol.server;
import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA;
import java.time.Duration;
import java.util.List;
import java.util.Map;
@ -19,6 +17,7 @@ import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport;
import io.modelcontextprotocol.server.transport.TomcatTestUtil;
import io.modelcontextprotocol.spec.HttpHeaders;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
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.TEXT_EVENT_STREAM;
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.json;
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;
@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
// ---------------------------------------

View file

@ -21,6 +21,7 @@ import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider;
import io.modelcontextprotocol.server.transport.TomcatTestUtil;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CompleteRequest;
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.Prompt;
import io.modelcontextprotocol.spec.McpSchema.PromptArgument;
import io.modelcontextprotocol.spec.McpSchema.PromptReference;
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;
import static org.assertj.core.api.Assertions.assertThat;
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.
@ -273,6 +275,59 @@ class McpCompletionTests {
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
void testDependentCompletionScenario() {
BiFunction<McpSyncServerExchange, CompleteRequest, CompleteResult> completionHandler = (exchange, request) -> {