Mark DSL functions for types as public (#472)

<!-- Provide a brief summary of your changes -->

## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
The dsl builders in [this
commit](74c93745cd (diff-598edaa01d))
were mistakenly marked internal and are therefore unusable. I have
simply marked them as public.

## How Has This Been Tested?
<!-- Have you tested this in a real application? Which scenarios were
tested? -->

## Breaking Changes
No

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [x] 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
- [x] 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
-->

---------

Co-authored-by: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com>
This commit is contained in:
Nathaniel Lombardi 2026-01-09 18:12:29 -05:00 • committed by GitHub
commit 6fee82ff1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 870 additions and 16 deletions

View file

@ -46,6 +46,12 @@ MCP Kotlin SDK — Kotlin Multiplatform implementation of the Model Context Prot
- All tests for each module are located in `src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/`
- Platform-specific tests go in `src/jvmTest/`, `src/jsTest/`, etc.
- Use Kotest assertions (`shouldBe`, `shouldContain`, etc.) for readable test failures.
- For nullable objects with nested properties, prefer `shouldNotBeNull { ... }` blocks:
```kotlin
content.annotations shouldNotBeNull {
priority shouldBe 1.0
}
```
- Use `shouldMatchJson` from Kotest for JSON validation.
- Mock Ktor HTTP clients using `MockEngine` from `io.ktor:ktor-client-mock`.
- Always add tests for new features or bug fixes, even if not explicitly requested.

View file

@ -1195,6 +1195,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Compl
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/Completion_dslKt {
public static final fun buildCompleteRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest;
}
public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ContentBlock : io/modelcontextprotocol/kotlin/sdk/types/WithMeta {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ContentBlock$Companion;
public abstract fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ContentTypes;
@ -1584,6 +1588,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Compani
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/Elicitation_dslKt {
public static final fun buildElicitRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource : io/modelcontextprotocol/kotlin/sdk/types/ContentBlock {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/EmbeddedResource$Companion;
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)V
@ -2016,6 +2024,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeResult$Com
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/Initialize_dslKt {
public static final fun buildInitializeRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/InitializedNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/InitializedNotification$Companion;
public fun <init> ()V
@ -2660,6 +2672,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotifi
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/Logging_dslKt {
public static final fun buildSetLevelRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest;
}
public abstract interface annotation class io/modelcontextprotocol/kotlin/sdk/types/McpDsl : java/lang/annotation/Annotation {
}
@ -2957,6 +2973,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequestBuilder :
public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/Request;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest_dslKt {
public static final fun buildPingRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/Progress {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Progress$Companion;
public fun <init> (DLjava/lang/Double;Ljava/lang/String;)V
@ -3211,6 +3231,11 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PromptReference$Comp
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/Prompts_dslKt {
public static final fun buildGetPromptRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest;
public static final fun buildListPromptsRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/RPCError {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/RPCError$Companion;
public fun <init> (ILjava/lang/String;Lkotlinx/serialization/json/JsonElement;)V
@ -3809,6 +3834,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotif
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/Resources_dslKt {
public static final fun buildListResourceTemplatesRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest;
public static final fun buildListResourcesRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest;
public static final fun buildReadResourceRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest;
public static final fun buildSubscribeRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest;
public static final fun buildUnsubscribeRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/Role : java/lang/Enum {
public static final field Assistant Lio/modelcontextprotocol/kotlin/sdk/types/Role;
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Role$Companion;
@ -3886,6 +3919,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNoti
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/Roots_dslKt {
public static final fun buildListRootsRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessage {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessage$Companion;
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/MediaContent;)V
@ -3927,6 +3964,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Sampling_dslKt {
public static final fun assistantAudio (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V
public static final fun assistantImage (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V
public static final fun assistantText (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V
public static final fun buildCreateMessageRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest;
public static final fun user (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function0;)V
public static final fun userAudio (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V
public static final fun userImage (Lio/modelcontextprotocol/kotlin/sdk/types/SamplingMessageBuilder;Lkotlin/jvm/functions/Function1;)V
@ -4490,6 +4528,11 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolsKt {
public static synthetic fun success$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/Tools_dslKt {
public static final fun buildCallToolRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest;
public static final fun buildListToolsRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest;
}
public final class io/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents : io/modelcontextprotocol/kotlin/sdk/types/ResourceContents {
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents$Companion;
public fun <init> (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V

View file

@ -42,7 +42,7 @@ import kotlin.contracts.contract
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildCompleteRequest(block: CompleteRequestBuilder.() -> Unit): CompleteRequest {
public inline fun buildCompleteRequest(block: CompleteRequestBuilder.() -> Unit): CompleteRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return CompleteRequestBuilder().apply(block).build()
}

View file

@ -59,7 +59,7 @@ import kotlin.contracts.contract
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildElicitRequest(block: ElicitRequestBuilder.() -> Unit): ElicitRequest {
public inline fun buildElicitRequest(block: ElicitRequestBuilder.() -> Unit): ElicitRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ElicitRequestBuilder().apply(block).build()
}

View file

@ -54,7 +54,7 @@ import kotlin.contracts.contract
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildInitializeRequest(block: InitializeRequestBuilder.() -> Unit): InitializeRequest {
public inline fun buildInitializeRequest(block: InitializeRequestBuilder.() -> Unit): InitializeRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return InitializeRequestBuilder().apply(block).build()
}

View file

@ -36,7 +36,7 @@ import kotlin.contracts.contract
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildSetLevelRequest(block: SetLevelRequestBuilder.() -> Unit): SetLevelRequest {
public inline fun buildSetLevelRequest(block: SetLevelRequestBuilder.() -> Unit): SetLevelRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return SetLevelRequestBuilder().apply(block).build()
}

View file

@ -32,7 +32,7 @@ import kotlin.contracts.contract
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildPingRequest(block: PingRequestBuilder.() -> Unit): PingRequest {
public inline fun buildPingRequest(block: PingRequestBuilder.() -> Unit): PingRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return PingRequestBuilder().apply(block).build()
}

View file

@ -40,7 +40,7 @@ import kotlin.contracts.contract
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildGetPromptRequest(block: GetPromptRequestBuilder.() -> Unit): GetPromptRequest {
public inline fun buildGetPromptRequest(block: GetPromptRequestBuilder.() -> Unit): GetPromptRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return GetPromptRequestBuilder().apply(block).build()
}
@ -116,7 +116,7 @@ public class GetPromptRequestBuilder @PublishedApi internal constructor() : Requ
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildListPromptsRequest(block: ListPromptsRequestBuilder.() -> Unit): ListPromptsRequest {
public inline fun buildListPromptsRequest(block: ListPromptsRequestBuilder.() -> Unit): ListPromptsRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ListPromptsRequestBuilder().apply(block).build()
}

View file

@ -31,7 +31,7 @@ import kotlin.contracts.contract
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildListResourcesRequest(block: ListResourcesRequestBuilder.() -> Unit): ListResourcesRequest {
public inline fun buildListResourcesRequest(block: ListResourcesRequestBuilder.() -> Unit): ListResourcesRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ListResourcesRequestBuilder().apply(block).build()
}
@ -82,7 +82,7 @@ public class ListResourcesRequestBuilder @PublishedApi internal constructor() :
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildReadResourceRequest(block: ReadResourceRequestBuilder.() -> Unit): ReadResourceRequest {
public inline fun buildReadResourceRequest(block: ReadResourceRequestBuilder.() -> Unit): ReadResourceRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ReadResourceRequestBuilder().apply(block).build()
}
@ -144,7 +144,7 @@ public class ReadResourceRequestBuilder @PublishedApi internal constructor() : R
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildSubscribeRequest(block: SubscribeRequestBuilder.() -> Unit): SubscribeRequest {
public inline fun buildSubscribeRequest(block: SubscribeRequestBuilder.() -> Unit): SubscribeRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return SubscribeRequestBuilder().apply(block).build()
}
@ -206,7 +206,7 @@ public class SubscribeRequestBuilder @PublishedApi internal constructor() : Requ
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildUnsubscribeRequest(block: UnsubscribeRequestBuilder.() -> Unit): UnsubscribeRequest {
public inline fun buildUnsubscribeRequest(block: UnsubscribeRequestBuilder.() -> Unit): UnsubscribeRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return UnsubscribeRequestBuilder().apply(block).build()
}
@ -271,7 +271,7 @@ public class UnsubscribeRequestBuilder @PublishedApi internal constructor() : Re
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildListResourceTemplatesRequest(
public inline fun buildListResourceTemplatesRequest(
block: ListResourceTemplatesRequestBuilder.() -> Unit,
): ListResourceTemplatesRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }

View file

@ -32,7 +32,7 @@ import kotlin.contracts.contract
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildListRootsRequest(block: ListRootsRequestBuilder.() -> Unit): ListRootsRequest {
public inline fun buildListRootsRequest(block: ListRootsRequestBuilder.() -> Unit): ListRootsRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ListRootsRequestBuilder().apply(block).build()
}

View file

@ -59,7 +59,7 @@ import kotlin.contracts.contract
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildCreateMessageRequest(block: CreateMessageRequestBuilder.() -> Unit): CreateMessageRequest {
public inline fun buildCreateMessageRequest(block: CreateMessageRequestBuilder.() -> Unit): CreateMessageRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return CreateMessageRequestBuilder().apply(block).build()
}

View file

@ -43,7 +43,7 @@ import kotlin.contracts.contract
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildCallToolRequest(block: CallToolRequestBuilder.() -> Unit): CallToolRequest {
public inline fun buildCallToolRequest(block: CallToolRequestBuilder.() -> Unit): CallToolRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return CallToolRequestBuilder().apply(block).build()
}
@ -143,7 +143,7 @@ public class CallToolRequestBuilder @PublishedApi internal constructor() : Reque
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildListToolsRequest(block: ListToolsRequestBuilder.() -> Unit): ListToolsRequest {
public inline fun buildListToolsRequest(block: ListToolsRequestBuilder.() -> Unit): ListToolsRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ListToolsRequestBuilder().apply(block).build()
}

View file

@ -0,0 +1,65 @@
package io.modelcontextprotocol.kotlin.sdk.types.dsl
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.types.PromptReference
import io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplateReference
import io.modelcontextprotocol.kotlin.sdk.types.buildCompleteRequest
import kotlin.test.Test
@OptIn(ExperimentalMcpApi::class)
class CompletionDslTest {
@Test
fun `buildCompleteRequest should build with prompt reference and context`() {
val request = buildCompleteRequest {
argument("query", "user input")
ref(PromptReference("searchPrompt"))
context {
put("userId", "123")
}
}
request.params.argument.name shouldBe "query"
request.params.argument.value shouldBe "user input"
(request.params.ref as PromptReference).name shouldBe "searchPrompt"
request.params.context shouldNotBeNull {
arguments?.get("userId") shouldBe "123"
}
}
@Test
fun `buildCompleteRequest should build with resource template reference and map context`() {
val request = buildCompleteRequest {
argument("path", "/users/123")
ref(ResourceTemplateReference("file:///{path}"))
context(mapOf("role" to "admin"))
}
request.params.argument.name shouldBe "path"
request.params.argument.value shouldBe "/users/123"
(request.params.ref as ResourceTemplateReference).uri shouldBe "file:///{path}"
request.params.context shouldNotBeNull {
arguments?.get("role") shouldBe "admin"
}
}
@Test
fun `buildCompleteRequest should throw if argument is missing`() {
shouldThrow<IllegalArgumentException> {
buildCompleteRequest {
ref(PromptReference("name"))
}
}
}
@Test
fun `buildCompleteRequest should throw if ref is missing`() {
shouldThrow<IllegalArgumentException> {
buildCompleteRequest {
argument("name", "value")
}
}
}
}

View file

@ -0,0 +1,84 @@
package io.modelcontextprotocol.kotlin.sdk.types.dsl
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.shouldBe
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams
import io.modelcontextprotocol.kotlin.sdk.types.buildElicitRequest
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlin.test.Test
@OptIn(ExperimentalMcpApi::class)
class ElicitationDslTest {
@Test
fun `buildElicitRequest should build with all fields`() {
val request = buildElicitRequest {
message = "Provide info"
requestedSchema {
properties {
put("email", buildJsonObject { put("type", "string") })
}
required = listOf("email")
}
}
request.params.message shouldBe "Provide info"
request.params.requestedSchema.properties["email"] shouldBe buildJsonObject { put("type", "string") }
request.params.requestedSchema.required shouldBe listOf("email")
}
@Test
fun `buildElicitRequest should support direct requestedSchema`() {
val schema = ElicitRequestParams.RequestedSchema(
properties = buildJsonObject { put("name", buildJsonObject { put("type", "string") }) },
required = listOf("name"),
)
val request = buildElicitRequest {
message = "Test"
requestedSchema(schema)
}
request.params.requestedSchema shouldBe schema
}
@Test
fun `ElicitRequestedSchemaBuilder should support direct properties assignment`() {
val props = buildJsonObject { put("key", "value") }
val request = buildElicitRequest {
message = "Test"
requestedSchema {
properties(props)
}
}
request.params.requestedSchema.properties shouldBe props
}
@Test
fun `buildElicitRequest should throw if message is missing`() {
shouldThrow<IllegalArgumentException> {
buildElicitRequest {
requestedSchema { properties { put("a", 1) } }
}
}
}
@Test
fun `buildElicitRequest should throw if requestedSchema is missing`() {
shouldThrow<IllegalArgumentException> {
buildElicitRequest {
message = "Test"
}
}
}
@Test
fun `ElicitRequestedSchemaBuilder should throw if properties are missing`() {
shouldThrow<IllegalArgumentException> {
buildElicitRequest {
message = "Test"
requestedSchema { }
}
}
}
}

View file

@ -0,0 +1,138 @@
package io.modelcontextprotocol.kotlin.sdk.types.dsl
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlin.test.Test
@OptIn(ExperimentalMcpApi::class)
class InitializeDslTest {
@Test
fun `buildInitializeRequest should create request with all fields`() {
val request = buildInitializeRequest {
protocolVersion = "2024-11-05"
capabilities {
sampling {
put("maxTokens", 100)
}
roots(listChanged = true)
elicitation {
put("mode", "interactive")
}
experimental {
put("custom", true)
}
}
info(
name = "TestClient",
version = "1.0.0",
title = "Test Client",
websiteUrl = "https://example.com",
)
}
request.params.protocolVersion shouldBe "2024-11-05"
request.params.capabilities.shouldNotBeNull {
sampling?.get("maxTokens")?.jsonPrimitive?.int shouldBe 100
roots?.listChanged shouldBe true
elicitation?.get("mode")?.jsonPrimitive?.content shouldBe "interactive"
experimental?.get("custom")?.jsonPrimitive?.content shouldBe "true"
}
request.params.clientInfo.shouldNotBeNull {
name shouldBe "TestClient"
version shouldBe "1.0.0"
title shouldBe "Test Client"
websiteUrl shouldBe "https://example.com"
}
}
@Test
fun `buildInitializeRequest should support direct capabilities and info`() {
val capabilities = ClientCapabilities(roots = ClientCapabilities.Roots(listChanged = false))
val info = Implementation(name = "Direct", version = "0.1")
val request = buildInitializeRequest {
protocolVersion = "1.0"
capabilities(capabilities)
info(info)
}
request.params.capabilities shouldBe capabilities
request.params.clientInfo shouldBe info
}
@Test
fun `capabilities DSL should support direct JsonObject values`() {
val samplingObj = buildJsonObject { put("key", "value") }
val elicitationObj = buildJsonObject { put("key", "value") }
val experimentalObj = buildJsonObject { put("key", "value") }
val request = buildInitializeRequest {
protocolVersion = "1.0"
capabilities {
sampling(samplingObj)
elicitation(elicitationObj)
experimental(experimentalObj)
}
info("Test", "1.0")
}
request.params.capabilities.shouldNotBeNull {
sampling shouldBe samplingObj
elicitation shouldBe elicitationObj
experimental shouldBe experimentalObj
}
}
@Test
fun `ClientCapabilitiesBuilder roots should support default arguments`() {
val request = buildInitializeRequest {
protocolVersion = "1.0"
capabilities {
roots()
}
info("Test", "1.0")
}
request.params.capabilities.roots.shouldNotBeNull {
listChanged shouldBe null
}
}
@Test
fun `buildInitializeRequest should throw if protocolVersion is missing`() {
shouldThrow<IllegalArgumentException> {
buildInitializeRequest {
capabilities { }
info("Test", "1.0")
}
}
}
@Test
fun `buildInitializeRequest should throw if capabilities are missing`() {
shouldThrow<IllegalArgumentException> {
buildInitializeRequest {
protocolVersion = "1.0"
info("Test", "1.0")
}
}
}
@Test
fun `buildInitializeRequest should throw if info is missing`() {
shouldThrow<IllegalArgumentException> {
buildInitializeRequest {
protocolVersion = "1.0"
capabilities { }
}
}
}
}

View file

@ -0,0 +1,27 @@
package io.modelcontextprotocol.kotlin.sdk.types.dsl
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.shouldBe
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel
import io.modelcontextprotocol.kotlin.sdk.types.buildSetLevelRequest
import kotlin.test.Test
@OptIn(ExperimentalMcpApi::class)
class LoggingDslTest {
@Test
fun `buildSetLevelRequest should create request with given level`() {
val request = buildSetLevelRequest {
loggingLevel = LoggingLevel.Info
}
request.params.level shouldBe LoggingLevel.Info
}
@Test
fun `buildSetLevelRequest should throw if loggingLevel is missing`() {
shouldThrow<IllegalArgumentException> {
buildSetLevelRequest { }
}
}
}

View file

@ -0,0 +1,65 @@
package io.modelcontextprotocol.kotlin.sdk.types.dsl
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.types.buildPingRequest
import kotlinx.serialization.json.add
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlin.test.Test
@OptIn(ExperimentalMcpApi::class)
class PingRequestDslTest {
@Test
fun `buildPingRequest should create request with meta containing all field types`() {
val request = buildPingRequest {
meta {
progressToken("token-123")
put("string", "value")
put("number", 42)
put("boolean", true)
put("null", null)
putJsonObject("obj") {
put("key", "val")
}
putJsonArray("arr") {
add("item")
}
}
}
request.params.shouldNotBeNull {
meta.shouldNotBeNull {
json["progressToken"]?.jsonPrimitive?.content shouldBe "token-123"
json["string"]?.jsonPrimitive?.content shouldBe "value"
json["number"]?.jsonPrimitive?.int shouldBe 42
json["boolean"]?.jsonPrimitive?.boolean shouldBe true
json["null"] shouldBe kotlinx.serialization.json.JsonNull
json["obj"]?.shouldNotBeNull()
json["arr"]?.shouldNotBeNull()
}
}
}
@Test
fun `RequestMeta DSL should support numeric progress tokens`() {
val requestInt = buildPingRequest {
meta { progressToken(123) }
}
requestInt.params?.meta?.json?.get("progressToken")?.jsonPrimitive?.int shouldBe 123
val requestLong = buildPingRequest {
meta { progressToken(456L) }
}
requestLong.params?.meta?.json?.get("progressToken")?.jsonPrimitive?.int shouldBe 456
}
@Test
fun `buildPingRequest should create request without params if meta is empty`() {
val request = buildPingRequest { }
request.params shouldBe null
}
}

View file

@ -0,0 +1,47 @@
package io.modelcontextprotocol.kotlin.sdk.types.dsl
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.types.buildGetPromptRequest
import io.modelcontextprotocol.kotlin.sdk.types.buildListPromptsRequest
import kotlin.test.Test
@OptIn(ExperimentalMcpApi::class)
class PromptsDslTest {
@Test
fun `buildGetPromptRequest should create request with name and arguments`() {
val request = buildGetPromptRequest {
name = "test-prompt"
arguments = mapOf("key" to "value")
}
request.params.name shouldBe "test-prompt"
request.params.arguments shouldBe mapOf("key" to "value")
}
@Test
fun `buildListPromptsRequest should create request with cursor`() {
val request = buildListPromptsRequest {
cursor = "next-page"
}
request.params shouldNotBeNull {
cursor shouldBe "next-page"
}
}
@Test
fun `buildGetPromptRequest should throw if name is missing`() {
shouldThrow<IllegalArgumentException> {
buildGetPromptRequest { }
}
}
@Test
fun `buildListPromptsRequest should create request without params if empty`() {
val request = buildListPromptsRequest { }
request.params shouldBe null
}
}

View file

@ -0,0 +1,92 @@
package io.modelcontextprotocol.kotlin.sdk.types.dsl
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.types.buildListResourceTemplatesRequest
import io.modelcontextprotocol.kotlin.sdk.types.buildListResourcesRequest
import io.modelcontextprotocol.kotlin.sdk.types.buildReadResourceRequest
import io.modelcontextprotocol.kotlin.sdk.types.buildSubscribeRequest
import io.modelcontextprotocol.kotlin.sdk.types.buildUnsubscribeRequest
import kotlin.test.Test
@OptIn(ExperimentalMcpApi::class)
class ResourcesDslTest {
@Test
fun `buildListResourcesRequest should create request with cursor`() {
val request = buildListResourcesRequest {
cursor = "next"
}
request.params shouldNotBeNull {
cursor shouldBe "next"
}
}
@Test
fun `buildReadResourceRequest should create request with uri`() {
val request = buildReadResourceRequest {
uri = "test://resource"
}
request.params.uri shouldBe "test://resource"
}
@Test
fun `buildSubscribeRequest should create request with uri`() {
val request = buildSubscribeRequest {
uri = "test://resource"
}
request.params.uri shouldBe "test://resource"
}
@Test
fun `buildUnsubscribeRequest should create request with uri`() {
val request = buildUnsubscribeRequest {
uri = "test://resource"
}
request.params.uri shouldBe "test://resource"
}
@Test
fun `buildListResourceTemplatesRequest should create request with cursor`() {
val request = buildListResourceTemplatesRequest {
cursor = "template-cursor"
}
request.params shouldNotBeNull {
cursor shouldBe "template-cursor"
}
}
@Test
fun `buildReadResourceRequest should throw if uri is missing`() {
shouldThrow<IllegalArgumentException> {
buildReadResourceRequest { }
}
}
@Test
fun `buildSubscribeRequest should throw if uri is missing`() {
shouldThrow<IllegalArgumentException> {
buildSubscribeRequest { }
}
}
@Test
fun `buildUnsubscribeRequest should throw if uri is missing`() {
shouldThrow<IllegalArgumentException> {
buildUnsubscribeRequest { }
}
}
@Test
fun `buildListResourcesRequest should create request without params if empty`() {
val request = buildListResourcesRequest { }
request.params shouldBe null
}
@Test
fun `buildListResourceTemplatesRequest should create request without params if empty`() {
val request = buildListResourceTemplatesRequest { }
request.params shouldBe null
}
}

View file

@ -0,0 +1,19 @@
package io.modelcontextprotocol.kotlin.sdk.types.dsl
import io.kotest.matchers.nulls.shouldNotBeNull
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.types.buildListRootsRequest
import kotlin.test.Test
@OptIn(ExperimentalMcpApi::class)
class RootsDslTest {
@Test
fun `buildListRootsRequest should create request with meta`() {
val request = buildListRootsRequest {
meta {
put("test", "value")
}
}
request.params.shouldNotBeNull()
}
}

Some files were not shown because too many files have changed in this diff Show more