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
21 changed files with 870 additions and 16 deletions

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

View file

@ -0,0 +1,201 @@
package io.modelcontextprotocol.kotlin.sdk.types.dsl
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.shouldBe
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
import io.modelcontextprotocol.kotlin.sdk.types.Annotations
import io.modelcontextprotocol.kotlin.sdk.types.AudioContent
import io.modelcontextprotocol.kotlin.sdk.types.ImageContent
import io.modelcontextprotocol.kotlin.sdk.types.IncludeContext
import io.modelcontextprotocol.kotlin.sdk.types.ModelPreferences
import io.modelcontextprotocol.kotlin.sdk.types.Role
import io.modelcontextprotocol.kotlin.sdk.types.SamplingMessage
import io.modelcontextprotocol.kotlin.sdk.types.TextContent
import io.modelcontextprotocol.kotlin.sdk.types.assistant
import io.modelcontextprotocol.kotlin.sdk.types.assistantAudio
import io.modelcontextprotocol.kotlin.sdk.types.assistantImage
import io.modelcontextprotocol.kotlin.sdk.types.buildCreateMessageRequest
import io.modelcontextprotocol.kotlin.sdk.types.user
import io.modelcontextprotocol.kotlin.sdk.types.userAudio
import io.modelcontextprotocol.kotlin.sdk.types.userImage
import io.modelcontextprotocol.kotlin.sdk.types.userText
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import kotlin.test.Test
@OptIn(ExperimentalMcpApi::class)
class SamplingDslTest {
@Test
fun `buildCreateMessageRequest should build with all fields`() {
val request = buildCreateMessageRequest {
maxTokens = 1000
systemPrompt = "System prompt"
temperature = 0.5
stopSequences = listOf("STOP")
messages {
user { "Hello" }
assistant { "Hi" }
userText {
text = "Text with annotations"
annotations(
audience = listOf(Role.User),
priority = 1.0,
lastModified = "2025-01-10T00:00:00Z",
)
meta {
put("key", "value")
}
}
assistantImage {
data = "base64image"
mimeType = "image/png"
annotations(Annotations(priority = 0.5))
}
assistantAudio {
data = "base64audio"
mimeType = "audio/wav"
}
}
preferences(hints = listOf("hint"), intelligence = 0.9)
metadata {
put("metaKey", "metaValue")
}
}
request.params.maxTokens shouldBe 1000
request.params.systemPrompt shouldBe "System prompt"
request.params.temperature shouldBe 0.5
request.params.stopSequences shouldBe listOf("STOP")
request.params.messages shouldHaveSize 5
(request.params.messages[2].content as TextContent).shouldNotBeNull {
text shouldBe "Text with annotations"
annotations shouldNotBeNull {
audience shouldBe listOf(Role.User)
priority shouldBe 1.0
lastModified shouldBe "2025-01-10T00:00:00Z"
}
meta?.get("key")?.jsonPrimitive?.content shouldBe "value"
}
(request.params.messages[3].content as ImageContent).shouldNotBeNull {
data shouldBe "base64image"
mimeType shouldBe "image/png"
annotations?.priority shouldBe 0.5
}
(request.params.messages[4].content as AudioContent).shouldNotBeNull {
data shouldBe "base64audio"
mimeType shouldBe "audio/wav"
}
request.params.modelPreferences shouldNotBeNull {
intelligencePriority shouldBe 0.9
}
request.params.metadata shouldNotBeNull {
get("metaKey")?.jsonPrimitive?.content shouldBe "metaValue"
}
}
@Test
fun `buildCreateMessageRequest should support direct assignments`() {
val messages = listOf(SamplingMessage(Role.User, TextContent("Hello")))
val preferences = ModelPreferences(costPriority = 0.1)
val request = buildCreateMessageRequest {
maxTokens = 100
messages(messages)
preferences(preferences)
context = IncludeContext.AllServers
}
request.params.messages shouldBe messages
request.params.modelPreferences shouldBe preferences
request.params.includeContext shouldBe IncludeContext.AllServers
}
@Test
fun `SamplingMessageBuilder should support direct content assignment`() {
val content = TextContent("Direct")
val request = buildCreateMessageRequest {
maxTokens = 100
messages {
user(content)
assistant(content)
}
}
request.params.messages[0].content shouldBe content
request.params.messages[1].content shouldBe content
}
@Test
fun `buildCreateMessageRequest should throw if maxTokens is missing`() {
shouldThrow<IllegalArgumentException> {
buildCreateMessageRequest {
messages { user { "Hi" } }
}
}
}
@Test
fun `buildCreateMessageRequest should throw if messages are missing`() {
shouldThrow<IllegalArgumentException> {
buildCreateMessageRequest {
maxTokens = 100
}
}
}
@Test
fun `TextContentBuilder should throw if text is missing`() {
shouldThrow<IllegalArgumentException> {
buildCreateMessageRequest {
maxTokens = 100
messages {
userText { }
}
}
}
}
@Test
fun `ImageContentBuilder should throw if data or mimeType is missing`() {
shouldThrow<IllegalArgumentException> {
buildCreateMessageRequest {
maxTokens = 100
messages {
userImage { data = "abc" }
}
}
}
shouldThrow<IllegalArgumentException> {
buildCreateMessageRequest {
maxTokens = 100
messages {
userImage { mimeType = "image/png" }
}
}
}
}
@Test
fun `AudioContentBuilder should throw if data or mimeType is missing`() {
shouldThrow<IllegalArgumentException> {
buildCreateMessageRequest {
maxTokens = 100
messages {
userAudio { data = "abc" }
}
}
}
shouldThrow<IllegalArgumentException> {
buildCreateMessageRequest {
maxTokens = 100
messages {
userAudio { mimeType = "audio/wav" }
}
}
}
}
}

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