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:
Maria Tigina 2025-11-03 19:21:43 +01:00 • committed by GitHub
commit 2fc0b40906
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 436 additions and 530 deletions

View file

@ -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) }
}
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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")
}
}