diff --git a/AGENTS.md b/AGENTS.md index 5b49a6db0..29aacef12 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 8fff88b7f..67acbdd6b 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -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 (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 ()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 (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 (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 (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 (Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)V diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt index dbefd314f..bbcfe6c5c 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/completion.dsl.kt @@ -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() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt index a4823cfd6..e398d98e9 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/elicitation.dsl.kt @@ -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() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt index 345b036f9..2cb58b77b 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/initialize.dsl.kt @@ -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() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.dsl.kt index 883d2cc2d..b65846c76 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/logging.dsl.kt @@ -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() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/pingRequest.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/pingRequest.dsl.kt index 2f5ac33e2..1898636c8 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/pingRequest.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/pingRequest.dsl.kt @@ -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() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt index 02eae95fc..b5aaaa2ff 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt @@ -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() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.dsl.kt index db40cd7ea..ff2ddf589 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/resources.dsl.kt @@ -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) } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.dsl.kt index a73f218c1..c1af20138 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/roots.dsl.kt @@ -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() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt index 8e87304ad..1bc00e281 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/sampling.dsl.kt @@ -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() } diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.dsl.kt index 9a5adf100..a3a9be325 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.dsl.kt @@ -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() } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionDslTest.kt new file mode 100644 index 000000000..cbe40c9a7 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionDslTest.kt @@ -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 { + buildCompleteRequest { + ref(PromptReference("name")) + } + } + } + + @Test + fun `buildCompleteRequest should throw if ref is missing`() { + shouldThrow { + buildCompleteRequest { + argument("name", "value") + } + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ElicitationDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ElicitationDslTest.kt new file mode 100644 index 000000000..e49e4d2ba --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ElicitationDslTest.kt @@ -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 { + buildElicitRequest { + requestedSchema { properties { put("a", 1) } } + } + } + } + + @Test + fun `buildElicitRequest should throw if requestedSchema is missing`() { + shouldThrow { + buildElicitRequest { + message = "Test" + } + } + } + + @Test + fun `ElicitRequestedSchemaBuilder should throw if properties are missing`() { + shouldThrow { + buildElicitRequest { + message = "Test" + requestedSchema { } + } + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt new file mode 100644 index 000000000..cb3511571 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeDslTest.kt @@ -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 { + buildInitializeRequest { + capabilities { } + info("Test", "1.0") + } + } + } + + @Test + fun `buildInitializeRequest should throw if capabilities are missing`() { + shouldThrow { + buildInitializeRequest { + protocolVersion = "1.0" + info("Test", "1.0") + } + } + } + + @Test + fun `buildInitializeRequest should throw if info is missing`() { + shouldThrow { + buildInitializeRequest { + protocolVersion = "1.0" + capabilities { } + } + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/LoggingDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/LoggingDslTest.kt new file mode 100644 index 000000000..5b09b4256 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/LoggingDslTest.kt @@ -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 { + buildSetLevelRequest { } + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PingRequestDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PingRequestDslTest.kt new file mode 100644 index 000000000..5a219ba3d --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PingRequestDslTest.kt @@ -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 + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsDslTest.kt new file mode 100644 index 000000000..937933ed1 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsDslTest.kt @@ -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 { + buildGetPromptRequest { } + } + } + + @Test + fun `buildListPromptsRequest should create request without params if empty`() { + val request = buildListPromptsRequest { } + request.params shouldBe null + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesDslTest.kt new file mode 100644 index 000000000..13df6703f --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesDslTest.kt @@ -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 { + buildReadResourceRequest { } + } + } + + @Test + fun `buildSubscribeRequest should throw if uri is missing`() { + shouldThrow { + buildSubscribeRequest { } + } + } + + @Test + fun `buildUnsubscribeRequest should throw if uri is missing`() { + shouldThrow { + 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 + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RootsDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RootsDslTest.kt new file mode 100644 index 000000000..037649ff8 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RootsDslTest.kt @@ -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() + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt new file mode 100644 index 000000000..5b60cfbe9 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/SamplingDslTest.kt @@ -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 { + buildCreateMessageRequest { + messages { user { "Hi" } } + } + } + } + + @Test + fun `buildCreateMessageRequest should throw if messages are missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + } + } + } + + @Test + fun `TextContentBuilder should throw if text is missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + userText { } + } + } + } + } + + @Test + fun `ImageContentBuilder should throw if data or mimeType is missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + userImage { data = "abc" } + } + } + } + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + userImage { mimeType = "image/png" } + } + } + } + } + + @Test + fun `AudioContentBuilder should throw if data or mimeType is missing`() { + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + userAudio { data = "abc" } + } + } + } + shouldThrow { + buildCreateMessageRequest { + maxTokens = 100 + messages { + userAudio { mimeType = "audio/wav" } + } + } + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsDslTest.kt new file mode 100644 index 000000000..957f0d2fb --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsDslTest.kt @@ -0,0 +1,67 @@ +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.buildCallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildListToolsRequest +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 ToolsDslTest { + @Test + fun `buildCallToolRequest should build with name and arguments`() { + val request = buildCallToolRequest { + name = "test-tool" + arguments { + put("key", "value") + put("count", 1) + } + } + + request.params.name shouldBe "test-tool" + request.params.arguments shouldNotBeNull { + get("key")?.jsonPrimitive?.content shouldBe "value" + get("count")?.jsonPrimitive?.int shouldBe 1 + } + } + + @Test + fun `buildListToolsRequest should build with cursor`() { + val request = buildListToolsRequest { + cursor = "tool-cursor" + } + + request.params shouldNotBeNull { + cursor shouldBe "tool-cursor" + } + } + + @Test + fun `buildCallToolRequest should support direct arguments assignment`() { + val args = buildJsonObject { put("key", "value") } + val request = buildCallToolRequest { + name = "test-tool" + arguments(args) + } + request.params.arguments shouldBe args + } + + @Test + fun `buildCallToolRequest should throw if name is missing`() { + shouldThrow { + buildCallToolRequest { } + } + } + + @Test + fun `buildListToolsRequest should build without params if empty`() { + val request = buildListToolsRequest { } + request.params shouldBe null + } +}