Refactor ServerTest test structure (#366)
Split ServerTest per feature test classes <!-- Provide a brief summary of your changes --> ## Motivation and Context <!-- Why is this change needed? What problem does it solve? --> ## How Has This Been Tested? <!-- Have you tested this in a real application? Which scenarios were tested? --> ## Breaking Changes <!-- Will users need to update their code or configurations? --> ## Types of changes <!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: --> - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update ## Checklist <!-- Go over all the following points, and put an `x` in all the boxes that apply. --> - [ ] I have read the [MCP Documentation](https://modelcontextprotocol.io) - [ ] My code follows the repository's style guidelines - [ ] New and existing tests pass locally - [ ] I have added appropriate error handling - [ ] I have added or updated documentation as needed ## Additional context <!-- Add any other context, implementation notes, or design decisions -->
This commit is contained in:
parent
913c0d111e
commit
2fc0b40906
6 changed files with 436 additions and 530 deletions
|
|
@ -0,0 +1,44 @@
|
||||||
|
package io.modelcontextprotocol.kotlin.sdk.server
|
||||||
|
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Implementation
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.client.Client
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.junit.jupiter.api.BeforeEach
|
||||||
|
|
||||||
|
abstract class AbstractServerFeaturesTest {
|
||||||
|
|
||||||
|
protected lateinit var server: Server
|
||||||
|
protected lateinit var client: Client
|
||||||
|
|
||||||
|
abstract fun getServerCapabilities(): ServerCapabilities
|
||||||
|
|
||||||
|
protected open fun getServerInstructionsProvider(): (() -> String)? = null
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
fun setUp() {
|
||||||
|
val serverOptions = ServerOptions(
|
||||||
|
capabilities = getServerCapabilities(),
|
||||||
|
)
|
||||||
|
|
||||||
|
server = Server(
|
||||||
|
serverInfo = Implementation(name = "test server", version = "1.0"),
|
||||||
|
options = serverOptions,
|
||||||
|
instructionsProvider = getServerInstructionsProvider(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
|
||||||
|
client = Client(
|
||||||
|
clientInfo = Implementation(name = "test client", version = "1.0"),
|
||||||
|
)
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
// Connect client and server
|
||||||
|
launch { client.connect(clientTransport) }
|
||||||
|
launch { server.createSession(serverTransport) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
package io.modelcontextprotocol.kotlin.sdk.server
|
||||||
|
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Implementation
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.client.Client
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertNull
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class ServerInstructionsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Server constructor should accept instructions provider parameter`() = runTest {
|
||||||
|
val serverInfo = Implementation(name = "test server", version = "1.0")
|
||||||
|
val serverOptions = ServerOptions(capabilities = ServerCapabilities())
|
||||||
|
val instructions = "This is a test server. Use it for testing purposes only."
|
||||||
|
|
||||||
|
val server = Server(serverInfo, serverOptions, { instructions })
|
||||||
|
|
||||||
|
// The instructions should be stored internally and used in handleInitialize
|
||||||
|
// We can't directly access the private field, but we can test it through initialization
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))
|
||||||
|
|
||||||
|
server.createSession(serverTransport)
|
||||||
|
client.connect(clientTransport)
|
||||||
|
|
||||||
|
assertEquals(instructions, client.serverInstructions)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Server constructor should accept instructions parameter`() = runTest {
|
||||||
|
val serverInfo = Implementation(name = "test server", version = "1.0")
|
||||||
|
val serverOptions = ServerOptions(capabilities = ServerCapabilities())
|
||||||
|
val instructions = "This is a test server. Use it for testing purposes only."
|
||||||
|
|
||||||
|
val server = Server(serverInfo, serverOptions, instructions)
|
||||||
|
|
||||||
|
// The instructions should be stored internally and used in handleInitialize
|
||||||
|
// We can't directly access the private field, but we can test it through initialization
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))
|
||||||
|
|
||||||
|
server.createSession(serverTransport)
|
||||||
|
client.connect(clientTransport)
|
||||||
|
|
||||||
|
assertEquals(instructions, client.serverInstructions)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `Server constructor should work without instructions parameter`() = runTest {
|
||||||
|
val serverInfo = Implementation(name = "test server", version = "1.0")
|
||||||
|
val serverOptions = ServerOptions(capabilities = ServerCapabilities())
|
||||||
|
|
||||||
|
// Test that server works when instructions parameter is omitted (defaults to null)
|
||||||
|
val server = Server(serverInfo, serverOptions)
|
||||||
|
|
||||||
|
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
||||||
|
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))
|
||||||
|
|
||||||
|
server.createSession(serverTransport)
|
||||||
|
client.connect(clientTransport)
|
||||||
|
|
||||||
|
assertNull(client.serverInstructions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package io.modelcontextprotocol.kotlin.sdk.server
|
||||||
|
|
||||||
|
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.ServerCapabilities
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
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 ServerPromptsTest : AbstractServerFeaturesTest() {
|
||||||
|
|
||||||
|
override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities(
|
||||||
|
prompts = ServerCapabilities.Prompts(false),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removePrompt should remove a prompt`() = runTest {
|
||||||
|
// Add a prompt
|
||||||
|
val testPrompt = Prompt("test-prompt", "Test Prompt", null)
|
||||||
|
server.addPrompt(testPrompt) {
|
||||||
|
GetPromptResult(
|
||||||
|
description = "Test prompt description",
|
||||||
|
messages = listOf(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// 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) {
|
||||||
|
GetPromptResult(
|
||||||
|
description = "Test prompt description 1",
|
||||||
|
messages = listOf(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
server.addPrompt(testPrompt2) {
|
||||||
|
GetPromptResult(
|
||||||
|
description = "Test prompt description 2",
|
||||||
|
messages = listOf(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 `removePrompt should return false when prompt does not exist`() = runTest {
|
||||||
|
// Track notifications
|
||||||
|
var promptListChangedNotificationReceived = false
|
||||||
|
client.setNotificationHandler<PromptListChangedNotification>(Method.Defined.NotificationsPromptsListChanged) {
|
||||||
|
promptListChangedNotificationReceived = true
|
||||||
|
CompletableDeferred(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
package io.modelcontextprotocol.kotlin.sdk.server
|
||||||
|
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Implementation
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Method
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.TextResourceContents
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
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 ServerResourcesTest : AbstractServerFeaturesTest() {
|
||||||
|
|
||||||
|
override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities(
|
||||||
|
resources = ServerCapabilities.Resources(null, null),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removeResource should remove a resource and send notification`() = runTest {
|
||||||
|
// Add a resource
|
||||||
|
val testResourceUri = "test://resource"
|
||||||
|
server.addResource(
|
||||||
|
uri = testResourceUri,
|
||||||
|
name = "Test Resource",
|
||||||
|
description = "A test resource",
|
||||||
|
mimeType = "text/plain",
|
||||||
|
) {
|
||||||
|
ReadResourceResult(
|
||||||
|
contents = listOf(
|
||||||
|
TextResourceContents(
|
||||||
|
text = "Test resource content",
|
||||||
|
uri = testResourceUri,
|
||||||
|
mimeType = "text/plain",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// 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",
|
||||||
|
) {
|
||||||
|
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",
|
||||||
|
) {
|
||||||
|
ReadResourceResult(
|
||||||
|
contents = listOf(
|
||||||
|
TextResourceContents(
|
||||||
|
text = "Test resource content 2",
|
||||||
|
uri = testResourceUri2,
|
||||||
|
mimeType = "text/plain",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 `removeResource should return false when resource does not exist`() = runTest {
|
||||||
|
// Track notifications
|
||||||
|
var resourceListChangedNotificationReceived = false
|
||||||
|
client.setNotificationHandler<ResourceListChangedNotification>(
|
||||||
|
Method.Defined.NotificationsResourcesListChanged,
|
||||||
|
) {
|
||||||
|
resourceListChangedNotificationReceived = true
|
||||||
|
CompletableDeferred(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,530 +0,0 @@
|
||||||
package io.modelcontextprotocol.kotlin.sdk.server
|
|
||||||
|
|
||||||
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
|
|
||||||
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.ReadResourceResult
|
|
||||||
import io.modelcontextprotocol.kotlin.sdk.ResourceListChangedNotification
|
|
||||||
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
|
|
||||||
import io.modelcontextprotocol.kotlin.sdk.TextContent
|
|
||||||
import io.modelcontextprotocol.kotlin.sdk.TextResourceContents
|
|
||||||
import io.modelcontextprotocol.kotlin.sdk.Tool
|
|
||||||
import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification
|
|
||||||
import io.modelcontextprotocol.kotlin.sdk.client.Client
|
|
||||||
import io.modelcontextprotocol.kotlin.sdk.shared.InMemoryTransport
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import org.junit.jupiter.api.assertNull
|
|
||||||
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()) {
|
|
||||||
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.createSession(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.createSession(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") {
|
|
||||||
CallToolResult(listOf(TextContent("Test result 1")))
|
|
||||||
}
|
|
||||||
server.addTool("test-tool-2", "Test Tool 2") {
|
|
||||||
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.createSession(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) {
|
|
||||||
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.createSession(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) {
|
|
||||||
GetPromptResult(
|
|
||||||
description = "Test prompt description 1",
|
|
||||||
messages = listOf(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
server.addPrompt(testPrompt2) {
|
|
||||||
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.createSession(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",
|
|
||||||
) {
|
|
||||||
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.createSession(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",
|
|
||||||
) {
|
|
||||||
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",
|
|
||||||
) {
|
|
||||||
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.createSession(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.createSession(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.createSession(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)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Server constructor should accept instructions provider parameter`() = runTest {
|
|
||||||
val serverInfo = Implementation(name = "test server", version = "1.0")
|
|
||||||
val serverOptions = ServerOptions(capabilities = ServerCapabilities())
|
|
||||||
val instructions = "This is a test server. Use it for testing purposes only."
|
|
||||||
|
|
||||||
val server = Server(serverInfo, serverOptions, { instructions })
|
|
||||||
|
|
||||||
// The instructions should be stored internally and used in handleInitialize
|
|
||||||
// We can't directly access the private field, but we can test it through initialization
|
|
||||||
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
|
||||||
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))
|
|
||||||
|
|
||||||
server.createSession(serverTransport)
|
|
||||||
client.connect(clientTransport)
|
|
||||||
|
|
||||||
assertEquals(instructions, client.serverInstructions)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Server constructor should accept instructions parameter`() = runTest {
|
|
||||||
val serverInfo = Implementation(name = "test server", version = "1.0")
|
|
||||||
val serverOptions = ServerOptions(capabilities = ServerCapabilities())
|
|
||||||
val instructions = "This is a test server. Use it for testing purposes only."
|
|
||||||
|
|
||||||
val server = Server(serverInfo, serverOptions, instructions)
|
|
||||||
|
|
||||||
// The instructions should be stored internally and used in handleInitialize
|
|
||||||
// We can't directly access the private field, but we can test it through initialization
|
|
||||||
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
|
||||||
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))
|
|
||||||
|
|
||||||
server.createSession(serverTransport)
|
|
||||||
client.connect(clientTransport)
|
|
||||||
|
|
||||||
assertEquals(instructions, client.serverInstructions)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Server constructor should work without instructions parameter`() = runTest {
|
|
||||||
val serverInfo = Implementation(name = "test server", version = "1.0")
|
|
||||||
val serverOptions = ServerOptions(capabilities = ServerCapabilities())
|
|
||||||
|
|
||||||
// Test that server works when instructions parameter is omitted (defaults to null)
|
|
||||||
val server = Server(serverInfo, serverOptions)
|
|
||||||
|
|
||||||
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
|
|
||||||
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))
|
|
||||||
|
|
||||||
server.createSession(serverTransport)
|
|
||||||
client.connect(clientTransport)
|
|
||||||
|
|
||||||
assertNull(client.serverInstructions)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
package io.modelcontextprotocol.kotlin.sdk.server
|
||||||
|
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Implementation
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Method
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.TextContent
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.Tool
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.ToolListChangedNotification
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
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 ServerToolsTest : AbstractServerFeaturesTest() {
|
||||||
|
|
||||||
|
override fun getServerCapabilities(): ServerCapabilities = ServerCapabilities(
|
||||||
|
tools = ServerCapabilities.Tools(null),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `removeTool should remove a tool`() = runTest {
|
||||||
|
// Add a tool
|
||||||
|
server.addTool("test-tool", "Test Tool", Tool.Input()) {
|
||||||
|
CallToolResult(listOf(TextContent("Test result")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// Track notifications
|
||||||
|
var toolListChangedNotificationReceived = false
|
||||||
|
client.setNotificationHandler<ToolListChangedNotification>(Method.Defined.NotificationsToolsListChanged) {
|
||||||
|
toolListChangedNotificationReceived = true
|
||||||
|
CompletableDeferred(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// Add tools
|
||||||
|
server.addTool("test-tool-1", "Test Tool 1") {
|
||||||
|
CallToolResult(listOf(TextContent("Test result 1")))
|
||||||
|
}
|
||||||
|
server.addTool("test-tool-2", "Test Tool 2") {
|
||||||
|
CallToolResult(listOf(TextContent("Test result 2")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue