Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ val server = Server(
listChanged = true
)
)
)
),
instructions = "This server provides example resources and demonstrates MCP capabilities."
)

// Add a resource
Expand Down Expand Up @@ -156,7 +157,8 @@ fun Application.module() {
prompts = ServerCapabilities.Prompts(listChanged = null),
resources = ServerCapabilities.Resources(subscribe = null, listChanged = null)
)
)
),
instructions = "This SSE server provides prompts and resources via Server-Sent Events."
)
}
}
Expand Down Expand Up @@ -184,7 +186,8 @@ fun Application.module() {
prompts = ServerCapabilities.Prompts(listChanged = null),
resources = ServerCapabilities.Resources(subscribe = null, listChanged = null)
)
)
),
instructions = "Connect via SSE to interact with this MCP server."
)
}
}
Expand Down
12 changes: 7 additions & 5 deletions kotlin-sdk-core/api/kotlin-sdk-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -975,16 +975,18 @@ public final class io/modelcontextprotocol/kotlin/sdk/InitializeRequest$Companio

public final class io/modelcontextprotocol/kotlin/sdk/InitializeResult : io/modelcontextprotocol/kotlin/sdk/ServerResult {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/InitializeResult$Companion;
public fun <init> (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lkotlinx/serialization/json/JsonObject;)V
public synthetic fun <init> (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V
public synthetic fun <init> (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;
public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/Implementation;
public final fun component4 ()Lkotlinx/serialization/json/JsonObject;
public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/InitializeResult;
public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/InitializeResult;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/InitializeResult;
public final fun component4 ()Ljava/lang/String;
public final fun component5 ()Lkotlinx/serialization/json/JsonObject;
public final fun copy (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/InitializeResult;
public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/InitializeResult;Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/Implementation;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/InitializeResult;
public fun equals (Ljava/lang/Object;)Z
public final fun getCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;
public final fun getInstructions ()Ljava/lang/String;
public final fun getProtocolVersion ()Ljava/lang/String;
public final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/Implementation;
public fun get_meta ()Lkotlinx/serialization/json/JsonObject;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,10 @@ public data class InitializeResult(
val protocolVersion: String = LATEST_PROTOCOL_VERSION,
val capabilities: ServerCapabilities = ServerCapabilities(),
val serverInfo: Implementation,
/**
* Optional instructions from the server to the client about how to use this server.
*/
val instructions: String? = null,
override val _meta: JsonObject = EmptyJsonObject,
) : ServerResult

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,4 +394,63 @@ class TypesTest {
assertEquals("id", request.argument.name)
assertEquals("123", request.argument.value)
}

// InitializeResult Tests
@Test
fun `should create InitializeResult with default instructions`() {
val serverInfo = Implementation(name = "test-server", version = "1.0.0")
val result = InitializeResult(
serverInfo = serverInfo,
)

assertEquals(LATEST_PROTOCOL_VERSION, result.protocolVersion)
assertEquals(serverInfo, result.serverInfo)
assertEquals(null, result.instructions)
}

@Test
fun `should create InitializeResult with custom instructions`() {
val serverInfo = Implementation(name = "test-server", version = "1.0.0")
val instructions = "Use this server to perform calculations. Call the 'add' tool to add numbers."
val result = InitializeResult(
serverInfo = serverInfo,
instructions = instructions,
)

assertEquals(LATEST_PROTOCOL_VERSION, result.protocolVersion)
assertEquals(serverInfo, result.serverInfo)
assertEquals(instructions, result.instructions)
}

@Test
fun `should serialize and deserialize InitializeResult with instructions`() {
val serverInfo = Implementation(name = "test-server", version = "1.0.0")
val instructions = "This server provides file system access. Use the 'read' tool to read files."
val result = InitializeResult(
serverInfo = serverInfo,
instructions = instructions,
)

val json = McpJson.encodeToString(result)
val decoded = McpJson.decodeFromString<InitializeResult>(json)
Comment on lines +434 to +435
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think we should introduce some serialization helper for tests like this, like here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would probably be applicable to all the tests in this file, so I would prefer to leave it to a different PR if that's ok

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with both statements


assertEquals(LATEST_PROTOCOL_VERSION, decoded.protocolVersion)
assertEquals(serverInfo, decoded.serverInfo)
assertEquals(instructions, decoded.instructions)
}

@Test
fun `should serialize and deserialize InitializeResult without instructions`() {
val serverInfo = Implementation(name = "test-server", version = "1.0.0")
val result = InitializeResult(
serverInfo = serverInfo,
)

val json = McpJson.encodeToString(result)
val decoded = McpJson.decodeFromString<InitializeResult>(json)

assertEquals(LATEST_PROTOCOL_VERSION, decoded.protocolVersion)
assertEquals(serverInfo, decoded.serverInfo)
assertEquals(null, decoded.instructions)
}
}
5 changes: 4 additions & 1 deletion kotlin-sdk-server/api/kotlin-sdk-server.api
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool {
}

public class io/modelcontextprotocol/kotlin/sdk/server/Server : io/modelcontextprotocol/kotlin/sdk/shared/Protocol {
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;)V
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;)V
public synthetic fun <init> (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun addPrompt (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)V
public final fun addPrompt (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function2;)V
public static synthetic fun addPrompt$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V
Expand All @@ -66,8 +67,10 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server : io/modelcontextp
public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Lio/modelcontextprotocol/kotlin/sdk/CreateMessageRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public final fun getClientCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ClientCapabilities;
public final fun getClientVersion ()Lio/modelcontextprotocol/kotlin/sdk/Implementation;
protected final fun getInstructions ()Ljava/lang/String;
public final fun getPrompts ()Ljava/util/Map;
public final fun getResources ()Ljava/util/Map;
protected final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/Implementation;
public final fun getTools ()Ljava/util/Map;
public final fun listRoots (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static synthetic fun listRoots$default (Lio/modelcontextprotocol/kotlin/sdk/server/Server;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,13 @@ public class ServerOptions(public val capabilities: ServerCapabilities, enforceS
*
* @param serverInfo Information about this server implementation (name, version).
* @param options Configuration options for the server.
* @param instructions Optional instructions from the server to the client about how to use this server.
*/
public open class Server(private val serverInfo: Implementation, options: ServerOptions) : Protocol(options) {
public open class Server(
protected val serverInfo: Implementation,
options: ServerOptions,
protected val instructions: String? = null,
) : Protocol(options) {
@Suppress("ktlint:standard:backing-property-naming")
private var _onInitialized: (() -> Unit) = {}

Expand Down Expand Up @@ -613,6 +618,7 @@ public open class Server(private val serverInfo: Implementation, options: Server
protocolVersion = protocolVersion,
capabilities = capabilities,
serverInfo = serverInfo,
instructions = instructions,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,4 +471,58 @@ class ServerTest {
}
assertEquals("Server does not support resources capability.", exception.message)
}

@Test
fun `Server constructor should accept instructions parameter`() = runTest {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the test!
One question: Is it possible to verify in the test that the server has received the instructions string?
Also, the test could be parametrized

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The serverInstructions were added to the client as a property that can be verified in these tests.

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

launch { client.connect(clientTransport) }
launch { server.connect(serverTransport) }

// The test passes if the server can be created with instructions without throwing
assertTrue(true, "Server should accept instructions parameter")
}

@Test
fun `Server constructor should work with null instructions`() = runTest {
val serverInfo = Implementation(name = "test server", version = "1.0")
val serverOptions = ServerOptions(capabilities = ServerCapabilities())

val server = Server(serverInfo, serverOptions, null)

// Test that server works with null instructions
val (clientTransport, serverTransport) = InMemoryTransport.createLinkedPair()
val client = Client(clientInfo = Implementation(name = "test client", version = "1.0"))

launch { client.connect(clientTransport) }
launch { server.connect(serverTransport) }

assertTrue(true, "Server should accept null instructions parameter")
}

@Test
fun `Server constructor should work with default 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"))

launch { client.connect(clientTransport) }
launch { server.connect(serverTransport) }

assertTrue(true, "Server should work with default instructions parameter")
}
}