diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java index 025cfeac..8d688934 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java @@ -137,6 +137,28 @@ void testAddDuplicateTool() { assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException(); } + @Test + void testPutTool() { + var mcpAsyncServer = McpServer.async(createMcpTransportProvider()) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().tools(true).build()) + .build(); + + Tool toolV1 = new McpSchema.Tool(TEST_TOOL_NAME, "Tool with version 1.0.0", emptyJsonSchema); + + StepVerifier.create(mcpAsyncServer.putTool(new McpServerFeatures.AsyncToolSpecification(toolV1, + (exchange, args) -> Mono.just(new CallToolResult(List.of(), false))))) + .verifyComplete(); + + Tool toolV2 = new McpSchema.Tool(TEST_TOOL_NAME, "Tool with version 2.0.0", emptyJsonSchema); + + StepVerifier.create(mcpAsyncServer.putTool(new McpServerFeatures.AsyncToolSpecification(toolV2, + (exchange, args) -> Mono.just(new CallToolResult(List.of(), false))))) + .verifyComplete(); + + assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException(); + } + @Test void testRemoveTool() { Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema); @@ -201,7 +223,7 @@ void testAddResource() { .capabilities(ServerCapabilities.builder().resources(true, false).build()) .build(); - Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "text/plain", "Test resource description", + Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "Test resource description", "text/plain", null); McpServerFeatures.AsyncResourceSpecification specification = new McpServerFeatures.AsyncResourceSpecification( resource, (exchange, req) -> Mono.just(new ReadResourceResult(List.of()))); @@ -233,7 +255,7 @@ void testAddResourceWithoutCapability() { .serverInfo("test-server", "1.0.0") .build(); - Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "text/plain", "Test resource description", + Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "Test resource description", "text/plain", null); McpServerFeatures.AsyncResourceSpecification specification = new McpServerFeatures.AsyncResourceSpecification( resource, (exchange, req) -> Mono.just(new ReadResourceResult(List.of()))); @@ -244,6 +266,28 @@ void testAddResourceWithoutCapability() { }); } + @Test + void testPutResource() { + var mcpAsyncServer = McpServer.async(createMcpTransportProvider()) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().resources(true, false).build()) + .build(); + + Resource resourceV1 = new Resource(TEST_RESOURCE_URI, "Test Resource", "Resource with version 1.0.0", + "text/plain", null); + McpServerFeatures.AsyncResourceSpecification specificationV1 = new McpServerFeatures.AsyncResourceSpecification( + resourceV1, (exchange, req) -> Mono.just(new ReadResourceResult(List.of()))); + StepVerifier.create(mcpAsyncServer.putResource(specificationV1)).verifyComplete(); + + Resource resourceV2 = new Resource(TEST_RESOURCE_URI, "Test Resource", "Resource with version 2.0.0", + "text/plain", null); + McpServerFeatures.AsyncResourceSpecification specificationV2 = new McpServerFeatures.AsyncResourceSpecification( + resourceV2, (exchange, req) -> Mono.just(new ReadResourceResult(List.of()))); + StepVerifier.create(mcpAsyncServer.putResource(specificationV2)).verifyComplete(); + + assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException(); + } + @Test void testRemoveResourceWithoutCapability() { // Create a server without resource capabilities @@ -301,6 +345,28 @@ void testAddPromptWithoutCapability() { }); } + @Test + void testPutPrompt() { + var mcpAsyncServer = McpServer.async(createMcpTransportProvider()) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().prompts(false).build()) + .build(); + + Prompt promptV1 = new Prompt(TEST_PROMPT_NAME, "Prompt with version 1.0.0", List.of()); + McpServerFeatures.AsyncPromptSpecification specificationV1 = new McpServerFeatures.AsyncPromptSpecification( + promptV1, (exchange, req) -> Mono.just(new GetPromptResult("Test prompt description", List + .of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content")))))); + + StepVerifier.create(mcpAsyncServer.putPrompt(specificationV1)).verifyComplete(); + + Prompt promptV2 = new Prompt(TEST_PROMPT_NAME, "Prompt with version 2.0.0", List.of()); + McpServerFeatures.AsyncPromptSpecification specificationV2 = new McpServerFeatures.AsyncPromptSpecification( + promptV2, (exchange, req) -> Mono.just(new GetPromptResult("Test prompt description", List + .of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content")))))); + + StepVerifier.create(mcpAsyncServer.putPrompt(specificationV2)).verifyComplete(); + } + @Test void testRemovePromptWithoutCapability() { // Create a server without prompt capabilities diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java index e313454b..05a3ac76 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java @@ -141,6 +141,28 @@ void testAddDuplicateTool() { assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException(); } + @Test + void testPutTool() { + var mcpSyncServer = McpServer.sync(createMcpTransportProvider()) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().tools(true).build()) + .build(); + + Tool toolV1 = new McpSchema.Tool(TEST_TOOL_NAME, "Tool with version 1.0.0", emptyJsonSchema); + + assertThatCode(() -> mcpSyncServer.putTool(new McpServerFeatures.SyncToolSpecification(toolV1, + (exchange, args) -> new CallToolResult(List.of(), false)))) + .doesNotThrowAnyException(); + + Tool toolV2 = new McpSchema.Tool(TEST_TOOL_NAME, "Tool with version 2.0.0", emptyJsonSchema); + + assertThatCode(() -> mcpSyncServer.putTool(new McpServerFeatures.SyncToolSpecification(toolV2, + (exchange, args) -> new CallToolResult(List.of(), false)))) + .doesNotThrowAnyException(); + + assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException(); + } + @Test void testRemoveTool() { Tool tool = new McpSchema.Tool(TEST_TOOL_NAME, "Test tool", emptyJsonSchema); @@ -198,7 +220,7 @@ void testAddResource() { .capabilities(ServerCapabilities.builder().resources(true, false).build()) .build(); - Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "text/plain", "Test resource description", + Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "Test resource description", "text/plain", null); McpServerFeatures.SyncResourceSpecification specification = new McpServerFeatures.SyncResourceSpecification( resource, (exchange, req) -> new ReadResourceResult(List.of())); @@ -228,7 +250,7 @@ void testAddResourceWithoutCapability() { .serverInfo("test-server", "1.0.0") .build(); - Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "text/plain", "Test resource description", + Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "Test resource description", "text/plain", null); McpServerFeatures.SyncResourceSpecification specification = new McpServerFeatures.SyncResourceSpecification( resource, (exchange, req) -> new ReadResourceResult(List.of())); @@ -237,6 +259,28 @@ void testAddResourceWithoutCapability() { .hasMessage("Server must be configured with resource capabilities"); } + @Test + void testPutResource() { + var mcpSyncServer = McpServer.sync(createMcpTransportProvider()) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().resources(true, false).build()) + .build(); + + Resource resourceV1 = new Resource(TEST_RESOURCE_URI, "Test Resource", "Resource with version 1.0.0", + "text/plain", null); + McpServerFeatures.SyncResourceSpecification specificationV1 = new McpServerFeatures.SyncResourceSpecification( + resourceV1, (exchange, req) -> new ReadResourceResult(List.of())); + assertThatCode(() -> mcpSyncServer.putResource(specificationV1)).doesNotThrowAnyException(); + + Resource resourceV2 = new Resource(TEST_RESOURCE_URI, "Test Resource", "Resource with version 2.0.0", + "text/plain", null); + McpServerFeatures.SyncResourceSpecification specificationV2 = new McpServerFeatures.SyncResourceSpecification( + resourceV2, (exchange, req) -> new ReadResourceResult(List.of())); + assertThatCode(() -> mcpSyncServer.putResource(specificationV2)).doesNotThrowAnyException(); + + assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException(); + } + @Test void testRemoveResourceWithoutCapability() { var serverWithoutResources = McpServer.sync(createMcpTransportProvider()) @@ -287,6 +331,26 @@ void testAddPromptWithoutCapability() { .hasMessage("Server must be configured with prompt capabilities"); } + @Test + void testPutPrompt() { + var mcpSyncServer = McpServer.sync(createMcpTransportProvider()) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().prompts(false).build()) + .build(); + + Prompt promptV1 = new Prompt(TEST_PROMPT_NAME, "Prompt with version 1.0.0", List.of()); + McpServerFeatures.SyncPromptSpecification specificationV1 = new McpServerFeatures.SyncPromptSpecification( + promptV1, (exchange, req) -> new GetPromptResult("Test prompt description", List + .of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content"))))); + assertThatCode(() -> mcpSyncServer.putPrompt(specificationV1)).doesNotThrowAnyException(); + + Prompt promptV2 = new Prompt(TEST_PROMPT_NAME, "Prompt with version 2.0.0", List.of()); + McpServerFeatures.SyncPromptSpecification specificationV2 = new McpServerFeatures.SyncPromptSpecification( + promptV2, (exchange, req) -> new GetPromptResult("Test prompt description", List + .of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content"))))); + assertThatCode(() -> mcpSyncServer.putPrompt(specificationV2)).doesNotThrowAnyException(); + } + @Test void testRemovePromptWithoutCapability() { var serverWithoutPrompts = McpServer.sync(createMcpTransportProvider()) diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java index 1efa13de..800c001f 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java @@ -303,6 +303,44 @@ public Mono addTool(McpServerFeatures.AsyncToolSpecification toolSpecifica }); } + /** + * Replaces an existing tool handler or adds a new one if it doesn't exist. + * @param toolSpecification The tool specification to put + * @return Mono that completes when clients have been notified of the change + */ + public Mono putTool(McpServerFeatures.AsyncToolSpecification toolSpecification) { + if (toolSpecification == null) { + return Mono.error(new McpError("Tool specification must not be null")); + } + if (toolSpecification.tool() == null) { + return Mono.error(new McpError("Tool must not be null")); + } + if (toolSpecification.call() == null) { + return Mono.error(new McpError("Tool call handler must not be null")); + } + if (this.serverCapabilities.tools() == null) { + return Mono.error(new McpError("Server must be configured with tool capabilities")); + } + + return Mono.defer(() -> { + this.tools.replaceAll(asyncToolSpecification -> { + if (!asyncToolSpecification.tool().name().equals(toolSpecification.tool().name())) { + return asyncToolSpecification; + } + logger.debug("Replaced tool handler: {}", toolSpecification.tool().name()); + return toolSpecification; + }); + if (this.tools.addIfAbsent(toolSpecification)) { + logger.debug("Added tool handler: {}", toolSpecification.tool().name()); + } + + if (this.serverCapabilities.tools().listChanged()) { + return notifyToolsListChanged(); + } + return Mono.empty(); + }); + } + /** * Remove a tool handler at runtime. * @param toolName The name of the tool handler to remove @@ -396,6 +434,34 @@ public Mono addResource(McpServerFeatures.AsyncResourceSpecification resou }); } + /** + * Replaces an existing resource handler or adds a new one if it doesn't exist. + * @param resourceSpecification The resource handler to put + * @return Mono that completes when clients have been notified of the change + */ + public Mono putResource(McpServerFeatures.AsyncResourceSpecification resourceSpecification) { + if (resourceSpecification == null || resourceSpecification.resource() == null) { + return Mono.error(new McpError("Resource must not be null")); + } + + if (this.serverCapabilities.resources() == null) { + return Mono.error(new McpError("Server must be configured with resource capabilities")); + } + + return Mono.defer(() -> { + if (this.resources.put(resourceSpecification.resource().uri(), resourceSpecification) != null) { + logger.debug("Replaced resource handler: {}", resourceSpecification.resource().uri()); + } + else { + logger.debug("Added resource handler: {}", resourceSpecification.resource().uri()); + } + if (this.serverCapabilities.resources().listChanged()) { + return notifyResourcesListChanged(); + } + return Mono.empty(); + }); + } + /** * Remove a resource handler at runtime. * @param resourceUri The URI of the resource handler to remove @@ -520,6 +586,36 @@ public Mono addPrompt(McpServerFeatures.AsyncPromptSpecification promptSpe }); } + /** + * Replaces an existing prompt handler or adds a new one if it doesn't exist. + * @param promptSpecification The prompt handler to put + * @return Mono that completes when clients have been notified of the change + */ + public Mono putPrompt(McpServerFeatures.AsyncPromptSpecification promptSpecification) { + if (promptSpecification == null) { + return Mono.error(new McpError("Prompt specification must not be null")); + } + if (this.serverCapabilities.prompts() == null) { + return Mono.error(new McpError("Server must be configured with prompt capabilities")); + } + + return Mono.defer(() -> { + if (this.prompts.put(promptSpecification.prompt().name(), promptSpecification) != null) { + logger.debug("Replaced prompt handler: {}", promptSpecification.prompt().name()); + } + else { + logger.debug("Added prompt handler: {}", promptSpecification.prompt().name()); + } + + // Servers that declared the listChanged capability SHOULD send a + // notification, when the list of available prompts changes + if (this.serverCapabilities.prompts().listChanged()) { + return notifyPromptsListChanged(); + } + return Mono.empty(); + }); + } + /** * Remove a prompt handler at runtime. * @param promptName The name of the prompt handler to remove diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpSyncServer.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpSyncServer.java index bf310450..31414706 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpSyncServer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpSyncServer.java @@ -71,6 +71,14 @@ public void addTool(McpServerFeatures.SyncToolSpecification toolHandler) { this.asyncServer.addTool(McpServerFeatures.AsyncToolSpecification.fromSync(toolHandler)).block(); } + /** + * Put a new tool handler or update an existing one. + * @param toolHandler The tool handler to put + */ + public void putTool(McpServerFeatures.SyncToolSpecification toolHandler) { + this.asyncServer.putTool(McpServerFeatures.AsyncToolSpecification.fromSync(toolHandler)).block(); + } + /** * Remove a tool handler. * @param toolName The name of the tool handler to remove @@ -87,6 +95,14 @@ public void addResource(McpServerFeatures.SyncResourceSpecification resourceHand this.asyncServer.addResource(McpServerFeatures.AsyncResourceSpecification.fromSync(resourceHandler)).block(); } + /** + * Put a new resource handler or update an existing one. + * @param resourceHandler The resource handler to put + */ + public void putResource(McpServerFeatures.SyncResourceSpecification resourceHandler) { + this.asyncServer.putResource(McpServerFeatures.AsyncResourceSpecification.fromSync(resourceHandler)).block(); + } + /** * Remove a resource handler. * @param resourceUri The URI of the resource handler to remove @@ -103,6 +119,14 @@ public void addPrompt(McpServerFeatures.SyncPromptSpecification promptSpecificat this.asyncServer.addPrompt(McpServerFeatures.AsyncPromptSpecification.fromSync(promptSpecification)).block(); } + /** + * Put a new prompt handler or update an existing one. + * @param promptSpecification The prompt specification to put + */ + public void putPrompt(McpServerFeatures.SyncPromptSpecification promptSpecification) { + this.asyncServer.putPrompt(McpServerFeatures.AsyncPromptSpecification.fromSync(promptSpecification)).block(); + } + /** * Remove a prompt handler. * @param promptName The name of the prompt handler to remove diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java index df0b0c72..1899c476 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java @@ -136,6 +136,28 @@ void testAddDuplicateTool() { assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException(); } + @Test + void testPutTool() { + var mcpAsyncServer = McpServer.async(createMcpTransportProvider()) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().tools(true).build()) + .build(); + + Tool toolV1 = new McpSchema.Tool(TEST_TOOL_NAME, "Tool with version 1.0.0", emptyJsonSchema); + + StepVerifier.create(mcpAsyncServer.putTool(new McpServerFeatures.AsyncToolSpecification(toolV1, + (exchange, args) -> Mono.just(new CallToolResult(List.of(), false))))) + .verifyComplete(); + + Tool toolV2 = new McpSchema.Tool(TEST_TOOL_NAME, "Tool with version 2.0.0", emptyJsonSchema); + + StepVerifier.create(mcpAsyncServer.putTool(new McpServerFeatures.AsyncToolSpecification(toolV2, + (exchange, args) -> Mono.just(new CallToolResult(List.of(), false))))) + .verifyComplete(); + + assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException(); + } + @Test void testRemoveTool() { Tool too = new McpSchema.Tool(TEST_TOOL_NAME, "Duplicate tool", emptyJsonSchema); @@ -200,7 +222,7 @@ void testAddResource() { .capabilities(ServerCapabilities.builder().resources(true, false).build()) .build(); - Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "text/plain", "Test resource description", + Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "Test resource description", "text/plain", null); McpServerFeatures.AsyncResourceSpecification specification = new McpServerFeatures.AsyncResourceSpecification( resource, (exchange, req) -> Mono.just(new ReadResourceResult(List.of()))); @@ -232,7 +254,7 @@ void testAddResourceWithoutCapability() { .serverInfo("test-server", "1.0.0") .build(); - Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "text/plain", "Test resource description", + Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "Test resource description", "text/plain", null); McpServerFeatures.AsyncResourceSpecification specification = new McpServerFeatures.AsyncResourceSpecification( resource, (exchange, req) -> Mono.just(new ReadResourceResult(List.of()))); @@ -243,6 +265,28 @@ void testAddResourceWithoutCapability() { }); } + @Test + void testPutResource() { + var mcpAsyncServer = McpServer.async(createMcpTransportProvider()) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().resources(true, false).build()) + .build(); + + Resource resourceV1 = new Resource(TEST_RESOURCE_URI, "Test Resource", "Resource with version 1.0.0", + "text/plain", null); + McpServerFeatures.AsyncResourceSpecification specificationV1 = new McpServerFeatures.AsyncResourceSpecification( + resourceV1, (exchange, req) -> Mono.just(new ReadResourceResult(List.of()))); + StepVerifier.create(mcpAsyncServer.putResource(specificationV1)).verifyComplete(); + + Resource resourceV2 = new Resource(TEST_RESOURCE_URI, "Test Resource", "Resource with version 2.0.0", + "text/plain", null); + McpServerFeatures.AsyncResourceSpecification specificationV2 = new McpServerFeatures.AsyncResourceSpecification( + resourceV2, (exchange, req) -> Mono.just(new ReadResourceResult(List.of()))); + StepVerifier.create(mcpAsyncServer.putResource(specificationV2)).verifyComplete(); + + assertThatCode(() -> mcpAsyncServer.closeGracefully().block(Duration.ofSeconds(10))).doesNotThrowAnyException(); + } + @Test void testRemoveResourceWithoutCapability() { // Create a server without resource capabilities @@ -300,6 +344,28 @@ void testAddPromptWithoutCapability() { }); } + @Test + void testPutPrompt() { + var mcpAsyncServer = McpServer.async(createMcpTransportProvider()) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().prompts(false).build()) + .build(); + + Prompt promptV1 = new Prompt(TEST_PROMPT_NAME, "Prompt with version 1.0.0", List.of()); + McpServerFeatures.AsyncPromptSpecification specificationV1 = new McpServerFeatures.AsyncPromptSpecification( + promptV1, (exchange, req) -> Mono.just(new GetPromptResult("Test prompt description", List + .of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content")))))); + + StepVerifier.create(mcpAsyncServer.putPrompt(specificationV1)).verifyComplete(); + + Prompt promptV2 = new Prompt(TEST_PROMPT_NAME, "Prompt with version 2.0.0", List.of()); + McpServerFeatures.AsyncPromptSpecification specificationV2 = new McpServerFeatures.AsyncPromptSpecification( + promptV2, (exchange, req) -> Mono.just(new GetPromptResult("Test prompt description", List + .of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content")))))); + + StepVerifier.create(mcpAsyncServer.putPrompt(specificationV2)).verifyComplete(); + } + @Test void testRemovePromptWithoutCapability() { // Create a server without prompt capabilities diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java index 0b38da85..9cbf588a 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java @@ -140,6 +140,28 @@ void testAddDuplicateTool() { assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException(); } + @Test + void testPutTool() { + var mcpSyncServer = McpServer.sync(createMcpTransportProvider()) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().tools(true).build()) + .build(); + + Tool toolV1 = new McpSchema.Tool(TEST_TOOL_NAME, "Tool with version 1.0.0", emptyJsonSchema); + + assertThatCode(() -> mcpSyncServer.putTool(new McpServerFeatures.SyncToolSpecification(toolV1, + (exchange, args) -> new CallToolResult(List.of(), false)))) + .doesNotThrowAnyException(); + + Tool toolV2 = new McpSchema.Tool(TEST_TOOL_NAME, "Tool with version 2.0.0", emptyJsonSchema); + + assertThatCode(() -> mcpSyncServer.putTool(new McpServerFeatures.SyncToolSpecification(toolV2, + (exchange, args) -> new CallToolResult(List.of(), false)))) + .doesNotThrowAnyException(); + + assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException(); + } + @Test void testRemoveTool() { Tool tool = new McpSchema.Tool(TEST_TOOL_NAME, "Test tool", emptyJsonSchema); @@ -197,7 +219,7 @@ void testAddResource() { .capabilities(ServerCapabilities.builder().resources(true, false).build()) .build(); - Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "text/plain", "Test resource description", + Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "Test resource description", "text/plain", null); McpServerFeatures.SyncResourceSpecification specification = new McpServerFeatures.SyncResourceSpecification( resource, (exchange, req) -> new ReadResourceResult(List.of())); @@ -227,7 +249,7 @@ void testAddResourceWithoutCapability() { .serverInfo("test-server", "1.0.0") .build(); - Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "text/plain", "Test resource description", + Resource resource = new Resource(TEST_RESOURCE_URI, "Test Resource", "Test resource description", "text/plain", null); McpServerFeatures.SyncResourceSpecification specification = new McpServerFeatures.SyncResourceSpecification( resource, (exchange, req) -> new ReadResourceResult(List.of())); @@ -236,6 +258,28 @@ void testAddResourceWithoutCapability() { .hasMessage("Server must be configured with resource capabilities"); } + @Test + void testPutResource() { + var mcpSyncServer = McpServer.sync(createMcpTransportProvider()) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().resources(true, false).build()) + .build(); + + Resource resourceV1 = new Resource(TEST_RESOURCE_URI, "Test Resource", "Resource with version 1.0.0", + "text/plain", null); + McpServerFeatures.SyncResourceSpecification specificationV1 = new McpServerFeatures.SyncResourceSpecification( + resourceV1, (exchange, req) -> new ReadResourceResult(List.of())); + assertThatCode(() -> mcpSyncServer.putResource(specificationV1)).doesNotThrowAnyException(); + + Resource resourceV2 = new Resource(TEST_RESOURCE_URI, "Test Resource", "Resource with version 2.0.0", + "text/plain", null); + McpServerFeatures.SyncResourceSpecification specificationV2 = new McpServerFeatures.SyncResourceSpecification( + resourceV2, (exchange, req) -> new ReadResourceResult(List.of())); + assertThatCode(() -> mcpSyncServer.putResource(specificationV2)).doesNotThrowAnyException(); + + assertThatCode(() -> mcpSyncServer.closeGracefully()).doesNotThrowAnyException(); + } + @Test void testRemoveResourceWithoutCapability() { var serverWithoutResources = McpServer.sync(createMcpTransportProvider()) @@ -286,6 +330,26 @@ void testAddPromptWithoutCapability() { .hasMessage("Server must be configured with prompt capabilities"); } + @Test + void testPutPrompt() { + var mcpSyncServer = McpServer.sync(createMcpTransportProvider()) + .serverInfo("test-server", "1.0.0") + .capabilities(ServerCapabilities.builder().prompts(false).build()) + .build(); + + Prompt promptV1 = new Prompt(TEST_PROMPT_NAME, "Prompt with version 1.0.0", List.of()); + McpServerFeatures.SyncPromptSpecification specificationV1 = new McpServerFeatures.SyncPromptSpecification( + promptV1, (exchange, req) -> new GetPromptResult("Test prompt description", List + .of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content"))))); + assertThatCode(() -> mcpSyncServer.putPrompt(specificationV1)).doesNotThrowAnyException(); + + Prompt promptV2 = new Prompt(TEST_PROMPT_NAME, "Prompt with version 2.0.0", List.of()); + McpServerFeatures.SyncPromptSpecification specificationV2 = new McpServerFeatures.SyncPromptSpecification( + promptV2, (exchange, req) -> new GetPromptResult("Test prompt description", List + .of(new PromptMessage(McpSchema.Role.ASSISTANT, new McpSchema.TextContent("Test content"))))); + assertThatCode(() -> mcpSyncServer.putPrompt(specificationV2)).doesNotThrowAnyException(); + } + @Test void testRemovePromptWithoutCapability() { var serverWithoutPrompts = McpServer.sync(createMcpTransportProvider())