diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java index f52709a..e6ef833 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java @@ -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 // --------------------------------------- diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/server/McpCompletionTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/server/McpCompletionTests.java index 9a68318..482085e 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/server/McpCompletionTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/server/McpCompletionTests.java @@ -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 completionHandler = (exchange, request) -> {