Adds 'instructions' string to the Server and propagates to the InitializeResult (#290)
<!-- Provide a brief summary of your changes -->
## Motivation and Context
The
[schema](cca15204f6/schema/2025-03-26/schema.ts (L192))
for the InitializeResult contains an optional 'instructions' string
## Additional context
The instructions string is included in the schema for for version
"2025-03-26"
The instructions in the Server class are given as a provider function
with the result of the provider propagated to the underlying
ServerSession. As the ServerSession change is meant to allow for a
'Singleton scoped' server class, this provides the ability to
dynamically modify the instructions at the server level but have them be
immutable per session.
This also refactors the `serverInfo` and `instructions` properties to be
protected. This `Server` class is open and meant to be overridden, there
is no harm in having these immutable properties available to subclasses.
This adds the serverInstructions to the client as returned from the
InitializeResult. This is used to verify the response in the Server
tests.
This commit is contained in:
parent
73c0af891b
commit
2f1b659efd
10 changed files with 184 additions and 15 deletions
16
README.md
16
README.md
|
|
@ -112,7 +112,9 @@ val server = Server(
|
|||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
){
|
||||
"This server provides example resources and demonstrates MCP capabilities."
|
||||
}
|
||||
|
||||
// Add a resource
|
||||
server.addResource(
|
||||
|
|
@ -156,8 +158,10 @@ fun Application.module() {
|
|||
prompts = ServerCapabilities.Prompts(listChanged = null),
|
||||
resources = ServerCapabilities.Resources(subscribe = null, listChanged = null)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
) {
|
||||
"This SSE server provides prompts and resources via Server-Sent Events."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -184,8 +188,10 @@ fun Application.module() {
|
|||
prompts = ServerCapabilities.Prompts(listChanged = null),
|
||||
resources = ServerCapabilities.Resources(subscribe = null, listChanged = null)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
) {
|
||||
"Connect via SSE to interact with this MCP server."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public class io/modelcontextprotocol/kotlin/sdk/client/Client : io/modelcontextp
|
|||
public final fun getPrompt (Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public static synthetic fun getPrompt$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/GetPromptRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
|
||||
public final fun getServerCapabilities ()Lio/modelcontextprotocol/kotlin/sdk/ServerCapabilities;
|
||||
public final fun getServerInstructions ()Ljava/lang/String;
|
||||
public final fun getServerVersion ()Lio/modelcontextprotocol/kotlin/sdk/Implementation;
|
||||
public final fun listPrompts (Lio/modelcontextprotocol/kotlin/sdk/ListPromptsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public static synthetic fun listPrompts$default (Lio/modelcontextprotocol/kotlin/sdk/client/Client;Lio/modelcontextprotocol/kotlin/sdk/ListPromptsRequest;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
|
||||
|
|
|
|||
|
|
@ -91,6 +91,14 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
public var serverCapabilities: ServerCapabilities? = null
|
||||
private set
|
||||
|
||||
/**
|
||||
* Optional human-readable instructions or description from the server.
|
||||
*
|
||||
* @return Instructions provided by the server, or `null` if none were given or initialization is not yet complete.
|
||||
*/
|
||||
public var serverInstructions: String? = null
|
||||
private set
|
||||
|
||||
/**
|
||||
* Retrieves the server's reported version information after initialization.
|
||||
*
|
||||
|
|
@ -154,6 +162,7 @@ public open class Client(private val clientInfo: Implementation, options: Client
|
|||
|
||||
serverCapabilities = result.capabilities
|
||||
serverVersion = result.serverInfo
|
||||
serverInstructions = result.instructions
|
||||
|
||||
notification(InitializedNotification())
|
||||
} catch (error: Throwable) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,9 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool {
|
|||
}
|
||||
|
||||
public class io/modelcontextprotocol/kotlin/sdk/server/Server {
|
||||
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 fun <init> (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;)V
|
||||
public synthetic fun <init> (Lio/modelcontextprotocol/kotlin/sdk/Implementation;Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;Lkotlin/jvm/functions/Function0;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
|
||||
|
|
@ -59,8 +61,11 @@ public class io/modelcontextprotocol/kotlin/sdk/server/Server {
|
|||
public final fun addTools (Ljava/util/List;)V
|
||||
public final fun close (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public final fun connect (Lio/modelcontextprotocol/kotlin/sdk/shared/Transport;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
protected final fun getInstructionsProvider ()Lkotlin/jvm/functions/Function0;
|
||||
protected final fun getOptions ()Lio/modelcontextprotocol/kotlin/sdk/server/ServerOptions;
|
||||
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 onClose (Lkotlin/jvm/functions/Function0;)V
|
||||
public final fun onConnect (Lkotlin/jvm/functions/Function0;)V
|
||||
|
|
@ -80,7 +85,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/ServerOptions : io/
|
|||
}
|
||||
|
||||
public class io/modelcontextprotocol/kotlin/sdk/server/ServerSession : 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
|
||||
protected fun assertCapabilityForMethod (Lio/modelcontextprotocol/kotlin/sdk/Method;)V
|
||||
protected fun assertNotificationCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V
|
||||
public fun assertRequestHandlerCapability (Lio/modelcontextprotocol/kotlin/sdk/Method;)V
|
||||
|
|
@ -90,6 +95,8 @@ public class io/modelcontextprotocol/kotlin/sdk/server/ServerSession : io/modelc
|
|||
public static synthetic fun createMessage$default (Lio/modelcontextprotocol/kotlin/sdk/server/ServerSession;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;
|
||||
protected final fun getServerInfo ()Lio/modelcontextprotocol/kotlin/sdk/Implementation;
|
||||
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/ServerSession;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/shared/RequestOptions;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
|
||||
public fun onClose ()V
|
||||
|
|
|
|||
|
|
@ -53,8 +53,28 @@ 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 instructionsProvider Optional provider for instructions from the server to the client about how to use
|
||||
* this server. The provider is called each time a new session is started to support dynamic instructions.
|
||||
*/
|
||||
public open class Server(private val serverInfo: Implementation, private val options: ServerOptions) {
|
||||
|
||||
public open class Server(
|
||||
protected val serverInfo: Implementation,
|
||||
protected val options: ServerOptions,
|
||||
protected val instructionsProvider: (() -> String)? = null,
|
||||
) {
|
||||
/**
|
||||
* Alternative constructor that provides the instructions directly as a string.
|
||||
*
|
||||
* @param serverInfo Information about this server implementation (name, version).
|
||||
* @param options Configuration options for the server.
|
||||
* @param instructions Instructions from the server to the client about how to use this server.
|
||||
*/
|
||||
public constructor(
|
||||
serverInfo: Implementation,
|
||||
options: ServerOptions,
|
||||
instructions: String,
|
||||
) : this(serverInfo, options, { instructions })
|
||||
|
||||
private val sessions = atomic(persistentListOf<ServerSession>())
|
||||
|
||||
@Suppress("ktlint:standard:backing-property-naming")
|
||||
|
|
@ -90,7 +110,7 @@ public open class Server(private val serverInfo: Implementation, private val opt
|
|||
* @return The initialized and connected server session.
|
||||
*/
|
||||
public suspend fun connect(transport: Transport): ServerSession {
|
||||
val session = ServerSession(serverInfo, options)
|
||||
val session = ServerSession(serverInfo, options, instructionsProvider?.invoke())
|
||||
|
||||
// Internal handlers for tools
|
||||
if (options.capabilities.tools != null) {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,11 @@ import kotlinx.serialization.json.JsonObject
|
|||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
public open class ServerSession(private val serverInfo: Implementation, options: ServerOptions) : Protocol(options) {
|
||||
public open class ServerSession(
|
||||
protected val serverInfo: Implementation,
|
||||
options: ServerOptions,
|
||||
protected val instructions: String?,
|
||||
) : Protocol(options) {
|
||||
@Suppress("ktlint:standard:backing-property-naming")
|
||||
private var _onInitialized: (() -> Unit) = {}
|
||||
|
||||
|
|
@ -366,6 +370,7 @@ public open class ServerSession(private val serverInfo: Implementation, options:
|
|||
protocolVersion = protocolVersion,
|
||||
capabilities = serverCapabilities,
|
||||
serverInfo = serverInfo,
|
||||
instructions = instructions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ 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
|
||||
|
|
@ -471,4 +472,59 @@ class ServerTest {
|
|||
}
|
||||
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.connect(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.connect(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.connect(serverTransport)
|
||||
client.connect(clientTransport)
|
||||
|
||||
assertNull(client.serverInstructions)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue