feat(server): Support removing tools, prompts and resources (#85)
* Add removing APIs for tools, prompts and resources * Add remove tests * API dump
This commit is contained in:
parent
c3efd65379
commit
d7b31aa37b
3 changed files with 652 additions and 0 deletions
|
|
@ -2794,6 +2794,12 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server : io/modelcontextp
|
||||||
public final fun onClose (Lkotlin/jvm/functions/Function0;)V
|
public final fun onClose (Lkotlin/jvm/functions/Function0;)V
|
||||||
public final fun onInitalized (Lkotlin/jvm/functions/Function0;)V
|
public final fun onInitalized (Lkotlin/jvm/functions/Function0;)V
|
||||||
public final fun ping (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
public final fun ping (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
public final fun removePrompt (Ljava/lang/String;)Z
|
||||||
|
public final fun removePrompts (Ljava/util/List;)I
|
||||||
|
public final fun removeResource (Ljava/lang/String;)Z
|
||||||
|
public final fun removeResources (Ljava/util/List;)I
|
||||||
|
public final fun removeTool (Ljava/lang/String;)Z
|
||||||
|
public final fun removeTools (Ljava/util/List;)I
|
||||||
public final fun sendLoggingMessage (Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
public final fun sendLoggingMessage (Lio/modelcontextprotocol/kotlin/sdk/LoggingMessageNotification;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
public final fun sendPromptListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
public final fun sendPromptListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
public final fun sendResourceListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
public final fun sendResourceListChanged (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,60 @@ public open class Server(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a single tool by name.
|
||||||
|
*
|
||||||
|
* @param name The name of the tool to remove.
|
||||||
|
* @return True if the tool was removed, false if it wasn't found.
|
||||||
|
* @throws IllegalStateException If the server does not support tools.
|
||||||
|
*/
|
||||||
|
public fun removeTool(name: String): Boolean {
|
||||||
|
if (capabilities.tools == null) {
|
||||||
|
logger.error { "Failed to remove tool '$name': Server does not support tools capability" }
|
||||||
|
throw IllegalStateException("Server does not support tools capability.")
|
||||||
|
}
|
||||||
|
logger.info { "Removing tool: $name" }
|
||||||
|
val removed = tools.remove(name) != null
|
||||||
|
logger.debug {
|
||||||
|
if (removed) {
|
||||||
|
"Tool removed: $name"
|
||||||
|
} else {
|
||||||
|
"Tool not found: $name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes multiple tools at once.
|
||||||
|
*
|
||||||
|
* @param toolNames A list of tool names to remove.
|
||||||
|
* @return The number of tools that were successfully removed.
|
||||||
|
* @throws IllegalStateException If the server does not support tools.
|
||||||
|
*/
|
||||||
|
public fun removeTools(toolNames: List<String>): Int {
|
||||||
|
if (capabilities.tools == null) {
|
||||||
|
logger.error { "Failed to remove tools: Server does not support tools capability" }
|
||||||
|
throw IllegalStateException("Server does not support tools capability.")
|
||||||
|
}
|
||||||
|
logger.info { "Removing ${toolNames.size} tools" }
|
||||||
|
var removedCount = 0
|
||||||
|
for (name in toolNames) {
|
||||||
|
logger.debug { "Removing tool: $name" }
|
||||||
|
if (tools.remove(name) != null) {
|
||||||
|
removedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info {
|
||||||
|
if (removedCount > 0) {
|
||||||
|
"Removed $removedCount tools"
|
||||||
|
} else {
|
||||||
|
"No tools were removed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removedCount
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a single prompt. The prompt can then be retrieved by the client.
|
* Registers a single prompt. The prompt can then be retrieved by the client.
|
||||||
*
|
*
|
||||||
|
|
@ -226,6 +280,60 @@ public open class Server(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a single prompt by name.
|
||||||
|
*
|
||||||
|
* @param name The name of the prompt to remove.
|
||||||
|
* @return True if the prompt was removed, false if it wasn't found.
|
||||||
|
* @throws IllegalStateException If the server does not support prompts.
|
||||||
|
*/
|
||||||
|
public fun removePrompt(name: String): Boolean {
|
||||||
|
if (capabilities.prompts == null) {
|
||||||
|
logger.error { "Failed to remove prompt '$name': Server does not support prompts capability" }
|
||||||
|
throw IllegalStateException("Server does not support prompts capability.")
|
||||||
|
}
|
||||||
|
logger.info { "Removing prompt: $name" }
|
||||||
|
val removed = prompts.remove(name) != null
|
||||||
|
logger.debug {
|
||||||
|
if (removed) {
|
||||||
|
"Prompt removed: $name"
|
||||||
|
} else {
|
||||||
|
"Prompt not found: $name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes multiple prompts at once.
|
||||||
|
*
|
||||||
|
* @param promptNames A list of prompt names to remove.
|
||||||
|
* @return The number of prompts that were successfully removed.
|
||||||
|
* @throws IllegalStateException If the server does not support prompts.
|
||||||
|
*/
|
||||||
|
public fun removePrompts(promptNames: List<String>): Int {
|
||||||
|
if (capabilities.prompts == null) {
|
||||||
|
logger.error { "Failed to remove prompts: Server does not support prompts capability" }
|
||||||
|
throw IllegalStateException("Server does not support prompts capability.")
|
||||||
|
}
|
||||||
|
logger.info { "Removing ${promptNames.size} prompts" }
|
||||||
|
var removedCount = 0
|
||||||
|
for (name in promptNames) {
|
||||||
|
logger.debug { "Removing prompt: $name" }
|
||||||
|
if (prompts.remove(name) != null) {
|
||||||
|
removedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info {
|
||||||
|
if (removedCount > 0) {
|
||||||
|
"Removed $removedCount prompts"
|
||||||
|
} else {
|
||||||
|
"No prompts were removed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removedCount
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a single resource. The resource content can then be read by the client.
|
* Registers a single resource. The resource content can then be read by the client.
|
||||||
*
|
*
|
||||||
|
|
@ -269,6 +377,60 @@ public open class Server(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a single resource by URI.
|
||||||
|
*
|
||||||
|
* @param uri The URI of the resource to remove.
|
||||||
|
* @return True if the resource was removed, false if it wasn't found.
|
||||||
|
* @throws IllegalStateException If the server does not support resources.
|
||||||
|
*/
|
||||||
|
public fun removeResource(uri: String): Boolean {
|
||||||
|
if (capabilities.resources == null) {
|
||||||
|
logger.error { "Failed to remove resource '$uri': Server does not support resources capability" }
|
||||||
|
throw IllegalStateException("Server does not support resources capability.")
|
||||||
|
}
|
||||||
|
logger.info { "Removing resource: $uri" }
|
||||||
|
val removed = resources.remove(uri) != null
|
||||||
|
logger.debug {
|
||||||
|
if (removed) {
|
||||||
|
"Resource removed: $uri"
|
||||||
|
} else {
|
||||||
|
"Resource not found: $uri"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes multiple resources at once.
|
||||||
|
*
|
||||||
|
* @param uris A list of resource URIs to remove.
|
||||||
|
* @return The number of resources that were successfully removed.
|
||||||
|
* @throws IllegalStateException If the server does not support resources.
|
||||||
|
*/
|
||||||
|
public fun removeResources(uris: List<String>): Int {
|
||||||
|
if (capabilities.resources == null) {
|
||||||
|
logger.error { "Failed to remove resources: Server does not support resources capability" }
|
||||||
|
throw IllegalStateException("Server does not support resources capability.")
|
||||||
|
}
|
||||||
|
logger.info { "Removing ${uris.size} resources" }
|
||||||
|
var removedCount = 0
|
||||||
|
for (uri in uris) {
|
||||||
|
logger.debug { "Removing resource: $uri" }
|
||||||
|
if (resources.remove(uri) != null) {
|
||||||
|
removedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info {
|
||||||
|
if (removedCount > 0) {
|
||||||
|
"Removed $removedCount resources"
|
||||||
|
} else {
|
||||||
|
"No resources were removed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removedCount
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a ping request to the client to check connectivity.
|
* Sends a ping request to the client to check connectivity.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
484
src/jvmTest/kotlin/server/ServerTest.kt
Normal file
484
src/jvmTest/kotlin/server/ServerTest.kt
Normal file
|
|
@ -0,0 +1,484 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ClientCapabilities
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.EmptyJsonObject
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.GetPromptResult
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Implementation
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Method
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Prompt
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.PromptListChangedNotification
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Resource
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Tool
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.client.Client
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.client.ClientOptions
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.server.RegisteredPrompt
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.server.RegisteredResource
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.server.RegisteredTool
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.server.Server
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
|
||||||
|
import InMemoryTransport
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.PromptMessage
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.PromptMessageContent
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Role
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.TextContent
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.TextResourceContents
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class ServerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removeTool should remove a tool`() = runTest {
|
||||||
|
// Create server with tools capability
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = ServerCapabilities(
|
||||||
|
tools = ServerCapabilities.Tools(null)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val server = Server(
|
||||||
|
Implementation(name = "test server", version = "1.0"),
|
||||||
|
serverOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add a tool
|
||||||
|
server.addTool("test-tool", "Test Tool", Tool.Input()) { request ->
|
||||||
|
CallToolResult(listOf(TextContent("Test result")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup client
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
val client = Client(
|
||||||
|
clientInfo = Implementation(name = "test client", version = "1.0"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connect client and server
|
||||||
|
launch { client.connect(clientTransport) }
|
||||||
|
launch { server.connect(serverTransport) }
|
||||||
|
|
||||||
|
// Remove the tool
|
||||||
|
val result = server.removeTool("test-tool")
|
||||||
|
|
||||||
|
// Verify the tool was removed
|
||||||
|
assertTrue(result, "Tool should be removed successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removeTool should return false when tool does not exist`() = runTest {
|
||||||
|
// Create server with tools capability
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = ServerCapabilities(
|
||||||
|
tools = ServerCapabilities.Tools(null)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val server = Server(
|
||||||
|
Implementation(name = "test server", version = "1.0"),
|
||||||
|
serverOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setup client
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
val client = Client(
|
||||||
|
clientInfo = Implementation(name = "test client", version = "1.0"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Track notifications
|
||||||
|
var toolListChangedNotificationReceived = false
|
||||||
|
client.setNotificationHandler<ToolListChangedNotification>(Method.Defined.NotificationsToolsListChanged) {
|
||||||
|
toolListChangedNotificationReceived = true
|
||||||
|
CompletableDeferred(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect client and server
|
||||||
|
launch { client.connect(clientTransport) }
|
||||||
|
launch { server.connect(serverTransport) }
|
||||||
|
|
||||||
|
// Try to remove a non-existent tool
|
||||||
|
val result = server.removeTool("non-existent-tool")
|
||||||
|
|
||||||
|
// Verify the result
|
||||||
|
assertFalse(result, "Removing non-existent tool should return false")
|
||||||
|
assertFalse(toolListChangedNotificationReceived, "No notification should be sent when tool doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removeTool should throw when tools capability is not supported`() = runTest {
|
||||||
|
// Create server without tools capability
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = ServerCapabilities()
|
||||||
|
)
|
||||||
|
val server = Server(
|
||||||
|
Implementation(name = "test server", version = "1.0"),
|
||||||
|
serverOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify that removing a tool throws an exception
|
||||||
|
val exception = assertThrows<IllegalStateException> {
|
||||||
|
server.removeTool("test-tool")
|
||||||
|
}
|
||||||
|
assertEquals("Server does not support tools capability.", exception.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removeTools should remove multiple tools`() = runTest {
|
||||||
|
// Create server with tools capability
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = ServerCapabilities(
|
||||||
|
tools = ServerCapabilities.Tools(null)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val server = Server(
|
||||||
|
Implementation(name = "test server", version = "1.0"),
|
||||||
|
serverOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add tools
|
||||||
|
server.addTool("test-tool-1", "Test Tool 1") { request ->
|
||||||
|
CallToolResult(listOf(TextContent("Test result 1")))
|
||||||
|
}
|
||||||
|
server.addTool("test-tool-2", "Test Tool 2") { request ->
|
||||||
|
CallToolResult(listOf(TextContent("Test result 2")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup client
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
val client = Client(
|
||||||
|
clientInfo = Implementation(name = "test client", version = "1.0"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connect client and server
|
||||||
|
launch { client.connect(clientTransport) }
|
||||||
|
launch { server.connect(serverTransport) }
|
||||||
|
|
||||||
|
// Remove the tools
|
||||||
|
val result = server.removeTools(listOf("test-tool-1", "test-tool-2"))
|
||||||
|
|
||||||
|
// Verify the tools were removed
|
||||||
|
assertEquals(2, result, "Both tools should be removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removePrompt should remove a prompt`() = runTest {
|
||||||
|
// Create server with prompts capability
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = ServerCapabilities(
|
||||||
|
prompts = ServerCapabilities.Prompts(listChanged = false)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val server = Server(
|
||||||
|
Implementation(name = "test server", version = "1.0"),
|
||||||
|
serverOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add a prompt
|
||||||
|
val testPrompt = Prompt("test-prompt", "Test Prompt", null)
|
||||||
|
server.addPrompt(testPrompt) { request ->
|
||||||
|
GetPromptResult(
|
||||||
|
description = "Test prompt description",
|
||||||
|
messages = listOf()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup client
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
val client = Client(
|
||||||
|
clientInfo = Implementation(name = "test client", version = "1.0"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connect client and server
|
||||||
|
launch { client.connect(clientTransport) }
|
||||||
|
launch { server.connect(serverTransport) }
|
||||||
|
|
||||||
|
// Remove the prompt
|
||||||
|
val result = server.removePrompt(testPrompt.name)
|
||||||
|
|
||||||
|
// Verify the prompt was removed
|
||||||
|
assertTrue(result, "Prompt should be removed successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removePrompts should remove multiple prompts and send notification`() = runTest {
|
||||||
|
// Create server with prompts capability
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = ServerCapabilities(
|
||||||
|
prompts = ServerCapabilities.Prompts(listChanged = false)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val server = Server(
|
||||||
|
Implementation(name = "test server", version = "1.0"),
|
||||||
|
serverOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add prompts
|
||||||
|
val testPrompt1 = Prompt("test-prompt-1", "Test Prompt 1", null)
|
||||||
|
val testPrompt2 = Prompt("test-prompt-2", "Test Prompt 2", null)
|
||||||
|
server.addPrompt(testPrompt1) { request ->
|
||||||
|
GetPromptResult(
|
||||||
|
description = "Test prompt description 1",
|
||||||
|
messages = listOf()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
server.addPrompt(testPrompt2) { request ->
|
||||||
|
GetPromptResult(
|
||||||
|
description = "Test prompt description 2",
|
||||||
|
messages = listOf()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup client
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
val client = Client(
|
||||||
|
clientInfo = Implementation(name = "test client", version = "1.0"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connect client and server
|
||||||
|
launch { client.connect(clientTransport) }
|
||||||
|
launch { server.connect(serverTransport) }
|
||||||
|
|
||||||
|
// Remove the prompts
|
||||||
|
val result = server.removePrompts(listOf(testPrompt1.name, testPrompt2.name))
|
||||||
|
|
||||||
|
// Verify the prompts were removed
|
||||||
|
assertEquals(2, result, "Both prompts should be removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removeResource should remove a resource and send notification`() = runTest {
|
||||||
|
// Create server with resources capability
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = ServerCapabilities(
|
||||||
|
resources = ServerCapabilities.Resources(null, null)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val server = Server(
|
||||||
|
Implementation(name = "test server", version = "1.0"),
|
||||||
|
serverOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add a resource
|
||||||
|
val testResourceUri = "test://resource"
|
||||||
|
server.addResource(
|
||||||
|
uri = testResourceUri,
|
||||||
|
name = "Test Resource",
|
||||||
|
description = "A test resource",
|
||||||
|
mimeType = "text/plain"
|
||||||
|
) { request ->
|
||||||
|
ReadResourceResult(
|
||||||
|
contents = listOf(
|
||||||
|
TextResourceContents(
|
||||||
|
text = "Test resource content",
|
||||||
|
uri = testResourceUri,
|
||||||
|
mimeType = "text/plain",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup client
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
val client = Client(
|
||||||
|
clientInfo = Implementation(name = "test client", version = "1.0"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connect client and server
|
||||||
|
launch { client.connect(clientTransport) }
|
||||||
|
launch { server.connect(serverTransport) }
|
||||||
|
|
||||||
|
// Remove the resource
|
||||||
|
val result = server.removeResource(testResourceUri)
|
||||||
|
|
||||||
|
// Verify the resource was removed
|
||||||
|
assertTrue(result, "Resource should be removed successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removeResources should remove multiple resources and send notification`() = runTest {
|
||||||
|
// Create server with resources capability
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = ServerCapabilities(
|
||||||
|
resources = ServerCapabilities.Resources(null, null)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val server = Server(
|
||||||
|
Implementation(name = "test server", version = "1.0"),
|
||||||
|
serverOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Add resources
|
||||||
|
val testResourceUri1 = "test://resource1"
|
||||||
|
val testResourceUri2 = "test://resource2"
|
||||||
|
server.addResource(
|
||||||
|
uri = testResourceUri1,
|
||||||
|
name = "Test Resource 1",
|
||||||
|
description = "A test resource 1",
|
||||||
|
mimeType = "text/plain"
|
||||||
|
) { request ->
|
||||||
|
ReadResourceResult(
|
||||||
|
contents = listOf(
|
||||||
|
TextResourceContents(
|
||||||
|
text = "Test resource content 1",
|
||||||
|
uri = testResourceUri1,
|
||||||
|
mimeType = "text/plain",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
server.addResource(
|
||||||
|
uri = testResourceUri2,
|
||||||
|
name = "Test Resource 2",
|
||||||
|
description = "A test resource 2",
|
||||||
|
mimeType = "text/plain"
|
||||||
|
) { request ->
|
||||||
|
ReadResourceResult(
|
||||||
|
contents = listOf(
|
||||||
|
TextResourceContents(
|
||||||
|
text = "Test resource content 2",
|
||||||
|
uri = testResourceUri2,
|
||||||
|
mimeType = "text/plain",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup client
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
val client = Client(
|
||||||
|
clientInfo = Implementation(name = "test client", version = "1.0"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Connect client and server
|
||||||
|
launch { client.connect(clientTransport) }
|
||||||
|
launch { server.connect(serverTransport) }
|
||||||
|
|
||||||
|
// Remove the resources
|
||||||
|
val result = server.removeResources(listOf(testResourceUri1, testResourceUri2))
|
||||||
|
|
||||||
|
// Verify the resources were removed
|
||||||
|
assertEquals(2, result, "Both resources should be removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removePrompt should return false when prompt does not exist`() = runTest {
|
||||||
|
// Create server with prompts capability
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = ServerCapabilities(
|
||||||
|
prompts = ServerCapabilities.Prompts(listChanged = false)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val server = Server(
|
||||||
|
Implementation(name = "test server", version = "1.0"),
|
||||||
|
serverOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setup client
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
val client = Client(
|
||||||
|
clientInfo = Implementation(name = "test client", version = "1.0"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Track notifications
|
||||||
|
var promptListChangedNotificationReceived = false
|
||||||
|
client.setNotificationHandler<PromptListChangedNotification>(Method.Defined.NotificationsPromptsListChanged) {
|
||||||
|
promptListChangedNotificationReceived = true
|
||||||
|
CompletableDeferred(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect client and server
|
||||||
|
launch { client.connect(clientTransport) }
|
||||||
|
launch { server.connect(serverTransport) }
|
||||||
|
|
||||||
|
// Try to remove a non-existent prompt
|
||||||
|
val result = server.removePrompt("non-existent-prompt")
|
||||||
|
|
||||||
|
// Verify the result
|
||||||
|
assertFalse(result, "Removing non-existent prompt should return false")
|
||||||
|
assertFalse(promptListChangedNotificationReceived, "No notification should be sent when prompt doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removePrompt should throw when prompts capability is not supported`() = runTest {
|
||||||
|
// Create server without prompts capability
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = ServerCapabilities()
|
||||||
|
)
|
||||||
|
val server = Server(
|
||||||
|
Implementation(name = "test server", version = "1.0"),
|
||||||
|
serverOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify that removing a prompt throws an exception
|
||||||
|
val exception = assertThrows<IllegalStateException> {
|
||||||
|
server.removePrompt("test-prompt")
|
||||||
|
}
|
||||||
|
assertEquals("Server does not support prompts capability.", exception.message)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removeResource should return false when resource does not exist`() = runTest {
|
||||||
|
// Create server with resources capability
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = ServerCapabilities(
|
||||||
|
resources = ServerCapabilities.Resources(null, null)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val server = Server(
|
||||||
|
Implementation(name = "test server", version = "1.0"),
|
||||||
|
serverOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Setup client
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
val client = Client(
|
||||||
|
clientInfo = Implementation(name = "test client", version = "1.0"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Track notifications
|
||||||
|
var resourceListChangedNotificationReceived = false
|
||||||
|
client.setNotificationHandler<ResourceListChangedNotification>(Method.Defined.NotificationsResourcesListChanged) {
|
||||||
|
resourceListChangedNotificationReceived = true
|
||||||
|
CompletableDeferred(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect client and server
|
||||||
|
launch { client.connect(clientTransport) }
|
||||||
|
launch { server.connect(serverTransport) }
|
||||||
|
|
||||||
|
// Try to remove a non-existent resource
|
||||||
|
val result = server.removeResource("non-existent-resource")
|
||||||
|
|
||||||
|
// Verify the result
|
||||||
|
assertFalse(result, "Removing non-existent resource should return false")
|
||||||
|
assertFalse(resourceListChangedNotificationReceived, "No notification should be sent when resource doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removeResource should throw when resources capability is not supported`() = runTest {
|
||||||
|
// Create server without resources capability
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = ServerCapabilities()
|
||||||
|
)
|
||||||
|
val server = Server(
|
||||||
|
Implementation(name = "test server", version = "1.0"),
|
||||||
|
serverOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
// Verify that removing a resource throws an exception
|
||||||
|
val exception = assertThrows<IllegalStateException> {
|
||||||
|
server.removeResource("test://resource")
|
||||||
|
}
|
||||||
|
assertEquals("Server does not support resources capability.", exception.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue