From 179a530b377a6dbe86234b8027013751f2915f86 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:19:38 +0200 Subject: [PATCH 1/7] feat(dsl): add result DSL builders for testing and serialization validation - Introduced `ToolsResultDslTest` and `PromptsResultDslTest` to test result builders for tool and prompt operations. - Added `buildCompleteResult` and `buildInitializeResult` for server-side result creation. - Updated API surface to include result DSL functionality. --- kotlin-sdk-core/api/kotlin-sdk-core.api | 209 ++++++ .../kotlin/sdk/types/completion.dsl.kt | 76 +++ .../kotlin/sdk/types/initialize.dsl.kt | 82 +++ .../kotlin/sdk/types/prompts.dsl.kt | 339 ++++++++++ .../kotlin/sdk/types/resources.dsl.kt | 223 +++++++ .../kotlin/sdk/types/result.dsl.kt | 83 +++ .../kotlin/sdk/types/tools.dsl.kt | 615 ++++++++++++++++++ .../sdk/types/dsl/CompletionResultDslTest.kt | 150 +++++ .../sdk/types/dsl/InitializeResultDslTest.kt | 201 ++++++ .../sdk/types/dsl/PromptsResultDslTest.kt | 194 ++++++ .../sdk/types/dsl/ResourcesResultDslTest.kt | 142 ++++ .../sdk/types/dsl/ToolsResultDslTest.kt | 292 +++++++++ 12 files changed, 2606 insertions(+) create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionResultDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeResultDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsResultDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesResultDslTest.kt create mode 100644 kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsResultDslTest.kt diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 0607df500..f3b09a0e7 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -848,6 +848,20 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Compa public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/CallToolResultBuilder : io/modelcontextprotocol/kotlin/sdk/types/ResultBuilder { + public fun ()V + public final fun audioContent (Ljava/lang/String;Ljava/lang/String;)V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult; + public final fun content (Lio/modelcontextprotocol/kotlin/sdk/types/ContentBlock;)V + public final fun imageContent (Ljava/lang/String;Ljava/lang/String;)V + public final fun isError ()Ljava/lang/Boolean; + public final fun setError (Ljava/lang/Boolean;)V + public final fun structuredContent (Lkotlin/jvm/functions/Function1;)V + public final fun structuredContent (Lkotlinx/serialization/json/JsonObject;)V + public final fun textContent (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification, io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotification$Companion; public fun (Lio/modelcontextprotocol/kotlin/sdk/types/CancelledNotificationParams;)V @@ -1228,8 +1242,21 @@ 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/CompleteResultBuilder : io/modelcontextprotocol/kotlin/sdk/types/ResultBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult; + public final fun getHasMore ()Ljava/lang/Boolean; + public final fun getTotal ()Ljava/lang/Integer; + public final fun setHasMore (Ljava/lang/Boolean;)V + public final fun setTotal (Ljava/lang/Integer;)V + public final fun values (Ljava/util/List;)V + public final fun values ([Ljava/lang/String;)V +} + 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 static final fun buildCompleteResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult; } public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ContentBlock : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { @@ -1798,6 +1825,16 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptResult$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/GetPromptResultBuilder : io/modelcontextprotocol/kotlin/sdk/types/ResultBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult; + public final fun getDescription ()Ljava/lang/String; + public final fun message (Lio/modelcontextprotocol/kotlin/sdk/types/PromptMessage;)V + public final fun message (Lio/modelcontextprotocol/kotlin/sdk/types/Role;Lio/modelcontextprotocol/kotlin/sdk/types/ContentBlock;)V + public final fun setDescription (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/Icon { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Companion; public fun (Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lio/modelcontextprotocol/kotlin/sdk/types/Icon$Theme;)V @@ -2057,8 +2094,23 @@ 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/InitializeResultBuilder : io/modelcontextprotocol/kotlin/sdk/types/ResultBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult; + public final fun capabilities (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;)V + public final fun getInstructions ()Ljava/lang/String; + public final fun getProtocolVersion ()Ljava/lang/String; + public final fun info (Lio/modelcontextprotocol/kotlin/sdk/types/Implementation;)V + public final fun info (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V + public static synthetic fun info$default (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResultBuilder;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)V + public final fun setInstructions (Ljava/lang/String;)V + public final fun setProtocolVersion (Ljava/lang/String;)V +} + 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 static final fun buildInitializeResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult; } public final class io/modelcontextprotocol/kotlin/sdk/types/InitializedNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification { @@ -2330,6 +2382,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult$Co public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ListPromptsResultBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResultBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult; + public final fun prompt (Lio/modelcontextprotocol/kotlin/sdk/types/Prompt;)V + public final fun prompt (Lkotlin/jvm/functions/Function1;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest$Companion; public fun ()V @@ -2404,6 +2464,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplate public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResultBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResultBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult; + public final fun template (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate;)V + public final fun template (Lkotlin/jvm/functions/Function1;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/PaginatedRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest$Companion; public fun ()V @@ -2478,6 +2546,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult$ public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ListResourcesResultBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResultBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult; + public final fun resource (Lio/modelcontextprotocol/kotlin/sdk/types/Resource;)V + public final fun resource (Lkotlin/jvm/functions/Function1;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest : io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest$Companion; public fun ()V @@ -2622,6 +2698,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsResult$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ListToolsResultBuilder : io/modelcontextprotocol/kotlin/sdk/types/PaginatedResultBuilder { + public fun ()V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsResult; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult; + public final fun tool (Lio/modelcontextprotocol/kotlin/sdk/types/Tool;)V + public final fun tool (Lkotlin/jvm/functions/Function1;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/LoggingLevel : java/lang/Enum { public static final field Alert Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel; public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/LoggingLevel$Companion; @@ -2968,6 +3052,12 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PaginatedResult$Defa public static fun get_meta (Lio/modelcontextprotocol/kotlin/sdk/types/PaginatedResult;)Lkotlinx/serialization/json/JsonObject; } +public abstract class io/modelcontextprotocol/kotlin/sdk/types/PaginatedResultBuilder : io/modelcontextprotocol/kotlin/sdk/types/ResultBuilder { + public fun ()V + public final fun getNextCursor ()Ljava/lang/String; + public final fun setNextCursor (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequest : io/modelcontextprotocol/kotlin/sdk/types/ClientRequest, io/modelcontextprotocol/kotlin/sdk/types/ServerRequest { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest$Companion; public fun ()V @@ -3173,6 +3263,23 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PromptArgument$Compa public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/PromptBuilder { + public fun ()V + public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/Prompt; + public final fun getArguments ()Ljava/util/List; + public final fun getDescription ()Ljava/lang/String; + public final fun getIcons ()Ljava/util/List; + public final fun getName ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public final fun meta (Lkotlin/jvm/functions/Function1;)V + public final fun meta (Lkotlinx/serialization/json/JsonObject;)V + public final fun setArguments (Ljava/util/List;)V + public final fun setDescription (Ljava/lang/String;)V + public final fun setIcons (Ljava/util/List;)V + public final fun setName (Ljava/lang/String;)V + public final fun setTitle (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/PromptListChangedNotification : io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/PromptListChangedNotification$Companion; public fun ()V @@ -3266,7 +3373,9 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PromptReference$Comp 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 buildGetPromptResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult; public static final fun buildListPromptsRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; + public static final fun buildListPromptsResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult; } public final class io/modelcontextprotocol/kotlin/sdk/types/RPCError { @@ -3414,6 +3523,17 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult$C public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ReadResourceResultBuilder : io/modelcontextprotocol/kotlin/sdk/types/ResultBuilder { + public fun ()V + public final fun blobContent (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public static synthetic fun blobContent$default (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResultBuilder;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V + public fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult; + public synthetic fun build$kotlin_sdk_core ()Lio/modelcontextprotocol/kotlin/sdk/types/RequestResult; + public final fun content (Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents;)V + public final fun textContent (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public static synthetic fun textContent$default (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResultBuilder;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/Reference { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/Reference$Companion; public abstract fun getType ()Lio/modelcontextprotocol/kotlin/sdk/types/ReferenceType; @@ -3633,6 +3753,29 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Resource$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceBuilder { + public fun ()V + public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/Resource; + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun getDescription ()Ljava/lang/String; + public final fun getIcons ()Ljava/util/List; + public final fun getMimeType ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getSize ()Ljava/lang/Long; + public final fun getTitle ()Ljava/lang/String; + public final fun getUri ()Ljava/lang/String; + public final fun meta (Lkotlin/jvm/functions/Function1;)V + public final fun meta (Lkotlinx/serialization/json/JsonObject;)V + public final fun setAnnotations (Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;)V + public final fun setDescription (Ljava/lang/String;)V + public final fun setIcons (Ljava/util/List;)V + public final fun setMimeType (Ljava/lang/String;)V + public final fun setName (Ljava/lang/String;)V + public final fun setSize (Ljava/lang/Long;)V + public final fun setTitle (Ljava/lang/String;)V + public final fun setUri (Ljava/lang/String;)V +} + public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ResourceContents : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceContents$Companion; public abstract fun getMimeType ()Ljava/lang/String; @@ -3779,6 +3922,27 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate$Com public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceTemplateBuilder { + public fun ()V + public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplate; + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/Annotations; + public final fun getDescription ()Ljava/lang/String; + public final fun getIcons ()Ljava/util/List; + public final fun getMimeType ()Ljava/lang/String; + public final fun getName ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public final fun getUriTemplate ()Ljava/lang/String; + public final fun meta (Lkotlin/jvm/functions/Function1;)V + public final fun meta (Lkotlinx/serialization/json/JsonObject;)V + public final fun setAnnotations (Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;)V + public final fun setDescription (Ljava/lang/String;)V + public final fun setIcons (Ljava/util/List;)V + public final fun setMimeType (Ljava/lang/String;)V + public final fun setName (Ljava/lang/String;)V + public final fun setTitle (Ljava/lang/String;)V + public final fun setUriTemplate (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceTemplateReference : io/modelcontextprotocol/kotlin/sdk/types/Reference { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ResourceTemplateReference$Companion; public fun (Ljava/lang/String;)V @@ -3869,12 +4033,23 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotif 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 buildListResourceTemplatesResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult; public static final fun buildListResourcesRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; + public static final fun buildListResourcesResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult; public static final fun buildReadResourceRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; + public static final fun buildReadResourceResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult; 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 abstract class io/modelcontextprotocol/kotlin/sdk/types/ResultBuilder { + public fun ()V + protected final fun getMeta ()Lkotlinx/serialization/json/JsonObject; + public final fun meta (Lkotlin/jvm/functions/Function1;)V + public final fun meta (Lkotlinx/serialization/json/JsonObject;)V + protected final fun setMeta (Lkotlinx/serialization/json/JsonObject;)V +} + 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; @@ -4491,6 +4666,27 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations$Comp public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ToolBuilder { + public fun ()V + public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/Tool; + public final fun getAnnotations ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations; + public final fun getDescription ()Ljava/lang/String; + public final fun getIcons ()Ljava/util/List; + public final fun getName ()Ljava/lang/String; + public final fun getTitle ()Ljava/lang/String; + public final fun inputSchema (Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;)V + public final fun inputSchema (Lkotlin/jvm/functions/Function1;)V + public final fun meta (Lkotlin/jvm/functions/Function1;)V + public final fun meta (Lkotlinx/serialization/json/JsonObject;)V + public final fun outputSchema (Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema;)V + public final fun outputSchema (Lkotlin/jvm/functions/Function1;)V + public final fun setAnnotations (Lio/modelcontextprotocol/kotlin/sdk/types/ToolAnnotations;)V + public final fun setDescription (Ljava/lang/String;)V + public final fun setIcons (Ljava/util/List;)V + public final fun setName (Ljava/lang/String;)V + public final fun setTitle (Ljava/lang/String;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification : io/modelcontextprotocol/kotlin/sdk/types/ServerNotification { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ToolListChangedNotification$Companion; public fun ()V @@ -4556,6 +4752,17 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolSchema$Companion public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/ToolSchemaBuilder { + public fun ()V + public final fun build ()Lio/modelcontextprotocol/kotlin/sdk/types/ToolSchema; + public final fun getDefs ()Lkotlinx/serialization/json/JsonObject; + public final fun getProperties ()Lkotlinx/serialization/json/JsonObject; + public final fun getRequired ()Ljava/util/List; + public final fun setDefs (Lkotlinx/serialization/json/JsonObject;)V + public final fun setProperties (Lkotlinx/serialization/json/JsonObject;)V + public final fun setRequired (Ljava/util/List;)V +} + public final class io/modelcontextprotocol/kotlin/sdk/types/ToolsKt { public static final fun error (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; public static synthetic fun error$default (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion;Ljava/lang/String;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; @@ -4565,7 +4772,9 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolsKt { 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 buildCallToolResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; public static final fun buildListToolsRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; + public static final fun buildListToolsResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsResult; } public final class io/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents : io/modelcontextprotocol/kotlin/sdk/types/ResourceContents { 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 29d3c9983..e18681ad9 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 @@ -168,3 +168,79 @@ public class CompleteRequestBuilder @PublishedApi internal constructor() : Reque return CompleteRequest(params) } } + +// ============================================================================ +// Result Builders (Server-side) +// ============================================================================ + +/** + * Creates a [CompleteResult] using a type-safe DSL builder. + * + * Example: + * ```kotlin + * val result = buildCompleteResult { + * values("user1", "user2", "user3") + * total = 3 + * } + * ``` + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +public inline fun buildCompleteResult(block: CompleteResultBuilder.() -> Unit): CompleteResult { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return CompleteResultBuilder().apply(block).build() +} + +private const val MAX_ITEMS = 100 + +/** + * DSL builder for constructing [CompleteResult] instances. + */ +@McpDsl +public class CompleteResultBuilder @PublishedApi internal constructor() : ResultBuilder() { + private var completionValues: List? = null + private var completionTotal: Int? = null + private var completionHasMore: Boolean? = null + + public fun values(vararg values: String) { + this.completionValues = values.toList() + } + + public fun values(values: List) { + this.completionValues = values + } + + public var total: Int? = null + set(value) { + field = value + completionTotal = value + } + + public var hasMore: Boolean? = null + set(value) { + field = value + completionHasMore = value + } + + @PublishedApi + override fun build(): CompleteResult { + val values = requireNotNull(completionValues) { + "Missing required field 'values'. Use values() to set completion values." + } + + require(values.size <= MAX_ITEMS) { + "Completion values must not exceed $MAX_ITEMS items, got ${values.size}" + } + + val completion = CompleteResult.Completion( + values = values, + total = completionTotal, + hasMore = completionHasMore, + ) + + return CompleteResult( + completion = completion, + meta = meta, + ) + } +} 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 2cb58b77b..9ba66b8c9 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 @@ -204,3 +204,85 @@ public class InitializeRequestBuilder @PublishedApi internal constructor() : Req return InitializeRequest(params = params) } } + +// ============================================================================ +// Result Builders (Server-side) +// ============================================================================ + +/** + * Creates an [InitializeResult] using a type-safe DSL builder. + * + * Example: + * ```kotlin + * val result = buildInitializeResult { + * protocolVersion = "2024-11-05" + * capabilities { + * prompts(listChanged = true) + * resources(subscribe = true, listChanged = true) + * tools(listChanged = true) + * } + * info("MyServer", "1.0.0") + * instructions = "Use this server for..." + * } + * ``` + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +public inline fun buildInitializeResult(block: InitializeResultBuilder.() -> Unit): InitializeResult { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return InitializeResultBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [InitializeResult] instances. + */ +@McpDsl +public class InitializeResultBuilder @PublishedApi internal constructor() : ResultBuilder() { + public var protocolVersion: String = LATEST_PROTOCOL_VERSION + private var capabilitiesValue: ServerCapabilities? = null + private var serverInfoValue: Implementation? = null + public var instructions: String? = null + + public fun capabilities(capabilities: ServerCapabilities) { + this.capabilitiesValue = capabilities + } + + public fun info( + name: String, + version: String, + title: String? = null, + websiteUrl: String? = null, + icons: List? = null, + ) { + serverInfoValue = Implementation( + name = name, + version = version, + title = title, + websiteUrl = websiteUrl, + icons = icons, + ) + } + + public fun info(info: Implementation) { + this.serverInfoValue = info + } + + @PublishedApi + override fun build(): InitializeResult { + val capabilities = requireNotNull(capabilitiesValue) { + "Missing required field 'capabilities'. " + + "Use capabilities(ServerCapabilities(...)) to set server capabilities." + } + val serverInfo = requireNotNull(serverInfoValue) { + "Missing required field 'info'. Use info(\"serverName\", \"1.0.0\")" + } + + return InitializeResult( + protocolVersion = protocolVersion, + capabilities = capabilities, + serverInfo = serverInfo, + instructions = instructions, + meta = meta, + ) + } +} 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 b5aaaa2ff..ff4af8251 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 @@ -1,6 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.buildJsonObject import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -143,3 +146,339 @@ public class ListPromptsRequestBuilder @PublishedApi internal constructor() : Pa return ListPromptsRequest(params) } } + +// ============================================================================ +// Result Builders (Server-side) +// ============================================================================ + +/** + * Creates a [GetPromptResult] using a type-safe DSL builder. + * + * ## Required + * - [messages][GetPromptResultBuilder.messages] - List of prompt messages (at least one) + * + * ## Optional + * - [description][GetPromptResultBuilder.description] - Description of the prompt + * - [meta][GetPromptResultBuilder.meta] - Metadata for the response + * + * Example: + * ```kotlin + * val result = buildGetPromptResult { + * description = "A greeting prompt" + * message { + * role = Role.User + * content = TextContent("Hello, how can I help you today?") + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the get prompt result + * @return A configured [GetPromptResult] instance + * @see GetPromptResultBuilder + * @see GetPromptResult + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +public inline fun buildGetPromptResult(block: GetPromptResultBuilder.() -> Unit): GetPromptResult { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return GetPromptResultBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [GetPromptResult] instances. + * + * This builder creates a response containing prompt messages. + * + * ## Required + * - At least one message (via [message] method) + * + * ## Optional + * - [description] - Description of the prompt + * - [meta] - Metadata for the response + * + * @see buildGetPromptResult + * @see GetPromptResult + */ +@McpDsl +public class GetPromptResultBuilder @PublishedApi internal constructor() : ResultBuilder() { + private val messagesList: MutableList = mutableListOf() + + /** + * Optional description for the prompt. + * + * Example: `description = "A personalized greeting prompt for users"` + */ + public var description: String? = null + + /** + * Adds a pre-built message to the result. + * + * Example: + * ```kotlin + * message(PromptMessage( + * role = Role.User, + * content = TextContent("What is your name?") + * )) + * ``` + * + * @param message The prompt message to add + */ + public fun message(message: PromptMessage) { + messagesList.add(message) + } + + /** + * Adds a message using role and content block. + * + * Example: + * ```kotlin + * message(Role.User, TextContent("What is your name?")) + * ``` + * + * @param role The role of the message sender + * @param content The content block + */ + public fun message(role: Role, content: ContentBlock) { + messagesList.add(PromptMessage(role = role, content = content)) + } + + @PublishedApi + override fun build(): GetPromptResult { + require(messagesList.isNotEmpty()) { + "At least one message is required. Use message() to add messages." + } + + return GetPromptResult( + messages = messagesList.toList(), + description = description, + meta = meta, + ) + } +} + +/** + * Creates a [ListPromptsResult] using a type-safe DSL builder. + * + * ## Required + * - [prompts][ListPromptsResultBuilder.prompts] - List of available prompts (at least one) + * + * ## Optional + * - [nextCursor][ListPromptsResultBuilder.nextCursor] - Pagination cursor for next page + * - [meta][ListPromptsResultBuilder.meta] - Metadata for the response + * + * Example: + * ```kotlin + * val result = buildListPromptsResult { + * prompt { + * name = "greeting" + * description = "A friendly greeting prompt" + * } + * prompt { + * name = "farewell" + * description = "A polite farewell prompt" + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the list prompts result + * @return A configured [ListPromptsResult] instance + * @see ListPromptsResultBuilder + * @see ListPromptsResult + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +public inline fun buildListPromptsResult(block: ListPromptsResultBuilder.() -> Unit): ListPromptsResult { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListPromptsResultBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [ListPromptsResult] instances. + * + * This builder creates a response containing a list of prompts available on the server, + * with optional pagination support. + * + * ## Required + * - At least one prompt (via [prompt] method) + * + * ## Optional + * - [nextCursor] - Pagination cursor (inherited from [PaginatedResultBuilder]) + * - [meta] - Metadata for the response (inherited from [ResultBuilder]) + * + * @see buildListPromptsResult + * @see ListPromptsResult + * @see PaginatedResultBuilder + */ +@McpDsl +public class ListPromptsResultBuilder @PublishedApi internal constructor() : PaginatedResultBuilder() { + private val promptsList: MutableList = mutableListOf() + + /** + * Adds a pre-built prompt to the result. + * + * Example: + * ```kotlin + * val myPrompt = Prompt( + * name = "greeting", + * description = "A friendly greeting" + * ) + * prompt(myPrompt) + * ``` + * + * @param prompt The prompt to add + */ + public fun prompt(prompt: Prompt) { + promptsList.add(prompt) + } + + /** + * Adds a prompt using a DSL builder. + * + * Example: + * ```kotlin + * prompt { + * name = "greeting" + * description = "A friendly greeting" + * arguments = listOf( + * PromptArgument(name = "userName", required = true) + * ) + * } + * ``` + * + * @param block Lambda for building the Prompt + */ + public fun prompt(block: PromptBuilder.() -> Unit) { + promptsList.add(PromptBuilder().apply(block).build()) + } + + @PublishedApi + override fun build(): ListPromptsResult { + require(promptsList.isNotEmpty()) { + "At least one prompt is required. Use prompt() or prompt { } to add prompts." + } + + return ListPromptsResult( + prompts = promptsList.toList(), + nextCursor = nextCursor, + meta = meta, + ) + } +} + +/** + * DSL builder for constructing [Prompt] instances. + * + * Used within [ListPromptsResultBuilder] to define individual prompts. + * + * ## Required + * - [name] - The programmatic identifier for the prompt + * + * ## Optional + * - [description] - Human-readable description + * - [arguments] - List of arguments the prompt accepts + * - [title] - Display name for the prompt + * - [icons] - Icon representations for UIs + * - [meta] - Metadata for the prompt + * + * @see Prompt + * @see ListPromptsResultBuilder + */ +@McpDsl +public class PromptBuilder @PublishedApi internal constructor() { + /** + * The programmatic identifier for this prompt. Required. + * + * Example: `name = "greeting"` + */ + public var name: String? = null + + /** + * Human-readable description of what the prompt does. + * + * Example: `description = "A friendly greeting prompt"` + */ + public var description: String? = null + + /** + * Optional list of arguments the prompt accepts. + * + * Example: + * ```kotlin + * arguments = listOf( + * PromptArgument(name = "userName", required = true, description = "The user's name"), + * PromptArgument(name = "language", required = false, description = "Preferred language") + * ) + * ``` + */ + public var arguments: List? = null + + /** + * Optional display name for the prompt. + * + * Example: `title = "Friendly Greeting"` + */ + public var title: String? = null + + /** + * Optional list of icons for the prompt. + * + * Example: + * ```kotlin + * icons = listOf( + * Icon(url = "https://example.com/icon.png", size = "32x32", mimeType = "image/png") + * ) + * ``` + */ + public var icons: List? = null + + private var metaValue: JsonObject? = null + + /** + * Sets metadata directly from a JsonObject. + * + * Example: + * ```kotlin + * meta(buildJsonObject { + * put("category", "greetings") + * }) + * ``` + * + * @param meta The metadata as a JsonObject + */ + public fun meta(meta: JsonObject) { + this.metaValue = meta + } + + /** + * Sets metadata using a DSL builder. + * + * Example: + * ```kotlin + * meta { + * put("category", "greetings") + * put("version", "1.0") + * } + * ``` + * + * @param block Lambda for building the metadata JsonObject + */ + public fun meta(block: JsonObjectBuilder.() -> Unit) { + meta(buildJsonObject(block)) + } + + @PublishedApi + internal fun build(): Prompt { + val name = requireNotNull(name) { + "Missing required field 'name'. Example: name = \"promptName\"" + } + + return Prompt( + name = name, + description = description, + arguments = arguments, + title = title, + icons = icons, + meta = metaValue, + ) + } +} 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 ff2ddf589..d9fd9a2df 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 @@ -1,6 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.buildJsonObject import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -300,3 +303,223 @@ public class ListResourceTemplatesRequestBuilder @PublishedApi internal construc return ListResourceTemplatesRequest(params) } } + +// ============================================================================ +// Result Builders (Server-side) +// ============================================================================ + +/** + * Creates a [ListResourcesResult] using a type-safe DSL builder. + * + * Example: + * ```kotlin + * val result = buildListResourcesResult { + * resource { + * uri = "file:///path/to/file.txt" + * name = "file.txt" + * description = "A text file" + * mimeType = "text/plain" + * } + * } + * ``` + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +public inline fun buildListResourcesResult(block: ListResourcesResultBuilder.() -> Unit): ListResourcesResult { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListResourcesResultBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [ListResourcesResult] instances. + */ +@McpDsl +public class ListResourcesResultBuilder @PublishedApi internal constructor() : PaginatedResultBuilder() { + private val resourcesList: MutableList = mutableListOf() + + public fun resource(resource: Resource) { + resourcesList.add(resource) + } + + public fun resource(block: ResourceBuilder.() -> Unit) { + resourcesList.add(ResourceBuilder().apply(block).build()) + } + + @PublishedApi + override fun build(): ListResourcesResult { + require(resourcesList.isNotEmpty()) { + "At least one resource is required. Use resource() or resource { } to add resources." + } + + return ListResourcesResult( + resources = resourcesList.toList(), + nextCursor = nextCursor, + meta = meta, + ) + } +} + +/** + * DSL builder for constructing [Resource] instances. + */ +@McpDsl +public class ResourceBuilder @PublishedApi internal constructor() { + public var uri: String? = null + public var name: String? = null + public var description: String? = null + public var mimeType: String? = null + public var size: Long? = null + public var title: String? = null + public var annotations: Annotations? = null + public var icons: List? = null + private var metaValue: JsonObject? = null + + public fun meta(meta: JsonObject) { + this.metaValue = meta + } + + public fun meta(block: JsonObjectBuilder.() -> Unit) { + meta(buildJsonObject(block)) + } + + @PublishedApi + internal fun build(): Resource { + val uri = requireNotNull(uri) { "Missing required field 'uri'" } + val name = requireNotNull(name) { "Missing required field 'name'" } + + return Resource( + uri = uri, + name = name, + description = description, + mimeType = mimeType, + size = size, + title = title, + annotations = annotations, + icons = icons, + meta = metaValue, + ) + } +} + +/** + * Creates a [ReadResourceResult] using a type-safe DSL builder. + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +public inline fun buildReadResourceResult(block: ReadResourceResultBuilder.() -> Unit): ReadResourceResult { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ReadResourceResultBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [ReadResourceResult] instances. + */ +@McpDsl +public class ReadResourceResultBuilder @PublishedApi internal constructor() : ResultBuilder() { + private val contentsList: MutableList = mutableListOf() + + public fun textContent(uri: String, text: String, mimeType: String? = null) { + contentsList.add(TextResourceContents(text = text, uri = uri, mimeType = mimeType)) + } + + public fun blobContent(uri: String, blob: String, mimeType: String? = null) { + contentsList.add(BlobResourceContents(blob = blob, uri = uri, mimeType = mimeType)) + } + + public fun content(content: ResourceContents) { + contentsList.add(content) + } + + @PublishedApi + override fun build(): ReadResourceResult { + require(contentsList.isNotEmpty()) { + "At least one content block is required. Use textContent(), blobContent(), or content()." + } + + return ReadResourceResult( + contents = contentsList.toList(), + meta = meta, + ) + } +} + +/** + * Creates a [ListResourceTemplatesResult] using a type-safe DSL builder. + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +public inline fun buildListResourceTemplatesResult( + block: ListResourceTemplatesResultBuilder.() -> Unit, +): ListResourceTemplatesResult { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListResourceTemplatesResultBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [ListResourceTemplatesResult] instances. + */ +@McpDsl +public class ListResourceTemplatesResultBuilder @PublishedApi internal constructor() : PaginatedResultBuilder() { + private val templatesList: MutableList = mutableListOf() + + public fun template(template: ResourceTemplate) { + templatesList.add(template) + } + + public fun template(block: ResourceTemplateBuilder.() -> Unit) { + templatesList.add(ResourceTemplateBuilder().apply(block).build()) + } + + @PublishedApi + override fun build(): ListResourceTemplatesResult { + require(templatesList.isNotEmpty()) { + "At least one template is required. Use template() or template { } to add templates." + } + + return ListResourceTemplatesResult( + resourceTemplates = templatesList.toList(), + nextCursor = nextCursor, + meta = meta, + ) + } +} + +/** + * DSL builder for constructing [ResourceTemplate] instances. + */ +@McpDsl +public class ResourceTemplateBuilder @PublishedApi internal constructor() { + public var uriTemplate: String? = null + public var name: String? = null + public var description: String? = null + public var mimeType: String? = null + public var title: String? = null + public var annotations: Annotations? = null + public var icons: List? = null + private var metaValue: JsonObject? = null + + public fun meta(meta: JsonObject) { + this.metaValue = meta + } + + public fun meta(block: JsonObjectBuilder.() -> Unit) { + meta(buildJsonObject(block)) + } + + @PublishedApi + internal fun build(): ResourceTemplate { + val uriTemplate = requireNotNull(uriTemplate) { "Missing required field 'uriTemplate'" } + val name = requireNotNull(name) { "Missing required field 'name'" } + + return ResourceTemplate( + uriTemplate = uriTemplate, + name = name, + description = description, + mimeType = mimeType, + title = title, + annotations = annotations, + icons = icons, + meta = metaValue, + ) + } +} diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt new file mode 100644 index 000000000..55d3aae43 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt @@ -0,0 +1,83 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonObjectBuilder +import kotlinx.serialization.json.buildJsonObject + +/** + * Base DSL builder for constructing MCP result instances. + * + * This abstract class provides common functionality for all result builders, + * including optional metadata support. + * + * All concrete result builder classes extend this base to inherit [meta] functionality. + * + * @see RequestResult + * @see ServerResult + */ +@McpDsl +public abstract class ResultBuilder { + protected var meta: JsonObject? = null + + /** + * Sets result metadata directly from a JsonObject. + * + * Example: + * ```kotlin + * meta(buildJsonObject { + * put("source", "server") + * put("timestamp", System.currentTimeMillis()) + * }) + * ``` + */ + public fun meta(meta: JsonObject) { + this.meta = meta + } + + /** + * Sets result metadata using a DSL builder. + * + * Metadata can include custom fields for tracking responses and providing + * additional context to clients. + * + * Example: + * ```kotlin + * listToolsResult { + * tools { /* ... */ } + * meta { + * put("serverVersion", "1.0.0") + * put("cached", true) + * put("generatedAt", System.currentTimeMillis()) + * } + * } + * ``` + * + * @param builderAction Lambda for building result metadata + */ + public fun meta(builderAction: JsonObjectBuilder.() -> Unit) { + meta = buildJsonObject(builderAction) + } + + internal abstract fun build(): RequestResult +} + +/** + * Base DSL builder for constructing paginated result instances. + * + * Extends [ResultBuilder] to add pagination support via [nextCursor]. + * + * @see ResultBuilder + * @see PaginatedResult + */ +@McpDsl +public abstract class PaginatedResultBuilder : ResultBuilder() { + /** + * Optional pagination cursor for fetching the next page of results. + * + * If present, indicates there may be more results available. + * Clients can pass this cursor in a subsequent paginated request. + * + * Example: `nextCursor = "eyJwYWdlIjogMn0="` + */ + public var nextCursor: String? = null +} 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 a3a9be325..e04787594 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 @@ -170,3 +170,618 @@ public class ListToolsRequestBuilder @PublishedApi internal constructor() : Pagi return ListToolsRequest(params) } } + +// ============================================================================ +// Result Builders (Server-side) +// ============================================================================ + +/** + * Creates a [CallToolResult] using a type-safe DSL builder. + * + * ## Required + * - [content][CallToolResultBuilder.content] - List of content blocks (at least one) + * + * ## Optional + * - [isError][CallToolResultBuilder.isError] - Whether the tool call resulted in an error + * - [structuredContent][CallToolResultBuilder.structuredContent] - Machine-readable structured output + * - [meta][CallToolResultBuilder.meta] - Metadata for the response + * + * Example success response: + * ```kotlin + * val result = buildCallToolResult { + * textContent("Operation completed successfully") + * } + * ``` + * + * Example error response: + * ```kotlin + * val result = buildCallToolResult { + * textContent("Failed to connect to database") + * isError = true + * } + * ``` + * + * Example with structured content: + * ```kotlin + * val result = buildCallToolResult { + * textContent("Query returned 3 results") + * structuredContent { + * put("count", 3) + * putJsonArray("results") { + * add("result1") + * add("result2") + * add("result3") + * } + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the call tool result + * @return A configured [CallToolResult] instance + * @see CallToolResultBuilder + * @see CallToolResult + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +public inline fun buildCallToolResult(block: CallToolResultBuilder.() -> Unit): CallToolResult { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return CallToolResultBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [CallToolResult] instances. + * + * This builder creates the server's response to a tool call, including the result content + * and optional error status. + * + * ## Required + * - At least one content block (via [textContent], [imageContent], [audioContent], or [content]) + * + * ## Optional + * - [isError] - Whether the tool call resulted in an error + * - [structuredContent] - Machine-readable structured output + * - [meta] - Metadata for the response + * + * @see buildCallToolResult + * @see CallToolResult + */ +@McpDsl +public class CallToolResultBuilder @PublishedApi internal constructor() : ResultBuilder() { + private val contentList: MutableList = mutableListOf() + + /** + * Whether the tool call resulted in an error. + * + * When true, the content should describe the error that occurred. + * If not set, this is assumed to be false (success). + * + * Example: `isError = true` + */ + public var isError: Boolean? = null + + private var structuredContentValue: JsonObject? = null + + /** + * Adds a text content block to the result. + * + * This is the most common content type for tool results. + * + * Example: + * ```kotlin + * textContent("Operation completed successfully") + * ``` + * + * @param text The text content + */ + public fun textContent(text: String) { + contentList.add(TextContent(text = text)) + } + + /** + * Adds an image content block to the result. + * + * Example: + * ```kotlin + * imageContent( + * data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ...", + * mimeType = "image/png" + * ) + * ``` + * + * @param data Base64-encoded image data + * @param mimeType The MIME type of the image (e.g., "image/png", "image/jpeg") + */ + public fun imageContent(data: String, mimeType: String) { + contentList.add(ImageContent(data = data, mimeType = mimeType)) + } + + /** + * Adds an audio content block to the result. + * + * Example: + * ```kotlin + * audioContent( + * data = "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgA...", + * mimeType = "audio/wav" + * ) + * ``` + * + * @param data Base64-encoded audio data + * @param mimeType The MIME type of the audio (e.g., "audio/wav", "audio/mp3") + */ + public fun audioContent(data: String, mimeType: String) { + contentList.add(AudioContent(data = data, mimeType = mimeType)) + } + + /** + * Adds a pre-built content block to the result. + * + * Use this when you have already constructed a content block. + * + * Example: + * ```kotlin + * val block = TextContent("Prebuilt content") + * content(block) + * ``` + * + * @param block The content block to add + */ + public fun content(block: ContentBlock) { + contentList.add(block) + } + + /** + * Sets structured content directly from a JsonObject. + * + * Example: + * ```kotlin + * structuredContent(buildJsonObject { + * put("status", "success") + * put("recordsAffected", 5) + * }) + * ``` + * + * @param content The structured content as a JsonObject + */ + public fun structuredContent(content: JsonObject) { + this.structuredContentValue = content + } + + /** + * Sets structured content using a DSL builder. + * + * Provides machine-readable output in addition to the human-readable content. + * + * Example: + * ```kotlin + * structuredContent { + * put("status", "success") + * put("count", 42) + * putJsonArray("items") { + * add("item1") + * add("item2") + * } + * } + * ``` + * + * @param block Lambda for building the structured content JsonObject + */ + public fun structuredContent(block: JsonObjectBuilder.() -> Unit) { + structuredContent(buildJsonObject(block)) + } + + @PublishedApi + override fun build(): CallToolResult { + require(contentList.isNotEmpty()) { + "At least one content block is required. Use textContent(), imageContent(), or audioContent()." + } + + return CallToolResult( + content = contentList.toList(), + isError = isError, + structuredContent = structuredContentValue, + meta = meta, + ) + } +} + +/** + * Creates a [ListToolsResult] using a type-safe DSL builder. + * + * ## Required + * - [tools][ListToolsResultBuilder.toolsList] - List of available tools (at least one) + * + * ## Optional + * - [nextCursor][ListToolsResultBuilder.nextCursor] - Pagination cursor for next page + * - [meta][ListToolsResultBuilder.meta] - Metadata for the response + * + * Example with single tool: + * ```kotlin + * val result = buildListToolsResult { + * tool { + * name = "searchDatabase" + * description = "Search the database for records" + * inputSchema { + * put("type", "object") + * putJsonObject("properties") { + * putJsonObject("query") { + * put("type", "string") + * put("description", "Search query") + * } + * } + * putJsonArray("required") { + * add("query") + * } + * } + * } + * } + * ``` + * + * Example with pagination: + * ```kotlin + * val result = buildListToolsResult { + * tool(Tool("tool1", ToolSchema())) + * tool(Tool("tool2", ToolSchema())) + * nextCursor = "eyJwYWdlIjogMn0=" + * } + * ``` + * + * @param block Configuration lambda for setting up the list tools result + * @return A configured [ListToolsResult] instance + * @see ListToolsResultBuilder + * @see ListToolsResult + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +public inline fun buildListToolsResult(block: ListToolsResultBuilder.() -> Unit): ListToolsResult { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListToolsResultBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [ListToolsResult] instances. + * + * This builder creates a response containing a list of tools available on the server, + * with optional pagination support. + * + * ## Required + * - At least one tool (via [tool] method) + * + * ## Optional + * - [nextCursor] - Pagination cursor (inherited from [PaginatedResultBuilder]) + * - [meta] - Metadata for the response (inherited from [ResultBuilder]) + * + * @see buildListToolsResult + * @see ListToolsResult + * @see PaginatedResultBuilder + */ +@McpDsl +public class ListToolsResultBuilder @PublishedApi internal constructor() : PaginatedResultBuilder() { + private val toolsList: MutableList = mutableListOf() + + /** + * Adds a pre-built tool to the result. + * + * Use this when you have already constructed a Tool instance. + * + * Example: + * ```kotlin + * val myTool = Tool( + * name = "calculate", + * inputSchema = ToolSchema(/* ... */), + * description = "Performs calculations" + * ) + * tool(myTool) + * ``` + * + * @param tool The tool to add + */ + public fun tool(tool: Tool) { + toolsList.add(tool) + } + + /** + * Adds a tool using a DSL builder. + * + * This is the recommended way to define tools inline. + * + * Example: + * ```kotlin + * tool { + * name = "getCurrentTime" + * description = "Get the current time" + * inputSchema { + * // Schema definition + * } + * } + * ``` + * + * @param block Lambda for building the Tool + */ + public fun tool(block: ToolBuilder.() -> Unit) { + toolsList.add(ToolBuilder().apply(block).build()) + } + + @PublishedApi + override fun build(): ListToolsResult { + require(toolsList.isNotEmpty()) { + "At least one tool is required. Use tool() or tool { } to add tools." + } + + return ListToolsResult( + tools = toolsList.toList(), + nextCursor = nextCursor, + meta = meta, + ) + } +} + +/** + * DSL builder for constructing [Tool] instances. + * + * Used within [ListToolsResultBuilder] to define individual tools. + * + * ## Required + * - [name] - The programmatic identifier for the tool + * - [inputSchema] - JSON Schema defining the tool's input parameters + * + * ## Optional + * - [description] - Human-readable description + * - [outputSchema] - JSON Schema defining the tool's output structure + * - [title] - Display name for the tool + * - [annotations] - Additional hints about tool behavior + * - [icons] - Icon representations for UIs + * - [meta] - Metadata for the tool + * + * @see Tool + * @see ListToolsResultBuilder + */ +@McpDsl +public class ToolBuilder @PublishedApi internal constructor() { + /** + * The programmatic identifier for this tool. Required. + * + * Example: `name = "searchDatabase"` + */ + public var name: String? = null + + /** + * Human-readable description of what the tool does. + * + * Example: `description = "Search the database for records matching a query"` + */ + public var description: String? = null + + private var inputSchemaValue: ToolSchema? = null + + private var outputSchemaValue: ToolSchema? = null + + /** + * Optional display name for the tool. + * + * Example: `title = "Database Search"` + */ + public var title: String? = null + + /** + * Optional annotations providing hints about tool behavior. + * + * Example: + * ```kotlin + * annotations = ToolAnnotations( + * readOnlyHint = true, + * idempotentHint = true + * ) + * ``` + */ + public var annotations: ToolAnnotations? = null + + /** + * Optional list of icons for the tool. + * + * Example: + * ```kotlin + * icons = listOf( + * Icon(url = "https://example.com/icon.png", size = "32x32", mimeType = "image/png") + * ) + * ``` + */ + public var icons: List? = null + + private var metaValue: JsonObject? = null + + /** + * Sets input schema directly from a ToolSchema. + * + * Example: + * ```kotlin + * inputSchema(ToolSchema( + * properties = buildJsonObject { /* ... */ }, + * required = listOf("query") + * )) + * ``` + * + * @param schema The input schema + */ + public fun inputSchema(schema: ToolSchema) { + this.inputSchemaValue = schema + } + + /** + * Sets input schema using a DSL builder. + * + * Example: + * ```kotlin + * inputSchema { + * properties = buildJsonObject { + * putJsonObject("query") { + * put("type", "string") + * put("description", "Search query") + * } + * putJsonObject("limit") { + * put("type", "integer") + * put("default", 10) + * } + * } + * required = listOf("query") + * } + * ``` + * + * @param block Lambda for building the ToolSchema + */ + public fun inputSchema(block: ToolSchemaBuilder.() -> Unit) { + inputSchema(ToolSchemaBuilder().apply(block).build()) + } + + /** + * Sets output schema directly from a ToolSchema. + * + * Example: + * ```kotlin + * outputSchema(ToolSchema( + * properties = buildJsonObject { /* ... */ } + * )) + * ``` + * + * @param schema The output schema + */ + public fun outputSchema(schema: ToolSchema) { + this.outputSchemaValue = schema + } + + /** + * Sets output schema using a DSL builder. + * + * Example: + * ```kotlin + * outputSchema { + * properties = buildJsonObject { + * putJsonObject("results") { + * put("type", "array") + * } + * putJsonObject("count") { + * put("type", "integer") + * } + * } + * } + * ``` + * + * @param block Lambda for building the ToolSchema + */ + public fun outputSchema(block: ToolSchemaBuilder.() -> Unit) { + outputSchema(ToolSchemaBuilder().apply(block).build()) + } + + /** + * Sets metadata directly from a JsonObject. + * + * Example: + * ```kotlin + * meta(buildJsonObject { + * put("version", "1.0") + * }) + * ``` + * + * @param meta The metadata as a JsonObject + */ + public fun meta(meta: JsonObject) { + this.metaValue = meta + } + + /** + * Sets metadata using a DSL builder. + * + * Example: + * ```kotlin + * meta { + * put("version", "1.0") + * put("deprecated", false) + * } + * ``` + * + * @param block Lambda for building the metadata JsonObject + */ + public fun meta(block: JsonObjectBuilder.() -> Unit) { + meta(buildJsonObject(block)) + } + + @PublishedApi + internal fun build(): Tool { + val name = requireNotNull(name) { + "Missing required field 'name'. Example: name = \"toolName\"" + } + val inputSchema = requireNotNull(inputSchemaValue) { + "Missing required field 'inputSchema'. Use inputSchema { } to define the schema." + } + + return Tool( + name = name, + inputSchema = inputSchema, + description = description, + outputSchema = outputSchemaValue, + title = title, + annotations = annotations, + icons = icons, + meta = metaValue, + ) + } +} + +/** + * DSL builder for constructing [ToolSchema] instances. + * + * Used to define JSON Schema for tool input/output parameters. + * + * @see ToolSchema + * @see ToolBuilder + */ +@McpDsl +public class ToolSchemaBuilder @PublishedApi internal constructor() { + /** + * Map of property names to their schema definitions. + * + * Example: + * ```kotlin + * properties = buildJsonObject { + * putJsonObject("query") { + * put("type", "string") + * put("description", "Search query") + * } + * } + * ``` + */ + public var properties: JsonObject? = null + + /** + * List of required property names. + * + * Example: `required = listOf("query", "userId")` + */ + public var required: List? = null + + /** + * Schema definitions available to references in properties ($defs). + * + * Example: + * ```kotlin + * defs = buildJsonObject { + * putJsonObject("Address") { + * put("type", "object") + * putJsonObject("properties") { + * putJsonObject("street") { + * put("type", "string") + * } + * } + * } + * } + * ``` + */ + public var defs: JsonObject? = null + + @PublishedApi + internal fun build(): ToolSchema = ToolSchema( + properties = properties, + required = required, + defs = defs, + ) +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionResultDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionResultDslTest.kt new file mode 100644 index 000000000..eef86b600 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionResultDslTest.kt @@ -0,0 +1,150 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.buildCompleteResult +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test + +/** + * Tests for CompleteResult DSL builder. + * + * Verifies CompleteResult can be constructed via DSL, + * covering minimal (required only), full (all fields), and edge cases. + */ +@OptIn(ExperimentalMcpApi::class) +class CompletionResultDslTest { + + @Test + fun `CompleteResult should build minimal with values only`() { + val result = buildCompleteResult { + values("user1", "user2", "user3") + } + + result.completion.values shouldHaveSize 3 + result.completion.values shouldBe listOf("user1", "user2", "user3") + result.completion.total.shouldBeNull() + result.completion.hasMore.shouldBeNull() + result.meta.shouldBeNull() + } + + @Test + fun `CompleteResult should build full with all fields`() { + val result = buildCompleteResult { + values(listOf("admin", "moderator", "user", "guest")) + total = 42 + hasMore = true + + meta { + put("cached", true) + put("queryTime", 50) + } + } + + result.completion.values shouldHaveSize 4 + result.completion.total shouldBe 42 + result.completion.hasMore shouldBe true + + result.meta shouldNotBeNull { + get("cached")?.jsonPrimitive?.boolean shouldBe true + get("queryTime")?.jsonPrimitive?.int shouldBe 50 + } + } + + @Test + fun `CompleteResult should support values via vararg`() { + val result = buildCompleteResult { + values("a", "b", "c", "d", "e") + } + + result.completion.values shouldHaveSize 5 + } + + @Test + fun `CompleteResult should support values via list`() { + val completions = listOf("option1", "option2", "option3") + + val result = buildCompleteResult { + values(completions) + } + + result.completion.values shouldBe completions + } + + @Test + fun `CompleteResult should throw if no values provided`() { + shouldThrow { + buildCompleteResult { } + } + } + + @Test + fun `CompleteResult should throw if values exceed 100 items`() { + val tooManyValues = (1..101).map { "value$it" } + + shouldThrow { + buildCompleteResult { + values(tooManyValues) + } + } + } + + @Test + fun `CompleteResult should support exactly 100 values`() { + val maxValues = (1..100).map { "value$it" } + + val result = buildCompleteResult { + values(maxValues) + } + + result.completion.values shouldHaveSize 100 + } + + @Test + fun `CompleteResult should support empty strings in values`() { + val result = buildCompleteResult { + values("", "non-empty", "") + } + + result.completion.values shouldBe listOf("", "non-empty", "") + } + + @Test + fun `CompleteResult should support unicode in values`() { + val result = buildCompleteResult { + values("Hello 🌍", "Ça va?", "北京", "مرحبا") + } + + result.completion.values shouldHaveSize 4 + result.completion.values[0] shouldBe "Hello 🌍" + } + + @Test + fun `CompleteResult should support total without hasMore`() { + val result = buildCompleteResult { + values("a", "b", "c") + total = 10 + } + + result.completion.total shouldBe 10 + result.completion.hasMore.shouldBeNull() + } + + @Test + fun `CompleteResult should support hasMore without total`() { + val result = buildCompleteResult { + values("a", "b", "c") + hasMore = true + } + + result.completion.hasMore shouldBe true + result.completion.total.shouldBeNull() + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeResultDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeResultDslTest.kt new file mode 100644 index 000000000..877cc582d --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeResultDslTest.kt @@ -0,0 +1,201 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION +import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities +import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeResult +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test + +/** + * Tests for InitializeResult DSL builder. + * + * Verifies InitializeResult can be constructed via DSL, + * covering minimal (required only), full (all fields), and edge cases. + */ +@OptIn(ExperimentalMcpApi::class) +class InitializeResultDslTest { + + @Test + fun `InitializeResult should build minimal with default protocol version`() { + val result = buildInitializeResult { + capabilities(ServerCapabilities()) + info("MyServer", "1.0.0") + } + + result.protocolVersion shouldBe LATEST_PROTOCOL_VERSION + result.capabilities shouldNotBeNull {} + result.serverInfo shouldNotBeNull { + name shouldBe "MyServer" + version shouldBe "1.0.0" + } + result.instructions.shouldBeNull() + result.meta.shouldBeNull() + } + + @Test + fun `InitializeResult should build full with all fields`() { + val result = buildInitializeResult { + protocolVersion = "2024-11-05" + + capabilities( + ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + resources = ServerCapabilities.Resources(listChanged = true, subscribe = true), + prompts = ServerCapabilities.Prompts(listChanged = true), + logging = EmptyJsonObject, + completions = EmptyJsonObject, + ), + ) + + info( + name = "AdvancedServer", + version = "2.0.0", + title = "Advanced MCP Server", + websiteUrl = "https://example.com", + ) + + instructions = "Use this server for advanced operations. Available tools include..." + + meta { + put("serverStartTime", 1707317000000L) + put("region", "us-west-1") + put("environment", "production") + } + } + + result.protocolVersion shouldBe "2024-11-05" + + result.capabilities shouldNotBeNull { + tools shouldNotBeNull { + listChanged shouldBe true + } + resources shouldNotBeNull { + listChanged shouldBe true + subscribe shouldBe true + } + prompts shouldNotBeNull { + listChanged shouldBe true + } + logging shouldNotBeNull {} + completions shouldNotBeNull {} + } + + result.serverInfo shouldNotBeNull { + name shouldBe "AdvancedServer" + version shouldBe "2.0.0" + title shouldBe "Advanced MCP Server" + websiteUrl shouldBe "https://example.com" + } + + result.instructions shouldBe "Use this server for advanced operations. Available tools include..." + + result.meta shouldNotBeNull { + get("region")?.jsonPrimitive?.content shouldBe "us-west-1" + get("environment")?.jsonPrimitive?.content shouldBe "production" + } + } + + @Test + fun `InitializeResult should support partial capabilities`() { + val result = buildInitializeResult { + capabilities( + ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = true), + // Other capabilities null + ), + ) + info("SimpleServer", "1.0") + } + + result.capabilities shouldNotBeNull { + tools shouldNotBeNull {} + resources.shouldBeNull() + prompts.shouldBeNull() + logging.shouldBeNull() + completions.shouldBeNull() + } + } + + @Test + fun `InitializeResult should support capabilities with false flags`() { + val result = buildInitializeResult { + capabilities( + ServerCapabilities( + tools = ServerCapabilities.Tools(listChanged = false), + resources = ServerCapabilities.Resources(listChanged = false, subscribe = false), + ), + ) + info("StaticServer", "1.0") + } + + result.capabilities shouldNotBeNull { + tools shouldNotBeNull { + listChanged shouldBe false + } + resources shouldNotBeNull { + listChanged shouldBe false + subscribe shouldBe false + } + } + } + + @Test + fun `InitializeResult should throw if capabilities missing`() { + shouldThrow { + buildInitializeResult { + info("Server", "1.0") + } + } + } + + @Test + fun `InitializeResult should throw if info missing`() { + shouldThrow { + buildInitializeResult { + capabilities(ServerCapabilities()) + } + } + } + + @Test + fun `InitializeResult should support long instructions`() { + val longInstructions = "This is a very long instruction text. ".repeat(100) + + val result = buildInitializeResult { + capabilities(ServerCapabilities()) + info("Server", "1.0") + instructions = longInstructions + } + + result.instructions shouldBe longInstructions + } + + @Test + fun `InitializeResult should support custom protocol versions`() { + val result = buildInitializeResult { + protocolVersion = "2025-01-01" + capabilities(ServerCapabilities()) + info("FutureServer", "3.0") + } + + result.protocolVersion shouldBe "2025-01-01" + } + + @Test + fun `InitializeResult should support unicode in instructions`() { + val result = buildInitializeResult { + capabilities(ServerCapabilities()) + info("Server", "1.0") + instructions = "サーバーの使い方: 🚀 Start here! Ça va?" + } + + result.instructions shouldBe "サーバーの使い方: 🚀 Start here! Ça va?" + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsResultDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsResultDslTest.kt new file mode 100644 index 000000000..6a674e890 --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsResultDslTest.kt @@ -0,0 +1,194 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.buildGetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.buildListPromptsResult +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlin.test.Test + +/** + * Tests for PromptsResult DSL builders. + * + * Verifies GetPromptResult and ListPromptsResult can be constructed via DSL, + * covering minimal (required only), full (all fields), and edge cases. + */ +@OptIn(ExperimentalMcpApi::class) +class PromptsResultDslTest { + + // ======================================================================== + // GetPromptResult Tests + // ======================================================================== + + @Test + fun `GetPromptResult should build minimal with single message`() { + val result = buildGetPromptResult { + message(Role.User, TextContent("Hello, how can I help you?")) + } + + result.messages shouldHaveSize 1 + result.messages[0].role shouldBe Role.User + (result.messages[0].content as TextContent).text shouldBe "Hello, how can I help you?" + result.description.shouldBeNull() + result.meta.shouldBeNull() + } + + @Test + fun `GetPromptResult should build full with multiple messages and description`() { + val result = buildGetPromptResult { + description = "A customer service greeting prompt with context" + + message(Role.User, TextContent("You are a helpful customer service assistant.")) + message(Role.User, TextContent("I need help with my order.")) + message(Role.Assistant, TextContent("I'd be happy to help! Could you provide your order number?")) + + meta { + put("category", "customer-service") + put("language", "en") + put("version", 2) + } + } + + result.messages shouldHaveSize 3 + result.messages[0].role shouldBe Role.User + result.messages[1].role shouldBe Role.User + result.messages[2].role shouldBe Role.Assistant + + result.description shouldBe "A customer service greeting prompt with context" + + result.meta shouldNotBeNull { + get("category")?.jsonPrimitive?.content shouldBe "customer-service" + get("language")?.jsonPrimitive?.content shouldBe "en" + get("version")?.jsonPrimitive?.content shouldBe "2" + } + } + + @Test + fun `GetPromptResult should throw if no messages provided`() { + shouldThrow { + buildGetPromptResult { } + } + } + + // ======================================================================== + // ListPromptsResult Tests + // ======================================================================== + + @Test + fun `ListPromptsResult should build minimal with single prompt`() { + val result = buildListPromptsResult { + prompt { + name = "greeting" + } + } + + result.prompts shouldHaveSize 1 + result.prompts[0].name shouldBe "greeting" + result.nextCursor.shouldBeNull() + result.meta.shouldBeNull() + } + + @Test + fun `ListPromptsResult should build full with multiple prompts and pagination`() { + val result = buildListPromptsResult { + prompt { + name = "greeting" + description = "A friendly greeting prompt" + title = "Friendly Greeting" + arguments = listOf( + PromptArgument(name = "userName", description = "The user's name", required = true), + PromptArgument(name = "language", description = "Preferred language", required = false), + ) + meta { + put("category", "greetings") + } + } + + prompt { + name = "farewell" + description = "A polite farewell prompt" + title = "Polite Farewell" + } + + prompt { + name = "product-recommendation" + description = "Recommends products based on user preferences" + arguments = listOf( + PromptArgument(name = "userId", required = true), + PromptArgument(name = "category", required = false), + ) + } + + nextCursor = "eyJwYWdlIjogMn0=" + + meta { + put("serverVersion", "2.0") + put("totalPrompts", 50) + } + } + + result.prompts shouldHaveSize 3 + + result.prompts[0].let { prompt -> + prompt.name shouldBe "greeting" + prompt.description shouldBe "A friendly greeting prompt" + prompt.title shouldBe "Friendly Greeting" + prompt.arguments?.let { args -> + args shouldHaveSize 2 + args[0].name shouldBe "userName" + args[0].required shouldBe true + args[1].name shouldBe "language" + args[1].required shouldBe false + } + } + + result.prompts[1].name shouldBe "farewell" + result.prompts[2].name shouldBe "product-recommendation" + + result.nextCursor shouldBe "eyJwYWdlIjogMn0=" + + result.meta shouldNotBeNull { + get("serverVersion")?.jsonPrimitive?.content shouldBe "2.0" + get("totalPrompts")?.jsonPrimitive?.content shouldBe "50" + } + } + + @Test + fun `ListPromptsResult should throw if no prompts provided`() { + shouldThrow { + buildListPromptsResult { } + } + } + + @Test + fun `ListPromptsResult should support prompt without arguments`() { + val result = buildListPromptsResult { + prompt { + name = "simplePrompt" + description = "A prompt with no arguments" + } + } + + result.prompts[0].arguments.shouldBeNull() + } + + @Test + fun `ListPromptsResult should support empty arguments list`() { + val result = buildListPromptsResult { + prompt { + name = "emptyArgs" + arguments = emptyList() + } + } + + result.prompts[0].arguments?.let { it shouldHaveSize 0 } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesResultDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesResultDslTest.kt new file mode 100644 index 000000000..cd3e2af0e --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesResultDslTest.kt @@ -0,0 +1,142 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.shouldBe +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.Annotations +import io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents +import io.modelcontextprotocol.kotlin.sdk.types.buildListResourceTemplatesResult +import io.modelcontextprotocol.kotlin.sdk.types.buildListResourcesResult +import io.modelcontextprotocol.kotlin.sdk.types.buildReadResourceResult +import kotlinx.serialization.json.put +import kotlin.test.Test + +/** + * Tests for ResourcesResult DSL builders. + * + * Verifies resource result types can be constructed via DSL. + */ +@OptIn(ExperimentalMcpApi::class) +class ResourcesResultDslTest { + + @Test + fun `ListResourcesResult should build minimal with single resource`() { + val result = buildListResourcesResult { + resource { + uri = "file:///path/to/file.txt" + name = "file.txt" + } + } + + result.resources shouldHaveSize 1 + result.resources[0].uri shouldBe "file:///path/to/file.txt" + result.resources[0].name shouldBe "file.txt" + } + + @Test + fun `ListResourcesResult should build full with multiple resources`() { + val result = buildListResourcesResult { + resource { + uri = "file:///docs/readme.md" + name = "readme.md" + description = "Project documentation" + mimeType = "text/markdown" + size = 2048 + title = "README" + annotations = Annotations( + audience = listOf(Role.User), + priority = 0.9, + ) + } + + resource { + uri = "db://users/123" + name = "user-123" + description = "User profile data" + mimeType = "application/json" + } + + nextCursor = "next-page" + meta { + put("cached", true) + } + } + + result.resources shouldHaveSize 2 + result.nextCursor shouldBe "next-page" + } + + @Test + fun `ReadResourceResult should build with text content`() { + val result = buildReadResourceResult { + textContent( + uri = "file:///docs/readme.md", + text = "# Project README\n\nWelcome!", + mimeType = "text/markdown", + ) + } + + result.contents shouldHaveSize 1 + (result.contents[0] as TextResourceContents).let { + it.uri shouldBe "file:///docs/readme.md" + it.text shouldBe "# Project README\n\nWelcome!" + it.mimeType shouldBe "text/markdown" + } + } + + @Test + fun `ReadResourceResult should build with blob content`() { + val result = buildReadResourceResult { + blobContent( + uri = "file:///images/logo.png", + blob = "iVBORw0KGgoggg==", + mimeType = "image/png", + ) + } + + result.contents shouldHaveSize 1 + (result.contents[0] as BlobResourceContents).let { + it.uri shouldBe "file:///images/logo.png" + it.mimeType shouldBe "image/png" + } + } + + @Test + fun `ListResourceTemplatesResult should build with templates`() { + val result = buildListResourceTemplatesResult { + template { + uriTemplate = "file:///{path}" + name = "file-template" + description = "Access any file" + mimeType = "text/plain" + } + + template { + uriTemplate = "db://users/{userId}" + name = "user-db-template" + } + + nextCursor = "next" + } + + result.resourceTemplates shouldHaveSize 2 + result.resourceTemplates[0].uriTemplate shouldBe "file:///{path}" + } + + @Test + fun `ListResourcesResult should throw if no resources`() { + shouldThrow { + buildListResourcesResult { } + } + } + + @Test + fun `ReadResourceResult should throw if no contents`() { + shouldThrow { + buildReadResourceResult { } + } + } +} diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsResultDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsResultDslTest.kt new file mode 100644 index 000000000..6463475ba --- /dev/null +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsResultDslTest.kt @@ -0,0 +1,292 @@ +package io.modelcontextprotocol.kotlin.sdk.types.dsl + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.collections.shouldContainAll +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldBeInstanceOf +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.AudioContent +import io.modelcontextprotocol.kotlin.sdk.types.ImageContent +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations +import io.modelcontextprotocol.kotlin.sdk.types.buildCallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.buildListToolsResult +import kotlinx.serialization.json.add +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put +import kotlinx.serialization.json.putJsonArray +import kotlinx.serialization.json.putJsonObject +import kotlin.test.Test + +/** + * Tests for ToolsResult DSL builders. + * + * Verifies CallToolResult and ListToolsResult can be constructed via DSL, + * covering minimal (required only), full (all fields), and edge cases. + */ +@OptIn(ExperimentalMcpApi::class) +class ToolsResultDslTest { + + // ======================================================================== + // CallToolResult Tests + // ======================================================================== + + @Test + fun `CallToolResult should build minimal with single text content`() { + val result = buildCallToolResult { + textContent("Operation successful") + } + + result.content shouldHaveSize 1 + (result.content[0] as TextContent).text shouldBe "Operation successful" + result.isError.shouldBeNull() + result.structuredContent.shouldBeNull() + result.meta.shouldBeNull() + } + + @Test + fun `CallToolResult should build full with all content types and structured data`() { + val result = buildCallToolResult { + textContent("Database query completed") + @Suppress("MaxLineLength") + imageContent( + data = + "iVBORw0KGgoggg==", + mimeType = "image/png", + ) + audioContent( + data = "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAAABmYWN0BAAAAAAAAABkYXRhAAAAAA==", + mimeType = "audio/wav", + ) + isError = false + structuredContent { + put("queryTime", 150) + put("recordsAffected", 42) + putJsonArray("columns") { + add("id") + add("name") + add("email") + } + } + meta { + put("source", "postgres") + put("cached", true) + } + } + + result.content shouldHaveSize 3 + result.content[0].shouldBeInstanceOf() + result.content[1].shouldBeInstanceOf() + result.content[2].shouldBeInstanceOf() + + result.isError shouldBe false + + result.structuredContent shouldNotBeNull { + get("queryTime")?.jsonPrimitive?.int shouldBe 150 + get("recordsAffected")?.jsonPrimitive?.int shouldBe 42 + get("columns")?.jsonArray?.map { it.jsonPrimitive.content } + ?.let { it shouldContainAll listOf("id", "name", "email") } + } + + result.meta shouldNotBeNull { + get("source")?.jsonPrimitive?.content shouldBe "postgres" + get("cached")?.jsonPrimitive?.boolean shouldBe true + } + } + + @Test + fun `CallToolResult should support error status`() { + val result = buildCallToolResult { + textContent("Failed to connect to database") + isError = true + } + + result.isError shouldBe true + (result.content[0] as TextContent).text shouldBe "Failed to connect to database" + } + + @Test + fun `CallToolResult should throw if no content provided`() { + shouldThrow { + buildCallToolResult { } + } + } + + // ======================================================================== + // ListToolsResult Tests + // ======================================================================== + + @Test + fun `ListToolsResult should build minimal with single tool`() { + val result = buildListToolsResult { + tool { + name = "getCurrentTime" + inputSchema { + // Empty schema for no parameters + } + } + } + + result.tools shouldHaveSize 1 + result.tools[0].name shouldBe "getCurrentTime" + result.nextCursor.shouldBeNull() + result.meta.shouldBeNull() + } + + @Test + @Suppress("LongMethod") + fun `ListToolsResult should build full with multiple tools and pagination`() { + val result = buildListToolsResult { + tool { + name = "searchDatabase" + description = "Search the database for records" + title = "Database Search" + inputSchema { + properties = buildJsonObject { + putJsonObject("query") { + put("type", "string") + put("description", "Search query") + } + putJsonObject("limit") { + put("type", "integer") + put("default", 10) + } + } + required = listOf("query") + } + outputSchema { + properties = buildJsonObject { + putJsonObject("results") { + put("type", "array") + } + } + } + annotations = ToolAnnotations( + readOnlyHint = true, + idempotentHint = true, + ) + } + + tool { + name = "updateRecord" + description = "Update a database record" + inputSchema { + properties = buildJsonObject { + putJsonObject("id") { + put("type", "integer") + } + putJsonObject("data") { + put("type", "object") + } + } + required = listOf("id", "data") + } + annotations = ToolAnnotations( + readOnlyHint = false, + destructiveHint = false, + ) + } + + nextCursor = "eyJwYWdlIjogMn0=" + + meta { + put("serverVersion", "1.0.0") + put("cached", false) + } + } + + result.tools shouldHaveSize 2 + + result.tools[0].let { tool -> + tool.name shouldBe "searchDatabase" + tool.description shouldBe "Search the database for records" + tool.title shouldBe "Database Search" + tool.inputSchema shouldNotBeNull { + required shouldBe listOf("query") + } + tool.annotations shouldNotBeNull { + readOnlyHint shouldBe true + idempotentHint shouldBe true + } + } + + result.tools[1].let { tool -> + tool.name shouldBe "updateRecord" + tool.annotations shouldNotBeNull { + readOnlyHint shouldBe false + destructiveHint shouldBe false + } + } + + result.nextCursor shouldBe "eyJwYWdlIjogMn0=" + + result.meta shouldNotBeNull { + get("serverVersion")?.jsonPrimitive?.content shouldBe "1.0.0" + get("cached")?.jsonPrimitive?.boolean shouldBe false + } + } + + @Test + fun `ListToolsResult should support tool with defs in schema`() { + val result = buildListToolsResult { + tool { + name = "createUser" + inputSchema { + properties = buildJsonObject { + putJsonObject("user") { + put("\$ref", "#/\$defs/User") + } + } + defs = buildJsonObject { + putJsonObject("User") { + put("type", "object") + putJsonObject("properties") { + putJsonObject("name") { + put("type", "string") + } + putJsonObject("email") { + put("type", "string") + } + } + } + } + } + } + } + + result.tools[0].inputSchema.defs shouldNotBeNull { + get("User") shouldNotBeNull {} + } + } + + @Test + fun `ListToolsResult should throw if no tools provided`() { + shouldThrow { + buildListToolsResult { } + } + } + + @Test + fun `ListToolsResult should support empty input schema`() { + val result = buildListToolsResult { + tool { + name = "noArgs" + inputSchema { + // No properties, no required fields + } + } + } + + result.tools[0].inputSchema shouldNotBeNull { + properties.shouldBeNull() + required.shouldBeNull() + } + } +} From ad51992d9743d308b32fe7657ea3f80dba5a760f Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:35:39 +0200 Subject: [PATCH 2/7] refactor(test): replace `CallToolResult` with `buildCallToolResult` DSL across integration tests - Updated integration tests to use the `buildCallToolResult` DSL for constructing results, improving clarity and reducing boilerplate. - Added `@OptIn(ExperimentalMcpApi::class)` annotations to test files requiring experimental API. - Refactored delay handling in tests to streamline coroutine usage. --- .../kotlin/AbstractToolIntegrationTest.kt | 27 ++++++++++--------- .../sse/KotlinServerForTsClientSse.kt | 24 +++++++++-------- .../StreamableHttpServerTransportTest.kt | 21 ++++++++------- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt index 7da82cc3a..fb73c492d 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt @@ -1,6 +1,7 @@ package io.modelcontextprotocol.kotlin.sdk.integration.kotlin import io.kotest.assertions.json.shouldEqualJson +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequestParams import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult @@ -9,6 +10,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ImageContent import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import io.modelcontextprotocol.kotlin.sdk.types.buildCallToolResult import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -28,6 +30,7 @@ import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue +@OptIn(ExperimentalMcpApi::class) abstract class AbstractToolIntegrationTest : KotlinTestBase() { private val testToolName = "echo" private val testToolDescription = "A simple echo tool that returns the input text" @@ -84,12 +87,12 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { ) { request -> val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided" - CallToolResult( - content = listOf(TextContent(text = "Echo: $text")), - structuredContent = buildJsonObject { + buildCallToolResult { + textContent("Echo: $text") + structuredContent { put("result", text) - }, - ) + } + } } } @@ -112,12 +115,12 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { ) { request -> val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided" - CallToolResult( - content = listOf(TextContent(text = "Echo: $text")), - structuredContent = buildJsonObject { + buildCallToolResult { + textContent("Echo: $text") + structuredContent { put("result", text) - }, - ) + } + } } server.addTool( @@ -164,9 +167,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { val delay = (request.params.arguments?.get("delay") as? JsonPrimitive)?.content?.toIntOrNull() ?: 1000 // simulate slow operation - runBlocking { - delay(delay.toLong()) - } + delay(delay.toLong()) CallToolResult( content = listOf(TextContent(text = "Completed after ${delay}ms delay")), diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt index 97acc29f6..123683788 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt @@ -17,11 +17,11 @@ import io.ktor.server.routing.delete import io.ktor.server.routing.get import io.ktor.server.routing.post import io.ktor.server.routing.routing +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions -import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError @@ -40,6 +40,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import io.modelcontextprotocol.kotlin.sdk.types.buildCallToolResult import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.Channel @@ -59,6 +60,7 @@ import java.util.concurrent.ConcurrentHashMap private val logger = KotlinLogging.logger {} +@OptIn(ExperimentalMcpApi::class) class KotlinServerForTsClient { private val serverTransports = ConcurrentHashMap() private val jsonFormat = Json { ignoreUnknownKeys = true } @@ -226,12 +228,12 @@ class KotlinServerForTsClient { ), ) { request -> val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World" - CallToolResult( - content = listOf(TextContent("Hello, $name!")), - structuredContent = buildJsonObject { + buildCallToolResult { + textContent("Hello, $name!") + structuredContent { put("greeting", JsonPrimitive("Hello, $name!")) - }, - ) + } + } } server.addTool( @@ -252,13 +254,13 @@ class KotlinServerForTsClient { ) { request -> val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World" - CallToolResult( - content = listOf(TextContent("Multiple greetings sent to $name!")), - structuredContent = buildJsonObject { + buildCallToolResult { + textContent("Multiple greetings sent to $name!") + structuredContent { put("greeting", JsonPrimitive("Multiple greetings sent to $name!")) put("notificationCount", JsonPrimitive(3)) - }, - ) + } + } } server.addPrompt( diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index 968c0572a..dc825c886 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -21,6 +21,7 @@ import io.ktor.server.routing.post import io.ktor.server.routing.routing import io.ktor.server.testing.ApplicationTestBuilder import io.ktor.server.testing.testApplication +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities import io.modelcontextprotocol.kotlin.sdk.types.EmptyResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation @@ -32,12 +33,10 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult -import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.RequestId -import io.modelcontextprotocol.kotlin.sdk.types.Tool -import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema +import io.modelcontextprotocol.kotlin.sdk.types.buildListToolsResult import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.buildJsonObject @@ -50,6 +49,7 @@ import kotlin.test.assertNotNull import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation import io.ktor.server.plugins.contentnegotiation.ContentNegotiation as ServerContentNegotiation +@OptIn(ExperimentalMcpApi::class) class StreamableHttpServerTransportTest { private val path = "/transport" @@ -161,12 +161,15 @@ class StreamableHttpServerTransportTest { val firstRequest = JSONRPCRequest(id = RequestId("first"), method = Method.Defined.ToolsList.value) val secondRequest = JSONRPCRequest(id = RequestId("second"), method = Method.Defined.ResourcesList.value) - val firstResult = ListToolsResult( - tools = listOf( - Tool(name = "tool-1", inputSchema = ToolSchema()), - ), - meta = buildJsonObject { put("label", "first") }, - ) + val firstResult = buildListToolsResult { + tool { + name = "tool-1" + inputSchema { } + } + meta { + put("label", "first") + } + } val secondResult = ListResourcesResult( resources = emptyList(), meta = buildJsonObject { put("label", "second") }, From d9ac40cc8d8323196d6a63f7ee81648fbaeaf2e5 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 17 Feb 2026 14:36:03 +0200 Subject: [PATCH 3/7] feat(test): extend DSL tests to cover error cases and metadata support - Added new test cases for `CallToolResult`, `ListResourcesResult`, and `ReadResourceResult` to validate error handling, structured content, and metadata processing. - Improved coverage for resource and template handling with additional edge cases for mixed-field resources and pagination. --- .../kotlin/sdk/types/dsl/ContentDslTest.kt | 4 +- .../sdk/types/dsl/ResourcesResultDslTest.kt | 148 ++++++++++++++++++ .../sdk/types/dsl/ToolsResultDslTest.kt | 35 +++++ 3 files changed, 185 insertions(+), 2 deletions(-) diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt index 180a30829..a33c34c8d 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt @@ -138,7 +138,7 @@ class ContentDslTest { messages { assistantImage { data = - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + "iVBORw0KGgoggg==" mimeType = "image/png" } } @@ -146,7 +146,7 @@ class ContentDslTest { (request.params.messages[0].content as ImageContent).shouldNotBeNull { data shouldBe - "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" + "iVBORw0KGgoggg==" mimeType shouldBe "image/png" annotations.shouldBeNull() meta.shouldBeNull() diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesResultDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesResultDslTest.kt index cd3e2af0e..30fc27852 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesResultDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesResultDslTest.kt @@ -2,6 +2,8 @@ package io.modelcontextprotocol.kotlin.sdk.types.dsl import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.Annotations @@ -11,6 +13,8 @@ import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents import io.modelcontextprotocol.kotlin.sdk.types.buildListResourceTemplatesResult import io.modelcontextprotocol.kotlin.sdk.types.buildListResourcesResult import io.modelcontextprotocol.kotlin.sdk.types.buildReadResourceResult +import kotlinx.serialization.json.boolean +import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlin.test.Test @@ -139,4 +143,148 @@ class ResourcesResultDslTest { buildReadResourceResult { } } } + + @Test + fun `ListResourcesResult should build full with all optional fields`() { + val result = buildListResourcesResult { + resource { + uri = "file:///docs/api.md" + name = "api-documentation" + description = "Complete API documentation" + mimeType = "text/markdown" + size = 4096 + title = "API Reference" + annotations = Annotations( + audience = listOf(Role.User, Role.Assistant), + priority = 0.8, + ) + } + nextCursor = "page-2" + meta { + put("totalResources", 150) + put("cached", true) + } + } + + result.resources shouldHaveSize 1 + result.resources[0].let { resource -> + resource.uri shouldBe "file:///docs/api.md" + resource.name shouldBe "api-documentation" + resource.description shouldBe "Complete API documentation" + resource.mimeType shouldBe "text/markdown" + resource.size shouldBe 4096 + resource.title shouldBe "API Reference" + resource.annotations shouldNotBeNull { + audience shouldBe listOf(Role.User, Role.Assistant) + priority shouldBe 0.8 + } + } + result.nextCursor shouldBe "page-2" + result.meta shouldNotBeNull {} + } + + @Test + fun `ReadResourceResult should build with multiple content items`() { + val result = buildReadResourceResult { + textContent( + uri = "file:///docs/part1.md", + text = "# Part 1\n\nIntroduction", + mimeType = "text/markdown", + ) + textContent( + uri = "file:///docs/part2.md", + text = "# Part 2\n\nDetails", + mimeType = "text/markdown", + ) + blobContent( + uri = "file:///images/diagram.png", + blob = "iVBORw0KGgoggg==", + mimeType = "image/png", + ) + } + + result.contents shouldHaveSize 3 + (result.contents[0] as TextResourceContents).uri shouldBe "file:///docs/part1.md" + (result.contents[1] as TextResourceContents).uri shouldBe "file:///docs/part2.md" + (result.contents[2] as BlobResourceContents).uri shouldBe "file:///images/diagram.png" + } + + @Test + fun `ReadResourceResult should support meta field`() { + val result = buildReadResourceResult { + textContent( + uri = "file:///data.json", + text = """{"key": "value"}""", + mimeType = "application/json", + ) + meta { + put("source", "database") + put("lastModified", 1707317000000L) + put("cached", false) + } + } + + result.meta shouldNotBeNull { + get("source")?.jsonPrimitive?.content shouldBe "database" + get("cached")?.jsonPrimitive?.boolean shouldBe false + } + } + + @Test + fun `ListResourceTemplatesResult should support pagination with nextCursor`() { + val result = buildListResourceTemplatesResult { + template { + uriTemplate = "db://users/{userId}" + name = "user-template" + } + template { + uriTemplate = "db://posts/{postId}" + name = "post-template" + } + nextCursor = "template-page-2" + } + + result.resourceTemplates shouldHaveSize 2 + result.nextCursor shouldBe "template-page-2" + } + + @Test + fun `ListResourceTemplatesResult should support meta field`() { + val result = buildListResourceTemplatesResult { + template { + uriTemplate = "api://{endpoint}" + name = "api-template" + } + meta { + put("version", "2.0") + put("totalTemplates", 25) + } + } + + result.meta shouldNotBeNull { + get("version")?.jsonPrimitive?.content shouldBe "2.0" + } + } + + @Test + fun `ListResourcesResult should support resources with minimal and full fields together`() { + val result = buildListResourcesResult { + resource { + uri = "simple://resource1" + name = "minimal" + } + resource { + uri = "complex://resource2" + name = "complete" + description = "Full resource" + mimeType = "application/json" + size = 2048 + title = "Complete Resource" + } + } + + result.resources shouldHaveSize 2 + result.resources[0].description.shouldBeNull() + result.resources[1].description shouldBe "Full resource" + } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsResultDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsResultDslTest.kt index 6463475ba..0218280a2 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsResultDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsResultDslTest.kt @@ -112,6 +112,41 @@ class ToolsResultDslTest { (result.content[0] as TextContent).text shouldBe "Failed to connect to database" } + @Test + fun `CallToolResult should support error with structuredContent`() { + val result = buildCallToolResult { + textContent("Operation failed: timeout exceeded") + isError = true + structuredContent { + put("errorCode", "TIMEOUT") + put("retryAfter", 5000) + put("message", "The operation timed out after 30 seconds") + } + } + + result.isError shouldBe true + result.content shouldHaveSize 1 + result.structuredContent shouldNotBeNull { + get("errorCode")?.jsonPrimitive?.content shouldBe "TIMEOUT" + get("retryAfter")?.jsonPrimitive?.int shouldBe 5000 + } + } + + @Test + fun `CallToolResult should support error with multiple content items`() { + val result = buildCallToolResult { + textContent("Error occurred during processing") + textContent("Stack trace: at line 42 in module.kt") + textContent("Please contact support if this persists") + isError = true + } + + result.isError shouldBe true + result.content shouldHaveSize 3 + (result.content[0] as TextContent).text shouldBe "Error occurred during processing" + (result.content[2] as TextContent).text shouldBe "Please contact support if this persists" + } + @Test fun `CallToolResult should throw if no content provided`() { shouldThrow { From 1da133676bbeca60d772a22757be636ff1fe26bd Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Tue, 17 Feb 2026 15:22:32 +0200 Subject: [PATCH 4/7] feat(dsl): add defensive copying and documentation for DSL builders - Added `.toList()` defensive copying in `build()` methods to ensure immutability of returned collections. - Enhanced documentation for CallToolResult, metadata, and field nullability to align with MCP serialization conventions. --- .../kotlin/sdk/types/result.dsl.kt | 20 ++++++++++--- .../kotlin/sdk/types/sampling.dsl.kt | 2 +- .../kotlin/sdk/types/tools.dsl.kt | 28 +++++++++++++++++-- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt index 55d3aae43..dc116c387 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt @@ -22,13 +22,18 @@ public abstract class ResultBuilder { /** * Sets result metadata directly from a JsonObject. * + * **Note:** Prefer using the DSL lambda variant [meta] for more idiomatic Kotlin code. + * This overload is provided for cases where you already have a constructed JsonObject. + * * Example: * ```kotlin - * meta(buildJsonObject { + * val existingMeta = buildJsonObject { * put("source", "server") - * put("timestamp", System.currentTimeMillis()) - * }) + * } + * meta(existingMeta) * ``` + * + * @see meta */ public fun meta(meta: JsonObject) { this.meta = meta @@ -37,10 +42,13 @@ public abstract class ResultBuilder { /** * Sets result metadata using a DSL builder. * + * **This is the preferred way to set metadata.** The DSL syntax is more idiomatic + * and integrates better with Kotlin's type-safe builders. + * * Metadata can include custom fields for tracking responses and providing * additional context to clients. * - * Example: + * Example (preferred): * ```kotlin * listToolsResult { * tools { /* ... */ } @@ -77,6 +85,10 @@ public abstract class PaginatedResultBuilder : ResultBuilder() { * If present, indicates there may be more results available. * Clients can pass this cursor in a subsequent paginated request. * + * **Design note:** This field is nullable to distinguish between "no next page" (`null`) + * and "next page exists" (non-null string). When `null`, the field is omitted from the + * serialized JSON, keeping the protocol efficient. + * * Example: `nextCursor = "eyJwYWdlIjogMn0="` */ public var nextCursor: String? = null 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 1bc00e281..c465373b4 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 @@ -262,7 +262,7 @@ public class SamplingMessageBuilder @PublishedApi internal constructor() { } @PublishedApi - internal fun build(): List = messages + internal fun build(): List = messages.toList() // Defensive copy } /** 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 e04787594..381d52fc3 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 @@ -242,6 +242,16 @@ public inline fun buildCallToolResult(block: CallToolResultBuilder.() -> Unit): * - [structuredContent] - Machine-readable structured output * - [meta] - Metadata for the response * + * ## Implementation Notes + * + * **Mutability:** This builder uses mutable collections during construction for efficient accumulation. + * The [build] method creates defensive copies (`.toList()`) to ensure the returned [CallToolResult] + * is immutable and safe to share. + * + * **Nullability:** Optional fields use nullable types (`Boolean?`, `JsonObject?`) rather than defaults + * to avoid serializing default values in the MCP protocol. When these fields are `null`, they are + * omitted from the JSON output, reducing message size and following protocol conventions. + * * @see buildCallToolResult * @see CallToolResult */ @@ -255,6 +265,10 @@ public class CallToolResultBuilder @PublishedApi internal constructor() : Result * When true, the content should describe the error that occurred. * If not set, this is assumed to be false (success). * + * **Design note:** This field is nullable rather than defaulting to `false` to avoid + * serializing the default value in the JSON protocol. When `null`, the field is omitted + * from the serialized output, reducing message size and following MCP protocol conventions. + * * Example: `isError = true` */ public var isError: Boolean? = null @@ -333,15 +347,20 @@ public class CallToolResultBuilder @PublishedApi internal constructor() : Result /** * Sets structured content directly from a JsonObject. * + * **Note:** Prefer using the DSL lambda variant [structuredContent] for more idiomatic Kotlin code. + * This overload is provided for cases where you already have a constructed JsonObject. + * * Example: * ```kotlin - * structuredContent(buildJsonObject { + * val existingData = buildJsonObject { * put("status", "success") * put("recordsAffected", 5) - * }) + * } + * structuredContent(existingData) * ``` * * @param content The structured content as a JsonObject + * @see structuredContent */ public fun structuredContent(content: JsonObject) { this.structuredContentValue = content @@ -350,9 +369,12 @@ public class CallToolResultBuilder @PublishedApi internal constructor() : Result /** * Sets structured content using a DSL builder. * + * **This is the preferred way to set structured content.** The DSL syntax is more idiomatic + * and integrates better with Kotlin's type-safe builders. + * * Provides machine-readable output in addition to the human-readable content. * - * Example: + * Example (preferred): * ```kotlin * structuredContent { * put("status", "success") From dc2c81fd5d7c204d8b963260c8ed59ebf6741118 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 18 Feb 2026 16:51:51 +0200 Subject: [PATCH 5/7] refactor(dsl)!: mark constructors in DSL builders as internal to restrict visibility --- kotlin-sdk-core/api/kotlin-sdk-core.api | 1 + .../io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt | 2 +- .../io/modelcontextprotocol/kotlin/sdk/types/prompts.dsl.kt | 4 ++-- .../io/modelcontextprotocol/kotlin/sdk/types/request.dsl.kt | 6 +++--- .../io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt | 4 ++-- .../io/modelcontextprotocol/kotlin/sdk/types/tools.kt | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index f3b09a0e7..aac2e6638 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -3675,6 +3675,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RequestMeta$Companio } public final class io/modelcontextprotocol/kotlin/sdk/types/RequestMetaBuilder { + public fun ()V public final fun progressToken (I)V public final fun progressToken (J)V public final fun progressToken (Ljava/lang/String;)V diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt index e538af572..b716b0472 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/content.dsl.kt @@ -21,7 +21,7 @@ import kotlinx.serialization.json.buildJsonObject * @see AudioContentBuilder */ @McpDsl -public abstract class MediaContentBuilder { +public abstract class MediaContentBuilder @PublishedApi internal constructor() { protected var annotations: Annotations? = null protected var meta: JsonObject? = null 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 ff4af8251..c65171fb4 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 @@ -155,7 +155,7 @@ public class ListPromptsRequestBuilder @PublishedApi internal constructor() : Pa * Creates a [GetPromptResult] using a type-safe DSL builder. * * ## Required - * - [messages][GetPromptResultBuilder.messages] - List of prompt messages (at least one) + * - [messages][GetPromptResultBuilder.messagesList] - List of prompt messages (at least one) * * ## Optional * - [description][GetPromptResultBuilder.description] - Description of the prompt @@ -260,7 +260,7 @@ public class GetPromptResultBuilder @PublishedApi internal constructor() : Resul * Creates a [ListPromptsResult] using a type-safe DSL builder. * * ## Required - * - [prompts][ListPromptsResultBuilder.prompts] - List of available prompts (at least one) + * - [prompts][ListPromptsResultBuilder.promptsList] - List of available prompts (at least one) * * ## Optional * - [nextCursor][ListPromptsResultBuilder.nextCursor] - Pagination cursor for next page diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.dsl.kt index 3e2a15041..fa8d8f49a 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/request.dsl.kt @@ -21,7 +21,7 @@ import kotlinx.serialization.json.buildJsonObject * @see PaginatedRequestBuilder */ @McpDsl -public abstract class RequestBuilder { +public abstract class RequestBuilder @PublishedApi internal constructor() { protected var meta: RequestMeta? = null /** @@ -73,7 +73,7 @@ public abstract class RequestBuilder { * @see RequestBuilder.meta */ @McpDsl -public class RequestMetaBuilder internal constructor() { +public class RequestMetaBuilder @PublishedApi internal constructor() { private val content: MutableMap = linkedMapOf() /** @@ -196,7 +196,7 @@ public class RequestMetaBuilder internal constructor() { * @see RequestBuilder */ @McpDsl -public abstract class PaginatedRequestBuilder : RequestBuilder() { +public abstract class PaginatedRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { /** * Optional pagination cursor for fetching the next page of results. * diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt index dc116c387..c619fe476 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/result.dsl.kt @@ -16,7 +16,7 @@ import kotlinx.serialization.json.buildJsonObject * @see ServerResult */ @McpDsl -public abstract class ResultBuilder { +public abstract class ResultBuilder @PublishedApi internal constructor() { protected var meta: JsonObject? = null /** @@ -78,7 +78,7 @@ public abstract class ResultBuilder { * @see PaginatedResult */ @McpDsl -public abstract class PaginatedResultBuilder : ResultBuilder() { +public abstract class PaginatedResultBuilder @PublishedApi internal constructor() : ResultBuilder() { /** * Optional pagination cursor for fetching the next page of results. * diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt index b7aade56f..79c956279 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tools.kt @@ -84,7 +84,7 @@ public data class Tool( public data class ToolSchema( val properties: JsonObject? = null, val required: List? = null, - @SerialName("\$defs") + @SerialName($$"$defs") val defs: JsonObject? = null, ) { @OptIn(ExperimentalSerializationApi::class) From 0f99c5a4c0d7048eb50e1af2facbb00627b73df6 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:59:20 +0200 Subject: [PATCH 6/7] refactor(dsl)!: replace `build*Request` patterns with `Companion.invoke` for cleaner DSL usage - Deprecated `build*Request` functions across DSLs in favor of the `Companion.invoke` operator. - Updated all documentation, examples, and tests to reflect the new syntax. - Improved clarity, reduced redundancy, and streamlined API usage. --- .../kotlin/AbstractToolIntegrationTest.kt | 6 +- .../sse/KotlinServerForTsClientSse.kt | 11 ++- kotlin-sdk-core/api/kotlin-sdk-core.api | 50 +++++------ .../kotlin/sdk/types/capabilities.dsl.kt | 4 +- .../kotlin/sdk/types/completion.dsl.kt | 25 ++---- .../kotlin/sdk/types/elicitation.dsl.kt | 19 ++-- .../kotlin/sdk/types/initialize.dsl.kt | 26 ++---- .../kotlin/sdk/types/logging.dsl.kt | 15 +--- .../kotlin/sdk/types/pingRequest.dsl.kt | 15 +--- .../kotlin/sdk/types/prompts.dsl.kt | 50 ++++------- .../kotlin/sdk/types/resources.dsl.kt | 88 +++++++------------ .../kotlin/sdk/types/roots.dsl.kt | 16 ++-- .../kotlin/sdk/types/sampling.dsl.kt | 17 ++-- .../kotlin/sdk/types/tools.dsl.kt | 54 ++++-------- .../sdk/types/dsl/CapabilitiesDslTest.kt | 15 ++-- .../kotlin/sdk/types/dsl/CompletionDslTest.kt | 11 +-- .../sdk/types/dsl/CompletionResultDslTest.kt | 25 +++--- .../kotlin/sdk/types/dsl/ContentDslTest.kt | 37 ++++---- .../sdk/types/dsl/ElicitationDslTest.kt | 15 ++-- .../kotlin/sdk/types/dsl/InitializeDslTest.kt | 17 ++-- .../sdk/types/dsl/InitializeResultDslTest.kt | 21 ++--- .../kotlin/sdk/types/dsl/LoggingDslTest.kt | 7 +- .../sdk/types/dsl/PingRequestDslTest.kt | 11 +-- .../kotlin/sdk/types/dsl/PromptsDslTest.kt | 13 +-- .../sdk/types/dsl/PromptsResultDslTest.kt | 21 ++--- .../kotlin/sdk/types/dsl/RequestDslTest.kt | 27 +++--- .../kotlin/sdk/types/dsl/ResourcesDslTest.kt | 31 +++---- .../sdk/types/dsl/ResourcesResultDslTest.kt | 33 +++---- .../kotlin/sdk/types/dsl/RootsDslTest.kt | 5 +- .../kotlin/sdk/types/dsl/SamplingDslTest.kt | 23 ++--- .../kotlin/sdk/types/dsl/ToolsDslTest.kt | 15 ++-- .../sdk/types/dsl/ToolsResultDslTest.kt | 27 +++--- .../StreamableHttpServerTransportTest.kt | 5 +- 33 files changed, 331 insertions(+), 424 deletions(-) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt index fb73c492d..8f2254074 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/kotlin/AbstractToolIntegrationTest.kt @@ -10,7 +10,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ImageContent import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema -import io.modelcontextprotocol.kotlin.sdk.types.buildCallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -87,7 +87,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { ) { request -> val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided" - buildCallToolResult { + CallToolResult { textContent("Echo: $text") structuredContent { put("result", text) @@ -115,7 +115,7 @@ abstract class AbstractToolIntegrationTest : KotlinTestBase() { ) { request -> val text = (request.params.arguments?.get("text") as? JsonPrimitive)?.content ?: "No text provided" - buildCallToolResult { + CallToolResult { textContent("Echo: $text") structuredContent { put("result", text) diff --git a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt index 123683788..96f3c5a7a 100644 --- a/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt +++ b/integration-test/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/typescript/sse/KotlinServerForTsClientSse.kt @@ -22,6 +22,7 @@ import io.modelcontextprotocol.kotlin.sdk.server.Server import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport import io.modelcontextprotocol.kotlin.sdk.shared.TransportSendOptions +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult import io.modelcontextprotocol.kotlin.sdk.types.Implementation import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCError @@ -40,7 +41,7 @@ import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema -import io.modelcontextprotocol.kotlin.sdk.types.buildCallToolResult +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.Channel @@ -175,9 +176,7 @@ class KotlinServerForTsClient { logger.info { "Terminating session: $sessionId" } val transport = serverTransports[sessionId]!! serverTransports.remove(sessionId) - runBlocking { - transport.close() - } + transport.close() call.respond(HttpStatusCode.OK) } else { logger.warn { "Invalid session termination request: $sessionId" } @@ -228,7 +227,7 @@ class KotlinServerForTsClient { ), ) { request -> val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World" - buildCallToolResult { + CallToolResult { textContent("Hello, $name!") structuredContent { put("greeting", JsonPrimitive("Hello, $name!")) @@ -254,7 +253,7 @@ class KotlinServerForTsClient { ) { request -> val name = (request.params.arguments?.get("name") as? JsonPrimitive)?.content ?: "World" - buildCallToolResult { + CallToolResult { textContent("Multiple greetings sent to $name!") structuredContent { put("greeting", JsonPrimitive("Multiple greetings sent to $name!")) diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index aac2e6638..90ec0f717 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -1255,8 +1255,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteResultBuilde } 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 static final fun buildCompleteResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult; } public abstract interface class io/modelcontextprotocol/kotlin/sdk/types/ContentBlock : io/modelcontextprotocol/kotlin/sdk/types/WithMeta { @@ -1649,7 +1649,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Compani } 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest$Companion;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 { @@ -2109,8 +2109,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeResultBuil } 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 static final fun buildInitializeResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult; } public final class io/modelcontextprotocol/kotlin/sdk/types/InitializedNotification : io/modelcontextprotocol/kotlin/sdk/types/ClientNotification { @@ -2790,7 +2790,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotifi } 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest$Companion;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 { @@ -3097,7 +3097,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequestBuilder : } 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest; } public final class io/modelcontextprotocol/kotlin/sdk/types/Progress { @@ -3372,10 +3372,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PromptReference$Comp } 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 buildGetPromptResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult; - public static final fun buildListPromptsRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; - public static final fun buildListPromptsResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsResult; } public final class io/modelcontextprotocol/kotlin/sdk/types/RPCError { @@ -4033,14 +4033,14 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotif } 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 buildListResourceTemplatesResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult; - public static final fun buildListResourcesRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; - public static final fun buildListResourcesResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult; - public static final fun buildReadResourceRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; - public static final fun buildReadResourceResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult; - 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; } public abstract class io/modelcontextprotocol/kotlin/sdk/types/ResultBuilder { @@ -4129,7 +4129,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNoti } 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; } public final class io/modelcontextprotocol/kotlin/sdk/types/SamplingMessage { @@ -4173,7 +4173,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 invoke (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest$Companion;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 @@ -4772,10 +4772,10 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolsKt { } 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 buildCallToolResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; - public static final fun buildListToolsRequest (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; - public static final fun buildListToolsResult (Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsResult; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsResult; } public final class io/modelcontextprotocol/kotlin/sdk/types/UnknownResourceContents : io/modelcontextprotocol/kotlin/sdk/types/ResourceContents { diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt index b132a5ed8..17f1c9a30 100644 --- a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt @@ -16,9 +16,9 @@ import kotlinx.serialization.json.buildJsonObject * - [elicitation] - Indicates support for elicitation from the server * - [experimental] - Defines experimental, non-standard capabilities * - * Example usage within [buildInitializeRequest][buildInitializeRequest]: + * Example usage within [InitializeRequest][InitializeRequest]: * ```kotlin - * val request = buildInitializeRequest { + * val request = InitializeRequest { * protocolVersion = "1.0" * capabilities { * sampling(ClientCapabilities.sampling) 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 e18681ad9..f620789bf 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 @@ -1,9 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** * Creates a [CompleteRequest] using a type-safe DSL builder. @@ -18,7 +15,7 @@ import kotlin.contracts.contract * * Example with [PromptReference]: * ```kotlin - * val request = buildCompleteRequest { + * val request = CompleteRequest { * argument("query", "user input") * ref(PromptReference("searchPrompt")) * } @@ -26,7 +23,7 @@ import kotlin.contracts.contract * * Example with [ResourceTemplateReference]: * ```kotlin - * val request = buildCompleteRequest { + * val request = CompleteRequest { * argument("path", "/users/123") * ref(ResourceTemplateReference("file:///{path}")) * context { @@ -40,12 +37,9 @@ import kotlin.contracts.contract * @return A configured [CompleteRequest] instance * @see CompleteRequestBuilder */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildCompleteRequest(block: CompleteRequestBuilder.() -> Unit): CompleteRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return CompleteRequestBuilder().apply(block).build() -} +public inline operator fun CompleteRequest.Companion.invoke(block: CompleteRequestBuilder.() -> Unit): CompleteRequest = + CompleteRequestBuilder().apply(block).build() /** * DSL builder for constructing [CompleteRequest] instances. @@ -53,7 +47,7 @@ public inline fun buildCompleteRequest(block: CompleteRequestBuilder.() -> Unit) * This builder provides methods to configure completion requests for prompts or resource templates. * Both [argument] and [ref] are required; [context] is optional. * - * @see buildCompleteRequest + * @see CompleteRequest */ @McpDsl public class CompleteRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { @@ -178,18 +172,15 @@ public class CompleteRequestBuilder @PublishedApi internal constructor() : Reque * * Example: * ```kotlin - * val result = buildCompleteResult { + * val result = CompleteResult { * values("user1", "user2", "user3") * total = 3 * } * ``` */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildCompleteResult(block: CompleteResultBuilder.() -> Unit): CompleteResult { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return CompleteResultBuilder().apply(block).build() -} +public inline operator fun CompleteResult.Companion.invoke(block: CompleteResultBuilder.() -> Unit): CompleteResult = + CompleteResultBuilder().apply(block).build() private const val MAX_ITEMS = 100 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 e398d98e9..be06de2c1 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 @@ -4,9 +4,6 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.buildJsonObject -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** * Creates an [ElicitRequest] using a type-safe DSL builder. @@ -20,7 +17,7 @@ import kotlin.contracts.contract * * Example requesting user information: * ```kotlin - * val request = buildElicitRequest { + * val request = ElicitRequest { * message = "Please provide your contact information" * requestedSchema { * properties { @@ -39,7 +36,7 @@ import kotlin.contracts.contract * * Example with simple text input: * ```kotlin - * val request = buildElicitRequest { + * val request = ElicitRequest { * message = "Enter a project name" * requestedSchema { * properties { @@ -57,12 +54,9 @@ import kotlin.contracts.contract * @see ElicitRequestBuilder * @see ElicitRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildElicitRequest(block: ElicitRequestBuilder.() -> Unit): ElicitRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ElicitRequestBuilder().apply(block).build() -} +public inline operator fun ElicitRequest.Companion.invoke(block: ElicitRequestBuilder.() -> Unit): ElicitRequest = + ElicitRequestBuilder().apply(block).build() /** * DSL builder for constructing [ElicitRequest] instances. @@ -77,7 +71,6 @@ public inline fun buildElicitRequest(block: ElicitRequestBuilder.() -> Unit): El * ## Optional * - [meta] - Metadata for the request * - * @see buildElicitRequest * @see ElicitRequest */ @McpDsl @@ -95,7 +88,7 @@ public class ElicitRequestBuilder @PublishedApi internal constructor() : Request * * Example: * ```kotlin - * buildElicitRequest { + * ElicitRequest { * message = "Enter details" * requestedSchema(ElicitRequestParams.RequestedSchema( * properties = buildJsonObject { @@ -119,7 +112,7 @@ public class ElicitRequestBuilder @PublishedApi internal constructor() : Request * * Example: * ```kotlin - * buildElicitRequest { + * ElicitRequest { * message = "Configure settings" * requestedSchema { * properties { 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 9ba66b8c9..cf06f771f 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 @@ -1,9 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** * Creates an [InitializeRequest] using a type-safe DSL builder. @@ -18,7 +15,7 @@ import kotlin.contracts.contract * * Example: * ```kotlin - * val request = buildInitializeRequest { + * val request = InitializeRequest { * protocolVersion = "2024-11-05" * capabilities { * sampling(ClientCapabilities.sampling) @@ -30,7 +27,7 @@ import kotlin.contracts.contract * * Example with full client info: * ```kotlin - * val request = buildInitializeRequest { + * val request = InitializeRequest { * protocolVersion = "2024-11-05" * capabilities { * sampling(ClientCapabilities.sampling) @@ -52,12 +49,10 @@ import kotlin.contracts.contract * @see InitializeRequestBuilder * @see InitializeRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildInitializeRequest(block: InitializeRequestBuilder.() -> Unit): InitializeRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return InitializeRequestBuilder().apply(block).build() -} +public inline operator fun InitializeRequest.Companion.invoke( + block: InitializeRequestBuilder.() -> Unit, +): InitializeRequest = InitializeRequestBuilder().apply(block).build() /** * DSL builder for constructing [InitializeRequest] instances. @@ -73,7 +68,6 @@ public inline fun buildInitializeRequest(block: InitializeRequestBuilder.() -> U * ## Optional * - [meta] - Metadata for the request * - * @see buildInitializeRequest * @see InitializeRequest */ @McpDsl @@ -214,7 +208,7 @@ public class InitializeRequestBuilder @PublishedApi internal constructor() : Req * * Example: * ```kotlin - * val result = buildInitializeResult { + * val result = InitializeResult { * protocolVersion = "2024-11-05" * capabilities { * prompts(listChanged = true) @@ -226,12 +220,10 @@ public class InitializeRequestBuilder @PublishedApi internal constructor() : Req * } * ``` */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildInitializeResult(block: InitializeResultBuilder.() -> Unit): InitializeResult { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return InitializeResultBuilder().apply(block).build() -} +public inline operator fun InitializeResult.Companion.invoke( + block: InitializeResultBuilder.() -> Unit, +): InitializeResult = InitializeResultBuilder().apply(block).build() /** * DSL builder for constructing [InitializeResult] instances. 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 b65846c76..3e1599ec9 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 @@ -1,9 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** * Creates a [SetLevelRequest] using a type-safe DSL builder. @@ -16,14 +13,14 @@ import kotlin.contracts.contract * * Example setting info level: * ```kotlin - * val request = buildSetLevelRequest { + * val request = SetLevelRequest { * loggingLevel = LoggingLevel.Info * } * ``` * * Example setting debug level: * ```kotlin - * val request = buildSetLevelRequest { + * val request = SetLevelRequest { * loggingLevel = LoggingLevel.Debug * } * ``` @@ -34,12 +31,9 @@ import kotlin.contracts.contract * @see SetLevelRequest * @see LoggingLevel */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildSetLevelRequest(block: SetLevelRequestBuilder.() -> Unit): SetLevelRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return SetLevelRequestBuilder().apply(block).build() -} +public inline operator fun SetLevelRequest.Companion.invoke(block: SetLevelRequestBuilder.() -> Unit): SetLevelRequest = + SetLevelRequestBuilder().apply(block).build() /** * DSL builder for constructing [SetLevelRequest] instances. @@ -52,7 +46,6 @@ public inline fun buildSetLevelRequest(block: SetLevelRequestBuilder.() -> Unit) * ## Optional * - [meta] - Metadata for the request * - * @see buildSetLevelRequest * @see SetLevelRequest * @see LoggingLevel */ 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 1898636c8..690dadcfe 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 @@ -1,9 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** * Creates a [PingRequest] using a type-safe DSL builder. @@ -13,12 +10,12 @@ import kotlin.contracts.contract * * Example with no parameters: * ```kotlin - * val request = buildPingRequest { } + * val request = PingRequest { } * ``` * * Example with metadata: * ```kotlin - * val request = buildPingRequest { + * val request = PingRequest { * meta { * put("timestamp", JsonPrimitive(System.currentTimeMillis())) * } @@ -30,12 +27,9 @@ import kotlin.contracts.contract * @see PingRequestBuilder * @see PingRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildPingRequest(block: PingRequestBuilder.() -> Unit): PingRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return PingRequestBuilder().apply(block).build() -} +public inline operator fun PingRequest.Companion.invoke(block: PingRequestBuilder.() -> Unit): PingRequest = + PingRequestBuilder().apply(block).build() /** * DSL builder for constructing [PingRequest] instances. @@ -46,7 +40,6 @@ public inline fun buildPingRequest(block: PingRequestBuilder.() -> Unit): PingRe * ## Optional * - [meta] - Metadata for the request * - * @see buildPingRequest * @see PingRequest */ @McpDsl 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 c65171fb4..3e89b44a7 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 @@ -4,9 +4,6 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.buildJsonObject -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** * Creates a [GetPromptRequest] using a type-safe DSL builder. @@ -20,14 +17,14 @@ import kotlin.contracts.contract * * Example without arguments: * ```kotlin - * val request = buildGetPromptRequest { + * val request = GetPromptRequest { * name = "greeting" * } * ``` * * Example with arguments: * ```kotlin - * val request = buildGetPromptRequest { + * val request = GetPromptRequest { * name = "userProfile" * arguments = mapOf( * "userId" to "123", @@ -41,12 +38,10 @@ import kotlin.contracts.contract * @see GetPromptRequestBuilder * @see GetPromptRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildGetPromptRequest(block: GetPromptRequestBuilder.() -> Unit): GetPromptRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return GetPromptRequestBuilder().apply(block).build() -} +public inline operator fun GetPromptRequest.Companion.invoke( + block: GetPromptRequestBuilder.() -> Unit, +): GetPromptRequest = GetPromptRequestBuilder().apply(block).build() /** * DSL builder for constructing [GetPromptRequest] instances. @@ -60,7 +55,6 @@ public inline fun buildGetPromptRequest(block: GetPromptRequestBuilder.() -> Uni * - [arguments] - Arguments to pass to the prompt * - [meta] - Metadata for the request * - * @see buildGetPromptRequest * @see GetPromptRequest */ @McpDsl @@ -102,12 +96,12 @@ public class GetPromptRequestBuilder @PublishedApi internal constructor() : Requ * * Example without pagination: * ```kotlin - * val request = buildListPromptsRequest { } + * val request = ListPromptsRequest { } * ``` * * Example with pagination: * ```kotlin - * val request = buildListPromptsRequest { + * val request = ListPromptsRequest { * cursor = "eyJwYWdlIjogMn0=" * } * ``` @@ -117,12 +111,10 @@ public class GetPromptRequestBuilder @PublishedApi internal constructor() : Requ * @see ListPromptsRequestBuilder * @see ListPromptsRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildListPromptsRequest(block: ListPromptsRequestBuilder.() -> Unit): ListPromptsRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ListPromptsRequestBuilder().apply(block).build() -} +public inline operator fun ListPromptsRequest.Companion.invoke( + block: ListPromptsRequestBuilder.() -> Unit, +): ListPromptsRequest = ListPromptsRequestBuilder().apply(block).build() /** * DSL builder for constructing [ListPromptsRequest] instances. @@ -134,7 +126,6 @@ public inline fun buildListPromptsRequest(block: ListPromptsRequestBuilder.() -> * - [cursor] - Pagination cursor (inherited from [PaginatedRequestBuilder]) * - [meta] - Metadata for the request (inherited from [RequestBuilder]) * - * @see buildListPromptsRequest * @see ListPromptsRequest * @see PaginatedRequestBuilder */ @@ -163,7 +154,7 @@ public class ListPromptsRequestBuilder @PublishedApi internal constructor() : Pa * * Example: * ```kotlin - * val result = buildGetPromptResult { + * val result = GetPromptResult { * description = "A greeting prompt" * message { * role = Role.User @@ -177,12 +168,9 @@ public class ListPromptsRequestBuilder @PublishedApi internal constructor() : Pa * @see GetPromptResultBuilder * @see GetPromptResult */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildGetPromptResult(block: GetPromptResultBuilder.() -> Unit): GetPromptResult { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return GetPromptResultBuilder().apply(block).build() -} +public inline operator fun GetPromptResult.Companion.invoke(block: GetPromptResultBuilder.() -> Unit): GetPromptResult = + GetPromptResultBuilder().apply(block).build() /** * DSL builder for constructing [GetPromptResult] instances. @@ -196,7 +184,6 @@ public inline fun buildGetPromptResult(block: GetPromptResultBuilder.() -> Unit) * - [description] - Description of the prompt * - [meta] - Metadata for the response * - * @see buildGetPromptResult * @see GetPromptResult */ @McpDsl @@ -268,7 +255,7 @@ public class GetPromptResultBuilder @PublishedApi internal constructor() : Resul * * Example: * ```kotlin - * val result = buildListPromptsResult { + * val result = ListPromptsResult { * prompt { * name = "greeting" * description = "A friendly greeting prompt" @@ -285,12 +272,10 @@ public class GetPromptResultBuilder @PublishedApi internal constructor() : Resul * @see ListPromptsResultBuilder * @see ListPromptsResult */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildListPromptsResult(block: ListPromptsResultBuilder.() -> Unit): ListPromptsResult { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ListPromptsResultBuilder().apply(block).build() -} +public inline operator fun ListPromptsResult.Companion.invoke( + block: ListPromptsResultBuilder.() -> Unit, +): ListPromptsResult = ListPromptsResultBuilder().apply(block).build() /** * DSL builder for constructing [ListPromptsResult] instances. @@ -305,7 +290,6 @@ public inline fun buildListPromptsResult(block: ListPromptsResultBuilder.() -> U * - [nextCursor] - Pagination cursor (inherited from [PaginatedResultBuilder]) * - [meta] - Metadata for the response (inherited from [ResultBuilder]) * - * @see buildListPromptsResult * @see ListPromptsResult * @see PaginatedResultBuilder */ 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 d9fd9a2df..e30e06c3f 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 @@ -4,9 +4,6 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.buildJsonObject -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** * Creates a [ListResourcesRequest] using a type-safe DSL builder. @@ -17,12 +14,12 @@ import kotlin.contracts.contract * * Example without pagination: * ```kotlin - * val request = buildListResourcesRequest { } + * val request = ListResourcesRequest { } * ``` * * Example with pagination: * ```kotlin - * val request = buildListResourcesRequest { + * val request = ListResourcesRequest { * cursor = "eyJwYWdlIjogMn0=" * } * ``` @@ -32,12 +29,10 @@ import kotlin.contracts.contract * @see ListResourcesRequestBuilder * @see ListResourcesRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildListResourcesRequest(block: ListResourcesRequestBuilder.() -> Unit): ListResourcesRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ListResourcesRequestBuilder().apply(block).build() -} +public inline operator fun ListResourcesRequest.Companion.invoke( + block: ListResourcesRequestBuilder.() -> Unit, +): ListResourcesRequest = ListResourcesRequestBuilder().apply(block).build() /** * DSL builder for constructing [ListResourcesRequest] instances. @@ -49,7 +44,6 @@ public inline fun buildListResourcesRequest(block: ListResourcesRequestBuilder.( * - [cursor] - Pagination cursor (inherited from [PaginatedRequestBuilder]) * - [meta] - Metadata for the request (inherited from [RequestBuilder]) * - * @see buildListResourcesRequest * @see ListResourcesRequest * @see PaginatedRequestBuilder */ @@ -73,7 +67,7 @@ public class ListResourcesRequestBuilder @PublishedApi internal constructor() : * * Example: * ```kotlin - * val request = buildReadResourceRequest { + * val request = ReadResourceRequest { * uri = "file:///path/to/resource.txt" * } * ``` @@ -83,12 +77,10 @@ public class ListResourcesRequestBuilder @PublishedApi internal constructor() : * @see ReadResourceRequestBuilder * @see ReadResourceRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildReadResourceRequest(block: ReadResourceRequestBuilder.() -> Unit): ReadResourceRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ReadResourceRequestBuilder().apply(block).build() -} +public inline operator fun ReadResourceRequest.Companion.invoke( + block: ReadResourceRequestBuilder.() -> Unit, +): ReadResourceRequest = ReadResourceRequestBuilder().apply(block).build() /** * DSL builder for constructing [ReadResourceRequest] instances. @@ -101,7 +93,6 @@ public inline fun buildReadResourceRequest(block: ReadResourceRequestBuilder.() * ## Optional * - [meta] - Metadata for the request * - * @see buildReadResourceRequest * @see ReadResourceRequest */ @McpDsl @@ -135,7 +126,7 @@ public class ReadResourceRequestBuilder @PublishedApi internal constructor() : R * * Example: * ```kotlin - * val request = buildSubscribeRequest { + * val request = SubscribeRequest { * uri = "file:///path/to/resource.txt" * } * ``` @@ -145,12 +136,10 @@ public class ReadResourceRequestBuilder @PublishedApi internal constructor() : R * @see SubscribeRequestBuilder * @see SubscribeRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildSubscribeRequest(block: SubscribeRequestBuilder.() -> Unit): SubscribeRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return SubscribeRequestBuilder().apply(block).build() -} +public inline operator fun SubscribeRequest.Companion.invoke( + block: SubscribeRequestBuilder.() -> Unit, +): SubscribeRequest = SubscribeRequestBuilder().apply(block).build() /** * DSL builder for constructing [SubscribeRequest] instances. @@ -163,7 +152,6 @@ public inline fun buildSubscribeRequest(block: SubscribeRequestBuilder.() -> Uni * ## Optional * - [meta] - Metadata for the request * - * @see buildSubscribeRequest * @see SubscribeRequest */ @McpDsl @@ -197,7 +185,7 @@ public class SubscribeRequestBuilder @PublishedApi internal constructor() : Requ * * Example: * ```kotlin - * val request = buildUnsubscribeRequest { + * val request = UnsubscribeRequest { * uri = "file:///path/to/resource.txt" * } * ``` @@ -207,12 +195,10 @@ public class SubscribeRequestBuilder @PublishedApi internal constructor() : Requ * @see UnsubscribeRequestBuilder * @see UnsubscribeRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildUnsubscribeRequest(block: UnsubscribeRequestBuilder.() -> Unit): UnsubscribeRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return UnsubscribeRequestBuilder().apply(block).build() -} +public inline operator fun UnsubscribeRequest.Companion.invoke( + block: UnsubscribeRequestBuilder.() -> Unit, +): UnsubscribeRequest = UnsubscribeRequestBuilder().apply(block).build() /** * DSL builder for constructing [UnsubscribeRequest] instances. @@ -225,7 +211,6 @@ public inline fun buildUnsubscribeRequest(block: UnsubscribeRequestBuilder.() -> * ## Optional * - [meta] - Metadata for the request * - * @see buildUnsubscribeRequest * @see UnsubscribeRequest */ @McpDsl @@ -257,12 +242,12 @@ public class UnsubscribeRequestBuilder @PublishedApi internal constructor() : Re * * Example without pagination: * ```kotlin - * val request = buildListResourceTemplatesRequest { } + * val request = ListResourceTemplatesRequest { } * ``` * * Example with pagination: * ```kotlin - * val request = buildListResourceTemplatesRequest { + * val request = ListResourceTemplatesRequest { * cursor = "eyJwYWdlIjogMn0=" * } * ``` @@ -272,14 +257,10 @@ public class UnsubscribeRequestBuilder @PublishedApi internal constructor() : Re * @see ListResourceTemplatesRequestBuilder * @see ListResourceTemplatesRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildListResourceTemplatesRequest( +public inline operator fun ListResourceTemplatesRequest.Companion.invoke( block: ListResourceTemplatesRequestBuilder.() -> Unit, -): ListResourceTemplatesRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ListResourceTemplatesRequestBuilder().apply(block).build() -} +): ListResourceTemplatesRequest = ListResourceTemplatesRequestBuilder().apply(block).build() /** * DSL builder for constructing [ListResourceTemplatesRequest] instances. @@ -291,7 +272,6 @@ public inline fun buildListResourceTemplatesRequest( * - [cursor] - Pagination cursor (inherited from [PaginatedRequestBuilder]) * - [meta] - Metadata for the request (inherited from [RequestBuilder]) * - * @see buildListResourceTemplatesRequest * @see ListResourceTemplatesRequest * @see PaginatedRequestBuilder */ @@ -313,7 +293,7 @@ public class ListResourceTemplatesRequestBuilder @PublishedApi internal construc * * Example: * ```kotlin - * val result = buildListResourcesResult { + * val result = ListResourcesResult { * resource { * uri = "file:///path/to/file.txt" * name = "file.txt" @@ -323,12 +303,10 @@ public class ListResourceTemplatesRequestBuilder @PublishedApi internal construc * } * ``` */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildListResourcesResult(block: ListResourcesResultBuilder.() -> Unit): ListResourcesResult { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ListResourcesResultBuilder().apply(block).build() -} +public inline operator fun ListResourcesResult.Companion.invoke( + block: ListResourcesResultBuilder.() -> Unit, +): ListResourcesResult = ListResourcesResultBuilder().apply(block).build() /** * DSL builder for constructing [ListResourcesResult] instances. @@ -404,12 +382,10 @@ public class ResourceBuilder @PublishedApi internal constructor() { /** * Creates a [ReadResourceResult] using a type-safe DSL builder. */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildReadResourceResult(block: ReadResourceResultBuilder.() -> Unit): ReadResourceResult { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ReadResourceResultBuilder().apply(block).build() -} +public inline operator fun ReadResourceResult.Companion.invoke( + block: ReadResourceResultBuilder.() -> Unit, +): ReadResourceResult = ReadResourceResultBuilder().apply(block).build() /** * DSL builder for constructing [ReadResourceResult] instances. @@ -446,14 +422,10 @@ public class ReadResourceResultBuilder @PublishedApi internal constructor() : Re /** * Creates a [ListResourceTemplatesResult] using a type-safe DSL builder. */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildListResourceTemplatesResult( +public inline operator fun ListResourceTemplatesResult.Companion.invoke( block: ListResourceTemplatesResultBuilder.() -> Unit, -): ListResourceTemplatesResult { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ListResourceTemplatesResultBuilder().apply(block).build() -} +): ListResourceTemplatesResult = ListResourceTemplatesResultBuilder().apply(block).build() /** * DSL builder for constructing [ListResourceTemplatesResult] instances. 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 c1af20138..dbc3e288f 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 @@ -1,9 +1,6 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** * Creates a [ListRootsRequest] using a type-safe DSL builder. @@ -13,12 +10,12 @@ import kotlin.contracts.contract * * Example with no parameters: * ```kotlin - * val request = buildListRootsRequest { } + * val request = ListRootsRequest { } * ``` * * Example with metadata: * ```kotlin - * val request = buildListRootsRequest { + * val request = ListRootsRequest { * meta { * put("context", "initialization") * } @@ -30,12 +27,10 @@ import kotlin.contracts.contract * @see ListRootsRequestBuilder * @see ListRootsRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildListRootsRequest(block: ListRootsRequestBuilder.() -> Unit): ListRootsRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ListRootsRequestBuilder().apply(block).build() -} +public inline operator fun ListRootsRequest.Companion.invoke( + block: ListRootsRequestBuilder.() -> Unit, +): ListRootsRequest = ListRootsRequestBuilder().apply(block).build() /** * DSL builder for constructing [ListRootsRequest] instances. @@ -46,7 +41,6 @@ public inline fun buildListRootsRequest(block: ListRootsRequestBuilder.() -> Uni * ## Optional * - [meta] - Metadata for the request * - * @see buildListRootsRequest * @see ListRootsRequest */ @McpDsl 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 c465373b4..b45ef825e 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 @@ -4,9 +4,6 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.buildJsonObject -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** * Creates a [CreateMessageRequest] using a type-safe DSL builder. @@ -26,7 +23,7 @@ import kotlin.contracts.contract * * Example: * ```kotlin - * val request = buildCreateMessageRequest { + * val request = CreateMessageRequest { * maxTokens = 1000 * systemPrompt = "You are a helpful assistant" * messages { @@ -39,7 +36,7 @@ import kotlin.contracts.contract * * Example with preferences: * ```kotlin - * val request = buildCreateMessageRequest { + * val request = CreateMessageRequest { * maxTokens = 500 * temperature = 0.7 * preferences( @@ -57,12 +54,10 @@ import kotlin.contracts.contract * @see CreateMessageRequestBuilder * @see CreateMessageRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildCreateMessageRequest(block: CreateMessageRequestBuilder.() -> Unit): CreateMessageRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return CreateMessageRequestBuilder().apply(block).build() -} +public inline operator fun CreateMessageRequest.Companion.invoke( + block: CreateMessageRequestBuilder.() -> Unit, +): CreateMessageRequest = CreateMessageRequestBuilder().apply(block).build() /** * DSL builder for constructing [CreateMessageRequest] instances. @@ -82,7 +77,7 @@ public inline fun buildCreateMessageRequest(block: CreateMessageRequestBuilder.( * - [metadata] - Additional metadata * - [meta] - Request metadata * - * @see buildCreateMessageRequest + * @see CreateMessageRequest * @see CreateMessageRequest */ @McpDsl 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 381d52fc3..a972caa60 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 @@ -4,9 +4,6 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.buildJsonObject -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract /** * Creates a [CallToolRequest] using a type-safe DSL builder. @@ -20,14 +17,14 @@ import kotlin.contracts.contract * * Example without arguments: * ```kotlin - * val request = buildCallToolRequest { + * val request = CallToolRequest { * name = "getCurrentTime" * } * ``` * * Example with arguments: * ```kotlin - * val request = buildCallToolRequest { + * val request = CallToolRequest { * name = "searchDatabase" * arguments { * put("query", "users") @@ -41,12 +38,9 @@ import kotlin.contracts.contract * @see CallToolRequestBuilder * @see CallToolRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildCallToolRequest(block: CallToolRequestBuilder.() -> Unit): CallToolRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return CallToolRequestBuilder().apply(block).build() -} +public inline operator fun CallToolRequest.Companion.invoke(block: CallToolRequestBuilder.() -> Unit): CallToolRequest = + CallToolRequestBuilder().apply(block).build() /** * DSL builder for constructing [CallToolRequest] instances. @@ -60,7 +54,6 @@ public inline fun buildCallToolRequest(block: CallToolRequestBuilder.() -> Unit) * - [arguments] - Arguments to pass to the tool * - [meta] - Metadata for the request * - * @see buildCallToolRequest * @see CallToolRequest */ @McpDsl @@ -126,12 +119,12 @@ public class CallToolRequestBuilder @PublishedApi internal constructor() : Reque * * Example without pagination: * ```kotlin - * val request = buildListToolsRequest { } + * val request = ListToolsRequest { } * ``` * * Example with pagination: * ```kotlin - * val request = buildListToolsRequest { + * val request = ListToolsRequest { * cursor = "eyJwYWdlIjogMn0=" * } * ``` @@ -141,12 +134,10 @@ public class CallToolRequestBuilder @PublishedApi internal constructor() : Reque * @see ListToolsRequestBuilder * @see ListToolsRequest */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildListToolsRequest(block: ListToolsRequestBuilder.() -> Unit): ListToolsRequest { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ListToolsRequestBuilder().apply(block).build() -} +public inline operator fun ListToolsRequest.Companion.invoke( + block: ListToolsRequestBuilder.() -> Unit, +): ListToolsRequest = ListToolsRequestBuilder().apply(block).build() /** * DSL builder for constructing [ListToolsRequest] instances. @@ -158,7 +149,6 @@ public inline fun buildListToolsRequest(block: ListToolsRequestBuilder.() -> Uni * - [cursor] - Pagination cursor (inherited from [PaginatedRequestBuilder]) * - [meta] - Metadata for the request (inherited from [RequestBuilder]) * - * @see buildListToolsRequest * @see ListToolsRequest * @see PaginatedRequestBuilder */ @@ -188,14 +178,14 @@ public class ListToolsRequestBuilder @PublishedApi internal constructor() : Pagi * * Example success response: * ```kotlin - * val result = buildCallToolResult { + * val result = CallToolResult { * textContent("Operation completed successfully") * } * ``` * * Example error response: * ```kotlin - * val result = buildCallToolResult { + * val result = CallToolResult { * textContent("Failed to connect to database") * isError = true * } @@ -203,7 +193,7 @@ public class ListToolsRequestBuilder @PublishedApi internal constructor() : Pagi * * Example with structured content: * ```kotlin - * val result = buildCallToolResult { + * val result = CallToolResult { * textContent("Query returned 3 results") * structuredContent { * put("count", 3) @@ -221,12 +211,9 @@ public class ListToolsRequestBuilder @PublishedApi internal constructor() : Pagi * @see CallToolResultBuilder * @see CallToolResult */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildCallToolResult(block: CallToolResultBuilder.() -> Unit): CallToolResult { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return CallToolResultBuilder().apply(block).build() -} +public inline operator fun CallToolResult.Companion.invoke(block: CallToolResultBuilder.() -> Unit): CallToolResult = + CallToolResultBuilder().apply(block).build() /** * DSL builder for constructing [CallToolResult] instances. @@ -252,7 +239,6 @@ public inline fun buildCallToolResult(block: CallToolResultBuilder.() -> Unit): * to avoid serializing default values in the MCP protocol. When these fields are `null`, they are * omitted from the JSON output, reducing message size and following protocol conventions. * - * @see buildCallToolResult * @see CallToolResult */ @McpDsl @@ -419,7 +405,7 @@ public class CallToolResultBuilder @PublishedApi internal constructor() : Result * * Example with single tool: * ```kotlin - * val result = buildListToolsResult { + * val result = ListToolsResult { * tool { * name = "searchDatabase" * description = "Search the database for records" @@ -441,7 +427,7 @@ public class CallToolResultBuilder @PublishedApi internal constructor() : Result * * Example with pagination: * ```kotlin - * val result = buildListToolsResult { + * val result = ListToolsResult { * tool(Tool("tool1", ToolSchema())) * tool(Tool("tool2", ToolSchema())) * nextCursor = "eyJwYWdlIjogMn0=" @@ -453,12 +439,9 @@ public class CallToolResultBuilder @PublishedApi internal constructor() : Result * @see ListToolsResultBuilder * @see ListToolsResult */ -@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline fun buildListToolsResult(block: ListToolsResultBuilder.() -> Unit): ListToolsResult { - contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } - return ListToolsResultBuilder().apply(block).build() -} +public inline operator fun ListToolsResult.Companion.invoke(block: ListToolsResultBuilder.() -> Unit): ListToolsResult = + ListToolsResultBuilder().apply(block).build() /** * DSL builder for constructing [ListToolsResult] instances. @@ -473,7 +456,6 @@ public inline fun buildListToolsResult(block: ListToolsResultBuilder.() -> Unit) * - [nextCursor] - Pagination cursor (inherited from [PaginatedResultBuilder]) * - [meta] - Metadata for the response (inherited from [ResultBuilder]) * - * @see buildListToolsResult * @see ListToolsResult * @see PaginatedResultBuilder */ diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt index e4a949fde..2e926c766 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CapabilitiesDslTest.kt @@ -4,7 +4,8 @@ import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi -import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.double import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put @@ -20,7 +21,7 @@ import kotlin.test.Test class CapabilitiesDslTest { @Test fun `capabilities should build minimal empty capabilities`() { - val request = buildInitializeRequest { + val request = InitializeRequest { protocolVersion = "2024-11-05" capabilities { } info("Test", "1.0") @@ -36,7 +37,7 @@ class CapabilitiesDslTest { @Test fun `capabilities should build full with all fields and nested properties`() { - val request = buildInitializeRequest { + val request = InitializeRequest { protocolVersion = "2024-11-05" capabilities { sampling { @@ -86,7 +87,7 @@ class CapabilitiesDslTest { @Test fun `capabilities should support roots variants`() { // Test listChanged = true - val requestTrue = buildInitializeRequest { + val requestTrue = InitializeRequest { protocolVersion = "2024-11-05" capabilities { roots(listChanged = true) } info("Test", "1.0") @@ -94,7 +95,7 @@ class CapabilitiesDslTest { requestTrue.params.capabilities.roots?.listChanged shouldBe true // Test listChanged = false - val requestFalse = buildInitializeRequest { + val requestFalse = InitializeRequest { protocolVersion = "2024-11-05" capabilities { roots(listChanged = false) } info("Test", "1.0") @@ -102,7 +103,7 @@ class CapabilitiesDslTest { requestFalse.params.capabilities.roots?.listChanged shouldBe false // Test listChanged = null (not provided) - val requestNull = buildInitializeRequest { + val requestNull = InitializeRequest { protocolVersion = "2024-11-05" capabilities { roots() } info("Test", "1.0") @@ -112,7 +113,7 @@ class CapabilitiesDslTest { @Test fun `capabilities should overwrite when same field set multiple times`() { - val request = buildInitializeRequest { + val request = InitializeRequest { protocolVersion = "2024-11-05" capabilities { sampling { put("temperature", 0.5) } 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 index cbe40c9a7..f4067ce92 100644 --- 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 @@ -4,16 +4,17 @@ 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.CompleteRequest import io.modelcontextprotocol.kotlin.sdk.types.PromptReference import io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplateReference -import io.modelcontextprotocol.kotlin.sdk.types.buildCompleteRequest +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class CompletionDslTest { @Test fun `buildCompleteRequest should build with prompt reference and context`() { - val request = buildCompleteRequest { + val request = CompleteRequest { argument("query", "user input") ref(PromptReference("searchPrompt")) context { @@ -31,7 +32,7 @@ class CompletionDslTest { @Test fun `buildCompleteRequest should build with resource template reference and map context`() { - val request = buildCompleteRequest { + val request = CompleteRequest { argument("path", "/users/123") ref(ResourceTemplateReference("file:///{path}")) context(mapOf("role" to "admin")) @@ -48,7 +49,7 @@ class CompletionDslTest { @Test fun `buildCompleteRequest should throw if argument is missing`() { shouldThrow { - buildCompleteRequest { + CompleteRequest { ref(PromptReference("name")) } } @@ -57,7 +58,7 @@ class CompletionDslTest { @Test fun `buildCompleteRequest should throw if ref is missing`() { shouldThrow { - buildCompleteRequest { + CompleteRequest { argument("name", "value") } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionResultDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionResultDslTest.kt index eef86b600..03ff8187e 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionResultDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/CompletionResultDslTest.kt @@ -6,7 +6,8 @@ import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi -import io.modelcontextprotocol.kotlin.sdk.types.buildCompleteResult +import io.modelcontextprotocol.kotlin.sdk.types.CompleteResult +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.boolean import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonPrimitive @@ -24,7 +25,7 @@ class CompletionResultDslTest { @Test fun `CompleteResult should build minimal with values only`() { - val result = buildCompleteResult { + val result = CompleteResult { values("user1", "user2", "user3") } @@ -37,7 +38,7 @@ class CompletionResultDslTest { @Test fun `CompleteResult should build full with all fields`() { - val result = buildCompleteResult { + val result = CompleteResult { values(listOf("admin", "moderator", "user", "guest")) total = 42 hasMore = true @@ -60,7 +61,7 @@ class CompletionResultDslTest { @Test fun `CompleteResult should support values via vararg`() { - val result = buildCompleteResult { + val result = CompleteResult { values("a", "b", "c", "d", "e") } @@ -71,7 +72,7 @@ class CompletionResultDslTest { fun `CompleteResult should support values via list`() { val completions = listOf("option1", "option2", "option3") - val result = buildCompleteResult { + val result = CompleteResult { values(completions) } @@ -81,7 +82,7 @@ class CompletionResultDslTest { @Test fun `CompleteResult should throw if no values provided`() { shouldThrow { - buildCompleteResult { } + CompleteResult { } } } @@ -90,7 +91,7 @@ class CompletionResultDslTest { val tooManyValues = (1..101).map { "value$it" } shouldThrow { - buildCompleteResult { + CompleteResult { values(tooManyValues) } } @@ -100,7 +101,7 @@ class CompletionResultDslTest { fun `CompleteResult should support exactly 100 values`() { val maxValues = (1..100).map { "value$it" } - val result = buildCompleteResult { + val result = CompleteResult { values(maxValues) } @@ -109,7 +110,7 @@ class CompletionResultDslTest { @Test fun `CompleteResult should support empty strings in values`() { - val result = buildCompleteResult { + val result = CompleteResult { values("", "non-empty", "") } @@ -118,7 +119,7 @@ class CompletionResultDslTest { @Test fun `CompleteResult should support unicode in values`() { - val result = buildCompleteResult { + val result = CompleteResult { values("Hello 🌍", "Ça va?", "北京", "مرحبا") } @@ -128,7 +129,7 @@ class CompletionResultDslTest { @Test fun `CompleteResult should support total without hasMore`() { - val result = buildCompleteResult { + val result = CompleteResult { values("a", "b", "c") total = 10 } @@ -139,7 +140,7 @@ class CompletionResultDslTest { @Test fun `CompleteResult should support hasMore without total`() { - val result = buildCompleteResult { + val result = CompleteResult { values("a", "b", "c") hasMore = true } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt index a33c34c8d..39c7ca125 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ContentDslTest.kt @@ -7,12 +7,13 @@ 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.CreateMessageRequest import io.modelcontextprotocol.kotlin.sdk.types.ImageContent import io.modelcontextprotocol.kotlin.sdk.types.Role import io.modelcontextprotocol.kotlin.sdk.types.TextContent 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.invoke import io.modelcontextprotocol.kotlin.sdk.types.userText import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put @@ -33,7 +34,7 @@ class ContentDslTest { @Test fun `userText should build minimal with text only`() { - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 100 messages { userText { @@ -51,7 +52,7 @@ class ContentDslTest { @Test fun `userText should build full with all fields and nested meta`() { - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 100 messages { userText { @@ -89,7 +90,7 @@ class ContentDslTest { @Test fun `userText should handle unicode and special characters`() { - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 100 messages { userText { @@ -103,7 +104,7 @@ class ContentDslTest { @Test fun `userText should handle empty string`() { - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 100 messages { userText { @@ -118,7 +119,7 @@ class ContentDslTest { @Test fun `userText should throw if text is missing`() { shouldThrow { - buildCreateMessageRequest { + CreateMessageRequest { maxTokens = 100 messages { userText { } @@ -133,7 +134,7 @@ class ContentDslTest { @Test fun `assistantImage should build minimal with data and mimeType`() { - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 100 messages { assistantImage { @@ -155,7 +156,7 @@ class ContentDslTest { @Test fun `assistantImage should build full with all fields and image metadata`() { - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 100 messages { assistantImage { @@ -199,7 +200,7 @@ class ContentDslTest { val mimeTypes = listOf("image/png", "image/jpeg", "image/webp", "image/gif", "image/svg+xml") mimeTypes.forEach { mime -> - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 100 messages { assistantImage { @@ -215,7 +216,7 @@ class ContentDslTest { @Test fun `assistantImage should throw if data is missing`() { shouldThrow { - buildCreateMessageRequest { + CreateMessageRequest { maxTokens = 100 messages { assistantImage { @@ -229,7 +230,7 @@ class ContentDslTest { @Test fun `assistantImage should throw if mimeType is missing`() { shouldThrow { - buildCreateMessageRequest { + CreateMessageRequest { maxTokens = 100 messages { assistantImage { @@ -246,7 +247,7 @@ class ContentDslTest { @Test fun `assistantAudio should build minimal with data and mimeType`() { - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 100 messages { assistantAudio { @@ -266,7 +267,7 @@ class ContentDslTest { @Test fun `assistantAudio should build full with all fields and audio metadata`() { - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 100 messages { assistantAudio { @@ -308,7 +309,7 @@ class ContentDslTest { @Test fun `assistantAudio should throw if data is missing`() { shouldThrow { - buildCreateMessageRequest { + CreateMessageRequest { maxTokens = 100 messages { assistantAudio { @@ -322,7 +323,7 @@ class ContentDslTest { @Test fun `assistantAudio should throw if mimeType is missing`() { shouldThrow { - buildCreateMessageRequest { + CreateMessageRequest { maxTokens = 100 messages { assistantAudio { @@ -340,7 +341,7 @@ class ContentDslTest { @Test fun `annotations should support boundary priority values`() { // Priority = 0.0 (minimum) - val requestMin = buildCreateMessageRequest { + val requestMin = CreateMessageRequest { maxTokens = 100 messages { userText { @@ -352,7 +353,7 @@ class ContentDslTest { (requestMin.params.messages[0].content as TextContent).annotations?.priority shouldBe 0.0 // Priority = 1.0 (maximum) - val requestMax = buildCreateMessageRequest { + val requestMax = CreateMessageRequest { maxTokens = 100 messages { userText { @@ -366,7 +367,7 @@ class ContentDslTest { @Test fun `annotations should support direct object construction`() { - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 100 messages { userText { 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 index e49e4d2ba..8544696a5 100644 --- 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 @@ -3,8 +3,9 @@ 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.ElicitRequest import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams -import io.modelcontextprotocol.kotlin.sdk.types.buildElicitRequest +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import kotlin.test.Test @@ -13,7 +14,7 @@ import kotlin.test.Test class ElicitationDslTest { @Test fun `buildElicitRequest should build with all fields`() { - val request = buildElicitRequest { + val request = ElicitRequest { message = "Provide info" requestedSchema { properties { @@ -34,7 +35,7 @@ class ElicitationDslTest { properties = buildJsonObject { put("name", buildJsonObject { put("type", "string") }) }, required = listOf("name"), ) - val request = buildElicitRequest { + val request = ElicitRequest { message = "Test" requestedSchema(schema) } @@ -45,7 +46,7 @@ class ElicitationDslTest { @Test fun `ElicitRequestedSchemaBuilder should support direct properties assignment`() { val props = buildJsonObject { put("key", "value") } - val request = buildElicitRequest { + val request = ElicitRequest { message = "Test" requestedSchema { properties(props) @@ -57,7 +58,7 @@ class ElicitationDslTest { @Test fun `buildElicitRequest should throw if message is missing`() { shouldThrow { - buildElicitRequest { + ElicitRequest { requestedSchema { properties { put("a", 1) } } } } @@ -66,7 +67,7 @@ class ElicitationDslTest { @Test fun `buildElicitRequest should throw if requestedSchema is missing`() { shouldThrow { - buildElicitRequest { + ElicitRequest { message = "Test" } } @@ -75,7 +76,7 @@ class ElicitationDslTest { @Test fun `ElicitRequestedSchemaBuilder should throw if properties are missing`() { shouldThrow { - buildElicitRequest { + ElicitRequest { 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 index cb3511571..589a3259f 100644 --- 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 @@ -6,7 +6,8 @@ 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 io.modelcontextprotocol.kotlin.sdk.types.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonPrimitive @@ -17,7 +18,7 @@ import kotlin.test.Test class InitializeDslTest { @Test fun `buildInitializeRequest should create request with all fields`() { - val request = buildInitializeRequest { + val request = InitializeRequest { protocolVersion = "2024-11-05" capabilities { sampling { @@ -59,7 +60,7 @@ class InitializeDslTest { val capabilities = ClientCapabilities(roots = ClientCapabilities.Roots(listChanged = false)) val info = Implementation(name = "Direct", version = "0.1") - val request = buildInitializeRequest { + val request = InitializeRequest { protocolVersion = "1.0" capabilities(capabilities) info(info) @@ -75,7 +76,7 @@ class InitializeDslTest { val elicitationObj = buildJsonObject { put("key", "value") } val experimentalObj = buildJsonObject { put("key", "value") } - val request = buildInitializeRequest { + val request = InitializeRequest { protocolVersion = "1.0" capabilities { sampling(samplingObj) @@ -94,7 +95,7 @@ class InitializeDslTest { @Test fun `ClientCapabilitiesBuilder roots should support default arguments`() { - val request = buildInitializeRequest { + val request = InitializeRequest { protocolVersion = "1.0" capabilities { roots() @@ -109,7 +110,7 @@ class InitializeDslTest { @Test fun `buildInitializeRequest should throw if protocolVersion is missing`() { shouldThrow { - buildInitializeRequest { + InitializeRequest { capabilities { } info("Test", "1.0") } @@ -119,7 +120,7 @@ class InitializeDslTest { @Test fun `buildInitializeRequest should throw if capabilities are missing`() { shouldThrow { - buildInitializeRequest { + InitializeRequest { protocolVersion = "1.0" info("Test", "1.0") } @@ -129,7 +130,7 @@ class InitializeDslTest { @Test fun `buildInitializeRequest should throw if info is missing`() { shouldThrow { - buildInitializeRequest { + InitializeRequest { protocolVersion = "1.0" capabilities { } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeResultDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeResultDslTest.kt index 877cc582d..9dc795b78 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeResultDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/InitializeResultDslTest.kt @@ -6,9 +6,10 @@ import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject +import io.modelcontextprotocol.kotlin.sdk.types.InitializeResult import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities -import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeResult +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlin.test.Test @@ -24,7 +25,7 @@ class InitializeResultDslTest { @Test fun `InitializeResult should build minimal with default protocol version`() { - val result = buildInitializeResult { + val result = InitializeResult { capabilities(ServerCapabilities()) info("MyServer", "1.0.0") } @@ -41,7 +42,7 @@ class InitializeResultDslTest { @Test fun `InitializeResult should build full with all fields`() { - val result = buildInitializeResult { + val result = InitializeResult { protocolVersion = "2024-11-05" capabilities( @@ -104,7 +105,7 @@ class InitializeResultDslTest { @Test fun `InitializeResult should support partial capabilities`() { - val result = buildInitializeResult { + val result = InitializeResult { capabilities( ServerCapabilities( tools = ServerCapabilities.Tools(listChanged = true), @@ -125,7 +126,7 @@ class InitializeResultDslTest { @Test fun `InitializeResult should support capabilities with false flags`() { - val result = buildInitializeResult { + val result = InitializeResult { capabilities( ServerCapabilities( tools = ServerCapabilities.Tools(listChanged = false), @@ -149,7 +150,7 @@ class InitializeResultDslTest { @Test fun `InitializeResult should throw if capabilities missing`() { shouldThrow { - buildInitializeResult { + InitializeResult { info("Server", "1.0") } } @@ -158,7 +159,7 @@ class InitializeResultDslTest { @Test fun `InitializeResult should throw if info missing`() { shouldThrow { - buildInitializeResult { + InitializeResult { capabilities(ServerCapabilities()) } } @@ -168,7 +169,7 @@ class InitializeResultDslTest { fun `InitializeResult should support long instructions`() { val longInstructions = "This is a very long instruction text. ".repeat(100) - val result = buildInitializeResult { + val result = InitializeResult { capabilities(ServerCapabilities()) info("Server", "1.0") instructions = longInstructions @@ -179,7 +180,7 @@ class InitializeResultDslTest { @Test fun `InitializeResult should support custom protocol versions`() { - val result = buildInitializeResult { + val result = InitializeResult { protocolVersion = "2025-01-01" capabilities(ServerCapabilities()) info("FutureServer", "3.0") @@ -190,7 +191,7 @@ class InitializeResultDslTest { @Test fun `InitializeResult should support unicode in instructions`() { - val result = buildInitializeResult { + val result = InitializeResult { capabilities(ServerCapabilities()) info("Server", "1.0") instructions = "サーバーの使い方: 🚀 Start here! Ça va?" 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 index 5b09b4256..cf57e9bb7 100644 --- 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 @@ -4,14 +4,15 @@ 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 io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class LoggingDslTest { @Test fun `buildSetLevelRequest should create request with given level`() { - val request = buildSetLevelRequest { + val request = SetLevelRequest { loggingLevel = LoggingLevel.Info } @@ -21,7 +22,7 @@ class LoggingDslTest { @Test fun `buildSetLevelRequest should throw if loggingLevel is missing`() { shouldThrow { - buildSetLevelRequest { } + SetLevelRequest { } } } } 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 index 79cef068f..eddc19ccb 100644 --- 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 @@ -3,7 +3,8 @@ 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 io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.add import kotlinx.serialization.json.boolean import kotlinx.serialization.json.int @@ -15,7 +16,7 @@ import kotlin.test.Test class PingRequestDslTest { @Test fun `buildPingRequest should create request with meta containing all field types`() { - val request = buildPingRequest { + val request = PingRequest { meta { progressToken("token-123") put("string", "value") @@ -46,14 +47,14 @@ class PingRequestDslTest { @Test fun `RequestMeta DSL should support numeric progress tokens`() { - val requestInt = buildPingRequest { + val requestInt = PingRequest { meta { progressToken(123) } } requestInt.params?.meta?.json ?.get("progressToken") ?.jsonPrimitive?.int shouldBe 123 - val requestLong = buildPingRequest { + val requestLong = PingRequest { meta { progressToken(456L) } } requestLong.params?.meta?.json @@ -63,7 +64,7 @@ class PingRequestDslTest { @Test fun `buildPingRequest should create request without params if meta is empty`() { - val request = buildPingRequest { } + val request = PingRequest { } 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 index 937933ed1..7df208d03 100644 --- 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 @@ -4,15 +4,16 @@ 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 io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class PromptsDslTest { @Test fun `buildGetPromptRequest should create request with name and arguments`() { - val request = buildGetPromptRequest { + val request = GetPromptRequest { name = "test-prompt" arguments = mapOf("key" to "value") } @@ -23,7 +24,7 @@ class PromptsDslTest { @Test fun `buildListPromptsRequest should create request with cursor`() { - val request = buildListPromptsRequest { + val request = ListPromptsRequest { cursor = "next-page" } @@ -35,13 +36,13 @@ class PromptsDslTest { @Test fun `buildGetPromptRequest should throw if name is missing`() { shouldThrow { - buildGetPromptRequest { } + GetPromptRequest { } } } @Test fun `buildListPromptsRequest should create request without params if empty`() { - val request = buildListPromptsRequest { } + val request = ListPromptsRequest { } request.params shouldBe null } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsResultDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsResultDslTest.kt index 6a674e890..9673ba219 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsResultDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/PromptsResultDslTest.kt @@ -6,11 +6,12 @@ import io.kotest.matchers.nulls.shouldBeNull import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsResult import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument import io.modelcontextprotocol.kotlin.sdk.types.Role import io.modelcontextprotocol.kotlin.sdk.types.TextContent -import io.modelcontextprotocol.kotlin.sdk.types.buildGetPromptResult -import io.modelcontextprotocol.kotlin.sdk.types.buildListPromptsResult +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put import kotlin.test.Test @@ -30,7 +31,7 @@ class PromptsResultDslTest { @Test fun `GetPromptResult should build minimal with single message`() { - val result = buildGetPromptResult { + val result = GetPromptResult { message(Role.User, TextContent("Hello, how can I help you?")) } @@ -43,7 +44,7 @@ class PromptsResultDslTest { @Test fun `GetPromptResult should build full with multiple messages and description`() { - val result = buildGetPromptResult { + val result = GetPromptResult { description = "A customer service greeting prompt with context" message(Role.User, TextContent("You are a helpful customer service assistant.")) @@ -74,7 +75,7 @@ class PromptsResultDslTest { @Test fun `GetPromptResult should throw if no messages provided`() { shouldThrow { - buildGetPromptResult { } + GetPromptResult { } } } @@ -84,7 +85,7 @@ class PromptsResultDslTest { @Test fun `ListPromptsResult should build minimal with single prompt`() { - val result = buildListPromptsResult { + val result = ListPromptsResult { prompt { name = "greeting" } @@ -98,7 +99,7 @@ class PromptsResultDslTest { @Test fun `ListPromptsResult should build full with multiple prompts and pagination`() { - val result = buildListPromptsResult { + val result = ListPromptsResult { prompt { name = "greeting" description = "A friendly greeting prompt" @@ -164,13 +165,13 @@ class PromptsResultDslTest { @Test fun `ListPromptsResult should throw if no prompts provided`() { shouldThrow { - buildListPromptsResult { } + ListPromptsResult { } } } @Test fun `ListPromptsResult should support prompt without arguments`() { - val result = buildListPromptsResult { + val result = ListPromptsResult { prompt { name = "simplePrompt" description = "A prompt with no arguments" @@ -182,7 +183,7 @@ class PromptsResultDslTest { @Test fun `ListPromptsResult should support empty arguments list`() { - val result = buildListPromptsResult { + val result = ListPromptsResult { prompt { name = "emptyArgs" arguments = emptyList() diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RequestDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RequestDslTest.kt index ad9c71917..d0001922a 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RequestDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/RequestDslTest.kt @@ -6,9 +6,10 @@ import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.RequestId -import io.modelcontextprotocol.kotlin.sdk.types.buildListToolsRequest +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.add import kotlinx.serialization.json.boolean @@ -30,7 +31,7 @@ import kotlin.test.Test class RequestDslTest { @Test fun `request should build minimal without any params`() { - val request = buildListToolsRequest { } + val request = ListToolsRequest { } request.params.shouldBeNull() request.method shouldBe Method.Defined.ToolsList @@ -38,7 +39,7 @@ class RequestDslTest { @Test fun `request should build full with cursor and meta containing all field types`() { - val request = buildListToolsRequest { + val request = ListToolsRequest { cursor = "next-page-eyJvZmZzZXQiOjEwMH0" meta { // ProgressToken @@ -125,7 +126,7 @@ class RequestDslTest { @Test fun `meta progressToken should support String Int and Long types`() { // String progressToken - val stringRequest = buildListToolsRequest { + val stringRequest = ListToolsRequest { meta { progressToken("token-abc") } } stringRequest.params?.meta?.progressToken shouldNotBeNull { @@ -134,7 +135,7 @@ class RequestDslTest { } // Int progressToken - val intRequest = buildListToolsRequest { + val intRequest = ListToolsRequest { meta { progressToken(42) } } intRequest.params?.meta?.progressToken shouldNotBeNull { @@ -143,7 +144,7 @@ class RequestDslTest { } // Long progressToken - val longRequest = buildListToolsRequest { + val longRequest = ListToolsRequest { meta { progressToken(999L) } } longRequest.params?.meta?.progressToken shouldNotBeNull { @@ -154,7 +155,7 @@ class RequestDslTest { @Test fun `meta should support custom fields without progressToken`() { - val request = buildListToolsRequest { + val request = ListToolsRequest { meta { put("requestId", "req-123") put("source", "cli") @@ -173,7 +174,7 @@ class RequestDslTest { @Test fun `cursor should work without meta`() { - val request = buildListToolsRequest { + val request = ListToolsRequest { cursor = "page-2-cursor" } @@ -185,7 +186,7 @@ class RequestDslTest { @Test fun `cursor should handle empty string`() { - val request = buildListToolsRequest { + val request = ListToolsRequest { cursor = "" } @@ -196,7 +197,7 @@ class RequestDslTest { @Test fun `meta should handle special characters in keys`() { - val request = buildListToolsRequest { + val request = ListToolsRequest { meta { put("key-with-dashes", "value1") put("key.with.dots", "value2") @@ -215,7 +216,7 @@ class RequestDslTest { @Test fun `meta should overwrite when progressToken set multiple times`() { - val request = buildListToolsRequest { + val request = ListToolsRequest { meta { progressToken("first") progressToken(123) // Different type @@ -231,7 +232,7 @@ class RequestDslTest { @Test fun `meta should overwrite when custom field set multiple times`() { - val request = buildListToolsRequest { + val request = ListToolsRequest { meta { put("key", "first") put("key", 123) // Different type @@ -247,7 +248,7 @@ class RequestDslTest { @Test fun `meta should handle very long cursor strings`() { val longCursor = "x".repeat(1000) - val request = buildListToolsRequest { + val request = ListToolsRequest { cursor = longCursor } 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 index 13df6703f..9af88d82a 100644 --- 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 @@ -4,18 +4,19 @@ 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 io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest +import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class ResourcesDslTest { @Test fun `buildListResourcesRequest should create request with cursor`() { - val request = buildListResourcesRequest { + val request = ListResourcesRequest { cursor = "next" } request.params shouldNotBeNull { @@ -25,7 +26,7 @@ class ResourcesDslTest { @Test fun `buildReadResourceRequest should create request with uri`() { - val request = buildReadResourceRequest { + val request = ReadResourceRequest { uri = "test://resource" } request.params.uri shouldBe "test://resource" @@ -33,7 +34,7 @@ class ResourcesDslTest { @Test fun `buildSubscribeRequest should create request with uri`() { - val request = buildSubscribeRequest { + val request = SubscribeRequest { uri = "test://resource" } request.params.uri shouldBe "test://resource" @@ -41,7 +42,7 @@ class ResourcesDslTest { @Test fun `buildUnsubscribeRequest should create request with uri`() { - val request = buildUnsubscribeRequest { + val request = UnsubscribeRequest { uri = "test://resource" } request.params.uri shouldBe "test://resource" @@ -49,7 +50,7 @@ class ResourcesDslTest { @Test fun `buildListResourceTemplatesRequest should create request with cursor`() { - val request = buildListResourceTemplatesRequest { + val request = ListResourceTemplatesRequest { cursor = "template-cursor" } request.params shouldNotBeNull { @@ -60,33 +61,33 @@ class ResourcesDslTest { @Test fun `buildReadResourceRequest should throw if uri is missing`() { shouldThrow { - buildReadResourceRequest { } + ReadResourceRequest { } } } @Test fun `buildSubscribeRequest should throw if uri is missing`() { shouldThrow { - buildSubscribeRequest { } + SubscribeRequest { } } } @Test fun `buildUnsubscribeRequest should throw if uri is missing`() { shouldThrow { - buildUnsubscribeRequest { } + UnsubscribeRequest { } } } @Test fun `buildListResourcesRequest should create request without params if empty`() { - val request = buildListResourcesRequest { } + val request = ListResourcesRequest { } request.params shouldBe null } @Test fun `buildListResourceTemplatesRequest should create request without params if empty`() { - val request = buildListResourceTemplatesRequest { } + val request = ListResourceTemplatesRequest { } request.params shouldBe null } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesResultDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesResultDslTest.kt index 30fc27852..a42a3e70b 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesResultDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ResourcesResultDslTest.kt @@ -8,11 +8,12 @@ import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.Annotations import io.modelcontextprotocol.kotlin.sdk.types.BlobResourceContents +import io.modelcontextprotocol.kotlin.sdk.types.ListResourceTemplatesResult +import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult +import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceResult import io.modelcontextprotocol.kotlin.sdk.types.Role import io.modelcontextprotocol.kotlin.sdk.types.TextResourceContents -import io.modelcontextprotocol.kotlin.sdk.types.buildListResourceTemplatesResult -import io.modelcontextprotocol.kotlin.sdk.types.buildListResourcesResult -import io.modelcontextprotocol.kotlin.sdk.types.buildReadResourceResult +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.boolean import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.put @@ -28,7 +29,7 @@ class ResourcesResultDslTest { @Test fun `ListResourcesResult should build minimal with single resource`() { - val result = buildListResourcesResult { + val result = ListResourcesResult { resource { uri = "file:///path/to/file.txt" name = "file.txt" @@ -42,7 +43,7 @@ class ResourcesResultDslTest { @Test fun `ListResourcesResult should build full with multiple resources`() { - val result = buildListResourcesResult { + val result = ListResourcesResult { resource { uri = "file:///docs/readme.md" name = "readme.md" @@ -75,7 +76,7 @@ class ResourcesResultDslTest { @Test fun `ReadResourceResult should build with text content`() { - val result = buildReadResourceResult { + val result = ReadResourceResult { textContent( uri = "file:///docs/readme.md", text = "# Project README\n\nWelcome!", @@ -93,7 +94,7 @@ class ResourcesResultDslTest { @Test fun `ReadResourceResult should build with blob content`() { - val result = buildReadResourceResult { + val result = ReadResourceResult { blobContent( uri = "file:///images/logo.png", blob = "iVBORw0KGgoggg==", @@ -110,7 +111,7 @@ class ResourcesResultDslTest { @Test fun `ListResourceTemplatesResult should build with templates`() { - val result = buildListResourceTemplatesResult { + val result = ListResourceTemplatesResult { template { uriTemplate = "file:///{path}" name = "file-template" @@ -133,20 +134,20 @@ class ResourcesResultDslTest { @Test fun `ListResourcesResult should throw if no resources`() { shouldThrow { - buildListResourcesResult { } + ListResourcesResult { } } } @Test fun `ReadResourceResult should throw if no contents`() { shouldThrow { - buildReadResourceResult { } + ReadResourceResult { } } } @Test fun `ListResourcesResult should build full with all optional fields`() { - val result = buildListResourcesResult { + val result = ListResourcesResult { resource { uri = "file:///docs/api.md" name = "api-documentation" @@ -185,7 +186,7 @@ class ResourcesResultDslTest { @Test fun `ReadResourceResult should build with multiple content items`() { - val result = buildReadResourceResult { + val result = ReadResourceResult { textContent( uri = "file:///docs/part1.md", text = "# Part 1\n\nIntroduction", @@ -211,7 +212,7 @@ class ResourcesResultDslTest { @Test fun `ReadResourceResult should support meta field`() { - val result = buildReadResourceResult { + val result = ReadResourceResult { textContent( uri = "file:///data.json", text = """{"key": "value"}""", @@ -232,7 +233,7 @@ class ResourcesResultDslTest { @Test fun `ListResourceTemplatesResult should support pagination with nextCursor`() { - val result = buildListResourceTemplatesResult { + val result = ListResourceTemplatesResult { template { uriTemplate = "db://users/{userId}" name = "user-template" @@ -250,7 +251,7 @@ class ResourcesResultDslTest { @Test fun `ListResourceTemplatesResult should support meta field`() { - val result = buildListResourceTemplatesResult { + val result = ListResourceTemplatesResult { template { uriTemplate = "api://{endpoint}" name = "api-template" @@ -268,7 +269,7 @@ class ResourcesResultDslTest { @Test fun `ListResourcesResult should support resources with minimal and full fields together`() { - val result = buildListResourcesResult { + val result = ListResourcesResult { resource { uri = "simple://resource1" name = "minimal" 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 index 037649ff8..972c84534 100644 --- 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 @@ -2,14 +2,15 @@ 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 io.modelcontextprotocol.kotlin.sdk.types.ListRootsRequest +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class RootsDslTest { @Test fun `buildListRootsRequest should create request with meta`() { - val request = buildListRootsRequest { + val request = ListRootsRequest { meta { put("test", "value") } 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 index c53a4008c..a970f08bb 100644 --- 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 @@ -7,6 +7,7 @@ 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.CreateMessageRequest import io.modelcontextprotocol.kotlin.sdk.types.ImageContent import io.modelcontextprotocol.kotlin.sdk.types.IncludeContext import io.modelcontextprotocol.kotlin.sdk.types.ModelPreferences @@ -16,7 +17,7 @@ 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.invoke import io.modelcontextprotocol.kotlin.sdk.types.user import io.modelcontextprotocol.kotlin.sdk.types.userAudio import io.modelcontextprotocol.kotlin.sdk.types.userImage @@ -30,7 +31,7 @@ class SamplingDslTest { @Test @Suppress("LongMethod") fun `buildCreateMessageRequest should build with all fields`() { - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 1000 systemPrompt = "System prompt" temperature = 0.5 @@ -104,7 +105,7 @@ class SamplingDslTest { fun `buildCreateMessageRequest should support direct assignments`() { val messages = listOf(SamplingMessage(Role.User, TextContent("Hello"))) val preferences = ModelPreferences(costPriority = 0.1) - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 100 messages(messages) preferences(preferences) @@ -119,7 +120,7 @@ class SamplingDslTest { @Test fun `SamplingMessageBuilder should support direct content assignment`() { val content = TextContent("Direct") - val request = buildCreateMessageRequest { + val request = CreateMessageRequest { maxTokens = 100 messages { user(content) @@ -133,7 +134,7 @@ class SamplingDslTest { @Test fun `buildCreateMessageRequest should throw if maxTokens is missing`() { shouldThrow { - buildCreateMessageRequest { + CreateMessageRequest { messages { user { "Hi" } } } } @@ -142,7 +143,7 @@ class SamplingDslTest { @Test fun `buildCreateMessageRequest should throw if messages are missing`() { shouldThrow { - buildCreateMessageRequest { + CreateMessageRequest { maxTokens = 100 } } @@ -151,7 +152,7 @@ class SamplingDslTest { @Test fun `TextContentBuilder should throw if text is missing`() { shouldThrow { - buildCreateMessageRequest { + CreateMessageRequest { maxTokens = 100 messages { userText { } @@ -163,7 +164,7 @@ class SamplingDslTest { @Test fun `ImageContentBuilder should throw if data or mimeType is missing`() { shouldThrow { - buildCreateMessageRequest { + CreateMessageRequest { maxTokens = 100 messages { userImage { data = "abc" } @@ -171,7 +172,7 @@ class SamplingDslTest { } } shouldThrow { - buildCreateMessageRequest { + CreateMessageRequest { maxTokens = 100 messages { userImage { mimeType = "image/png" } @@ -183,7 +184,7 @@ class SamplingDslTest { @Test fun `AudioContentBuilder should throw if data or mimeType is missing`() { shouldThrow { - buildCreateMessageRequest { + CreateMessageRequest { maxTokens = 100 messages { userAudio { data = "abc" } @@ -191,7 +192,7 @@ class SamplingDslTest { } } shouldThrow { - buildCreateMessageRequest { + CreateMessageRequest { 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 index 957f0d2fb..346d310be 100644 --- 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 @@ -4,8 +4,9 @@ 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 io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int import kotlinx.serialization.json.jsonPrimitive @@ -16,7 +17,7 @@ import kotlin.test.Test class ToolsDslTest { @Test fun `buildCallToolRequest should build with name and arguments`() { - val request = buildCallToolRequest { + val request = CallToolRequest { name = "test-tool" arguments { put("key", "value") @@ -33,7 +34,7 @@ class ToolsDslTest { @Test fun `buildListToolsRequest should build with cursor`() { - val request = buildListToolsRequest { + val request = ListToolsRequest { cursor = "tool-cursor" } @@ -45,7 +46,7 @@ class ToolsDslTest { @Test fun `buildCallToolRequest should support direct arguments assignment`() { val args = buildJsonObject { put("key", "value") } - val request = buildCallToolRequest { + val request = CallToolRequest { name = "test-tool" arguments(args) } @@ -55,13 +56,13 @@ class ToolsDslTest { @Test fun `buildCallToolRequest should throw if name is missing`() { shouldThrow { - buildCallToolRequest { } + CallToolRequest { } } } @Test fun `buildListToolsRequest should build without params if empty`() { - val request = buildListToolsRequest { } + val request = ListToolsRequest { } request.params shouldBe null } } diff --git a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsResultDslTest.kt b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsResultDslTest.kt index 0218280a2..cb3e09c6e 100644 --- a/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsResultDslTest.kt +++ b/kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/dsl/ToolsResultDslTest.kt @@ -9,11 +9,12 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.AudioContent +import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult import io.modelcontextprotocol.kotlin.sdk.types.ImageContent +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult import io.modelcontextprotocol.kotlin.sdk.types.TextContent import io.modelcontextprotocol.kotlin.sdk.types.ToolAnnotations -import io.modelcontextprotocol.kotlin.sdk.types.buildCallToolResult -import io.modelcontextprotocol.kotlin.sdk.types.buildListToolsResult +import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.add import kotlinx.serialization.json.boolean import kotlinx.serialization.json.buildJsonObject @@ -40,7 +41,7 @@ class ToolsResultDslTest { @Test fun `CallToolResult should build minimal with single text content`() { - val result = buildCallToolResult { + val result = CallToolResult { textContent("Operation successful") } @@ -53,7 +54,7 @@ class ToolsResultDslTest { @Test fun `CallToolResult should build full with all content types and structured data`() { - val result = buildCallToolResult { + val result = CallToolResult { textContent("Database query completed") @Suppress("MaxLineLength") imageContent( @@ -103,7 +104,7 @@ class ToolsResultDslTest { @Test fun `CallToolResult should support error status`() { - val result = buildCallToolResult { + val result = CallToolResult { textContent("Failed to connect to database") isError = true } @@ -114,7 +115,7 @@ class ToolsResultDslTest { @Test fun `CallToolResult should support error with structuredContent`() { - val result = buildCallToolResult { + val result = CallToolResult { textContent("Operation failed: timeout exceeded") isError = true structuredContent { @@ -134,7 +135,7 @@ class ToolsResultDslTest { @Test fun `CallToolResult should support error with multiple content items`() { - val result = buildCallToolResult { + val result = CallToolResult { textContent("Error occurred during processing") textContent("Stack trace: at line 42 in module.kt") textContent("Please contact support if this persists") @@ -150,7 +151,7 @@ class ToolsResultDslTest { @Test fun `CallToolResult should throw if no content provided`() { shouldThrow { - buildCallToolResult { } + CallToolResult { } } } @@ -160,7 +161,7 @@ class ToolsResultDslTest { @Test fun `ListToolsResult should build minimal with single tool`() { - val result = buildListToolsResult { + val result = ListToolsResult { tool { name = "getCurrentTime" inputSchema { @@ -178,7 +179,7 @@ class ToolsResultDslTest { @Test @Suppress("LongMethod") fun `ListToolsResult should build full with multiple tools and pagination`() { - val result = buildListToolsResult { + val result = ListToolsResult { tool { name = "searchDatabase" description = "Search the database for records" @@ -270,7 +271,7 @@ class ToolsResultDslTest { @Test fun `ListToolsResult should support tool with defs in schema`() { - val result = buildListToolsResult { + val result = ListToolsResult { tool { name = "createUser" inputSchema { @@ -304,13 +305,13 @@ class ToolsResultDslTest { @Test fun `ListToolsResult should throw if no tools provided`() { shouldThrow { - buildListToolsResult { } + ListToolsResult { } } } @Test fun `ListToolsResult should support empty input schema`() { - val result = buildListToolsResult { + val result = ListToolsResult { tool { name = "noArgs" inputSchema { diff --git a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt index dc825c886..e1a3d13ea 100644 --- a/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt +++ b/kotlin-sdk-server/src/jvmTest/kotlin/io/modelcontextprotocol/kotlin/sdk/server/StreamableHttpServerTransportTest.kt @@ -33,10 +33,11 @@ import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCRequest import io.modelcontextprotocol.kotlin.sdk.types.JSONRPCResponse import io.modelcontextprotocol.kotlin.sdk.types.LATEST_PROTOCOL_VERSION import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesResult +import io.modelcontextprotocol.kotlin.sdk.types.ListToolsResult import io.modelcontextprotocol.kotlin.sdk.types.McpJson import io.modelcontextprotocol.kotlin.sdk.types.Method import io.modelcontextprotocol.kotlin.sdk.types.RequestId -import io.modelcontextprotocol.kotlin.sdk.types.buildListToolsResult +import io.modelcontextprotocol.kotlin.sdk.types.invoke import io.modelcontextprotocol.kotlin.sdk.types.toJSON import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.buildJsonObject @@ -161,7 +162,7 @@ class StreamableHttpServerTransportTest { val firstRequest = JSONRPCRequest(id = RequestId("first"), method = Method.Defined.ToolsList.value) val secondRequest = JSONRPCRequest(id = RequestId("second"), method = Method.Defined.ResourcesList.value) - val firstResult = buildListToolsResult { + val firstResult = ListToolsResult { tool { name = "tool-1" inputSchema { } From 5b39d04a7ee461420303c1989ba2595bc956e7a1 Mon Sep 17 00:00:00 2001 From: Konstantin Pavlov <1517853+kpavlov@users.noreply.github.com> Date: Thu, 19 Feb 2026 08:58:31 +0200 Subject: [PATCH 7/7] refactor(dsl)!: deprecate `build*Request` functions in favor of `Companion.invoke` and update tests - Added deprecation annotations to `build*Request` methods across all relevant DSLs with migration guidance. - Updated associated test cases and documentation to use the new `Companion.invoke` syntax. - Extract subscription DSLs from resources.dsl.kt into subscriptions.dsl.kt --- kotlin-sdk-core/api/kotlin-sdk-core.api | 23 +- .../kotlin/sdk/types/completion.dsl.kt | 49 +++++ .../kotlin/sdk/types/elicitation.dsl.kt | 65 ++++++ .../kotlin/sdk/types/initialize.dsl.kt | 62 ++++++ .../kotlin/sdk/types/logging.dsl.kt | 44 ++++ .../kotlin/sdk/types/pingRequest.dsl.kt | 40 ++++ .../kotlin/sdk/types/prompts.dsl.kt | 83 +++++++ .../kotlin/sdk/types/resources.dsl.kt | 202 +++++++++--------- .../kotlin/sdk/types/roots.dsl.kt | 40 ++++ .../kotlin/sdk/types/sampling.dsl.kt | 64 ++++++ .../kotlin/sdk/types/subscriptions.dsl.kt | 190 ++++++++++++++++ .../kotlin/sdk/types/tools.dsl.kt | 84 ++++++++ .../kotlin/sdk/types/dsl/CompletionDslTest.kt | 59 ++++- .../sdk/types/dsl/ElicitationDslTest.kt | 78 ++++++- .../kotlin/sdk/types/dsl/InitializeDslTest.kt | 135 +++++++++++- .../kotlin/sdk/types/dsl/LoggingDslTest.kt | 20 +- .../sdk/types/dsl/PingRequestDslTest.kt | 60 +++++- .../kotlin/sdk/types/dsl/PromptsDslTest.kt | 44 +++- .../kotlin/sdk/types/dsl/ResourcesDslTest.kt | 101 ++++++++- .../kotlin/sdk/types/dsl/RootsDslTest.kt | 11 + .../kotlin/sdk/types/dsl/SamplingDslTest.kt | 189 +++++++++++++++- .../kotlin/sdk/types/dsl/ToolsDslTest.kt | 62 +++++- 22 files changed, 1560 insertions(+), 145 deletions(-) create mode 100644 kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/subscriptions.dsl.kt diff --git a/kotlin-sdk-core/api/kotlin-sdk-core.api b/kotlin-sdk-core/api/kotlin-sdk-core.api index 90ec0f717..141783aae 100644 --- a/kotlin-sdk-core/api/kotlin-sdk-core.api +++ b/kotlin-sdk-core/api/kotlin-sdk-core.api @@ -1255,6 +1255,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CompleteResultBuilde } 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteRequest; public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CompleteResult; } @@ -1649,6 +1650,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ElicitResult$Compani } 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ElicitRequest; } @@ -2109,6 +2111,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/InitializeResultBuil } 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeRequest; public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/InitializeResult; } @@ -2790,6 +2793,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/LoggingMessageNotifi } 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/SetLevelRequest; } @@ -3097,6 +3101,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PingRequestBuilder : } 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/PingRequest; } @@ -3372,6 +3377,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/PromptReference$Comp } 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptRequest; public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/GetPromptResult; public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListPromptsRequest; @@ -4033,14 +4040,15 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ResourceUpdatedNotif } 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 invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesRequest; public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourceTemplatesResult; public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesRequest; public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListResourcesResult; public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceRequest; public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ReadResourceResult; - public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; - public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; } public abstract class io/modelcontextprotocol/kotlin/sdk/types/ResultBuilder { @@ -4129,6 +4137,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/RootsListChangedNoti } 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListRootsRequest; } @@ -4173,6 +4182,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 invoke (Lio/modelcontextprotocol/kotlin/sdk/types/CreateMessageRequest$Companion;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 @@ -4511,6 +4521,13 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/SubscribeRequestPara public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class io/modelcontextprotocol/kotlin/sdk/types/Subscriptions_dslKt { + 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/SubscribeRequest; + public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/UnsubscribeRequest; +} + public final class io/modelcontextprotocol/kotlin/sdk/types/TextContent : io/modelcontextprotocol/kotlin/sdk/types/MediaContent { public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/TextContent$Companion; public fun (Ljava/lang/String;Lio/modelcontextprotocol/kotlin/sdk/types/Annotations;Lkotlinx/serialization/json/JsonObject;)V @@ -4772,6 +4789,8 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ToolsKt { } 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 static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolRequest; public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/CallToolResult; public static final fun invoke (Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest$Companion;Lkotlin/jvm/functions/Function1;)Lio/modelcontextprotocol/kotlin/sdk/types/ListToolsRequest; 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 f620789bf..9668b5350 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 @@ -1,6 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * Creates a [CompleteRequest] using a type-safe DSL builder. @@ -41,6 +44,52 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi public inline operator fun CompleteRequest.Companion.invoke(block: CompleteRequestBuilder.() -> Unit): CompleteRequest = CompleteRequestBuilder().apply(block).build() +/** + * Creates a [CompleteRequest] using a type-safe DSL builder. + * + * ## Required + * - [argument][CompleteRequestBuilder.argument] - Sets the argument name and value to complete + * - [ref][CompleteRequestBuilder.ref] - Sets the reference to a prompt or resource template + * + * ## Optional + * - [context][CompleteRequestBuilder.context] - Adds additional context for the completion + * - [meta][CompleteRequestBuilder.meta] - Adds metadata to the request + * + * Example with [PromptReference]: + * ```kotlin + * val request = buildCompleteRequest { + * argument("query", "user input") + * ref(PromptReference("searchPrompt")) + * } + * ``` + * + * Example with [ResourceTemplateReference]: + * ```kotlin + * val request = buildCompleteRequest { + * argument("path", "/users/123") + * ref(ResourceTemplateReference("file:///{path}")) + * context { + * put("userId", "123") + * put("role", "admin") + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the completion request + * @return A configured [CompleteRequest] instance + * @see CompleteRequestBuilder + */ +@OptIn(ExperimentalContracts::class) +@Deprecated( + message = "Use CompleteRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("CompleteRequest{apply(block)}"), +) +public inline fun buildCompleteRequest(block: CompleteRequestBuilder.() -> Unit): CompleteRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return CompleteRequestBuilder().apply(block).build() +} + /** * DSL builder for constructing [CompleteRequest] instances. * 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 be06de2c1..9132d8342 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 @@ -4,6 +4,9 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.buildJsonObject +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * Creates an [ElicitRequest] using a type-safe DSL builder. @@ -58,6 +61,68 @@ import kotlinx.serialization.json.buildJsonObject public inline operator fun ElicitRequest.Companion.invoke(block: ElicitRequestBuilder.() -> Unit): ElicitRequest = ElicitRequestBuilder().apply(block).build() +/** + * Creates an [ElicitRequest] using a type-safe DSL builder. + * + * ## Required + * - [message][ElicitRequestBuilder.message] - The message to present to the user + * - [requestedSchema][ElicitRequestBuilder.requestedSchema] - Schema defining the structure of requested data + * + * ## Optional + * - [meta][ElicitRequestBuilder.meta] - Metadata for the request + * + * Example requesting user information: + * ```kotlin + * val request = buildElicitRequest { + * message = "Please provide your contact information" + * requestedSchema { + * properties { + * put("email", JsonObject(mapOf( + * "type" to JsonPrimitive("string"), + * "description" to JsonPrimitive("Your email address") + * ))) + * put("name", JsonObject(mapOf( + * "type" to JsonPrimitive("string") + * ))) + * } + * required = listOf("email") + * } + * } + * ``` + * + * Example with simple text input: + * ```kotlin + * val request = buildElicitRequest { + * message = "Enter a project name" + * requestedSchema { + * properties { + * put("projectName", JsonObject(mapOf( + * "type" to JsonPrimitive("string"), + * "description" to JsonPrimitive("Name for the new project") + * ))) + * } + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the elicitation request + * @return A configured [ElicitRequest] instance + * @see ElicitRequestBuilder + * @see ElicitRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +@Deprecated( + message = "Use ElicitRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("ElicitRequest{apply(block)}"), + +) +public inline fun buildElicitRequest(block: ElicitRequestBuilder.() -> Unit): ElicitRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ElicitRequestBuilder().apply(block).build() +} + /** * DSL builder for constructing [ElicitRequest] instances. * 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 cf06f771f..73969b098 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 @@ -1,6 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * Creates an [InitializeRequest] using a type-safe DSL builder. @@ -54,6 +57,65 @@ public inline operator fun InitializeRequest.Companion.invoke( block: InitializeRequestBuilder.() -> Unit, ): InitializeRequest = InitializeRequestBuilder().apply(block).build() +/** + * Creates an [InitializeRequest] using a type-safe DSL builder. + * + * ## Required + * - [protocolVersion][InitializeRequestBuilder.protocolVersion] - MCP protocol version + * - [capabilities][InitializeRequestBuilder.capabilities] - Client capabilities + * - [info][InitializeRequestBuilder.info] - Client implementation information + * + * ## Optional + * - [meta][InitializeRequestBuilder.meta] - Metadata for the request + * + * Example: + * ```kotlin + * val request = buildInitializeRequest { + * protocolVersion = "2024-11-05" + * capabilities { + * sampling(ClientCapabilities.sampling) + * roots(listChanged = true) + * } + * info("MyClient", "1.0.0") + * } + * ``` + * + * Example with full client info: + * ```kotlin + * val request = buildInitializeRequest { + * protocolVersion = "2024-11-05" + * capabilities { + * sampling(ClientCapabilities.sampling) + * experimental { + * put("feature", JsonPrimitive(true)) + * } + * } + * info( + * name = "MyAdvancedClient", + * version = "2.0.0", + * title = "Advanced MCP Client", + * websiteUrl = "https://example.com" + * ) + * } + * ``` + * + * @param block Configuration lambda for setting up the initialize request + * @return A configured [InitializeRequest] instance + * @see InitializeRequestBuilder + * @see InitializeRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +@Deprecated( + message = "Use InitializeRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("InitializeRequest{apply(block)}"), +) +public inline fun buildInitializeRequest(block: InitializeRequestBuilder.() -> Unit): InitializeRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return InitializeRequestBuilder().apply(block).build() +} + /** * DSL builder for constructing [InitializeRequest] instances. * 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 3e1599ec9..5d60bed75 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 @@ -1,6 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * Creates a [SetLevelRequest] using a type-safe DSL builder. @@ -35,6 +38,47 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi public inline operator fun SetLevelRequest.Companion.invoke(block: SetLevelRequestBuilder.() -> Unit): SetLevelRequest = SetLevelRequestBuilder().apply(block).build() +/** + * Creates a [SetLevelRequest] using a type-safe DSL builder. + * + * ## Required + * - [loggingLevel][SetLevelRequestBuilder.loggingLevel] - The logging level to set + * + * ## Optional + * - [meta][SetLevelRequestBuilder.meta] - Metadata for the request + * + * Example setting info level: + * ```kotlin + * val request = buildSetLevelRequest { + * loggingLevel = LoggingLevel.Info + * } + * ``` + * + * Example setting debug level: + * ```kotlin + * val request = buildSetLevelRequest { + * loggingLevel = LoggingLevel.Debug + * } + * ``` + * + * @param block Configuration lambda for setting up the logging level request + * @return A configured [SetLevelRequest] instance + * @see SetLevelRequestBuilder + * @see SetLevelRequest + * @see LoggingLevel + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +@Deprecated( + message = "Use SetLevelRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("SetLevelRequest{apply(block)}"), +) +public inline fun buildSetLevelRequest(block: SetLevelRequestBuilder.() -> Unit): SetLevelRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return SetLevelRequestBuilder().apply(block).build() +} + /** * DSL builder for constructing [SetLevelRequest] instances. * 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 690dadcfe..92949359f 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 @@ -1,6 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * Creates a [PingRequest] using a type-safe DSL builder. @@ -31,6 +34,43 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi public inline operator fun PingRequest.Companion.invoke(block: PingRequestBuilder.() -> Unit): PingRequest = PingRequestBuilder().apply(block).build() +/** + * Creates a [PingRequest] using a type-safe DSL builder. + * + * ## Optional + * - [meta][PingRequestBuilder.meta] - Metadata for the request + * + * Example with no parameters: + * ```kotlin + * val request = buildPingRequest { } + * ``` + * + * Example with metadata: + * ```kotlin + * val request = buildPingRequest { + * meta { + * put("timestamp", JsonPrimitive(System.currentTimeMillis())) + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the ping request + * @return A configured [PingRequest] instance + * @see PingRequestBuilder + * @see PingRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +@Deprecated( + message = "Use PingRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("PingRequest{apply(block)}"), +) +public inline fun buildPingRequest(block: PingRequestBuilder.() -> Unit): PingRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return PingRequestBuilder().apply(block).build() +} + /** * DSL builder for constructing [PingRequest] instances. * 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 3e89b44a7..efd16cc03 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 @@ -4,6 +4,9 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.buildJsonObject +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * Creates a [GetPromptRequest] using a type-safe DSL builder. @@ -43,6 +46,50 @@ public inline operator fun GetPromptRequest.Companion.invoke( block: GetPromptRequestBuilder.() -> Unit, ): GetPromptRequest = GetPromptRequestBuilder().apply(block).build() +/** + * Creates a [GetPromptRequest] using a type-safe DSL builder. + * + * ## Required + * - [name][GetPromptRequestBuilder.name] - The name of the prompt to retrieve + * + * ## Optional + * - [arguments][GetPromptRequestBuilder.arguments] - Arguments to pass to the prompt + * - [meta][GetPromptRequestBuilder.meta] - Metadata for the request + * + * Example without arguments: + * ```kotlin + * val request = buildGetPromptRequest { + * name = "greeting" + * } + * ``` + * + * Example with arguments: + * ```kotlin + * val request = buildGetPromptRequest { + * name = "userProfile" + * arguments = mapOf( + * "userId" to "123", + * "includeDetails" to "true" + * ) + * } + * ``` + * + * @param block Configuration lambda for setting up the get prompt request + * @return A configured [GetPromptRequest] instance + * @see GetPromptRequestBuilder + * @see GetPromptRequest + */ +@OptIn(ExperimentalContracts::class) +@Deprecated( + message = "Use GetPromptRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("GetPromptRequest{apply(block)}"), +) +public inline fun buildGetPromptRequest(block: GetPromptRequestBuilder.() -> Unit): GetPromptRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return GetPromptRequestBuilder().apply(block).build() +} + /** * DSL builder for constructing [GetPromptRequest] instances. * @@ -116,6 +163,42 @@ public inline operator fun ListPromptsRequest.Companion.invoke( block: ListPromptsRequestBuilder.() -> Unit, ): ListPromptsRequest = ListPromptsRequestBuilder().apply(block).build() +/** + * Creates a [ListPromptsRequest] using a type-safe DSL builder. + * + * ## Optional + * - [cursor][ListPromptsRequestBuilder.cursor] - Pagination cursor for fetching next page + * - [meta][ListPromptsRequestBuilder.meta] - Metadata for the request + * + * Example without pagination: + * ```kotlin + * val request = buildListPromptsRequest { } + * ``` + * + * Example with pagination: + * ```kotlin + * val request = buildListPromptsRequest { + * cursor = "eyJwYWdlIjogMn0=" + * } + * ``` + * + * @param block Configuration lambda for setting up the list prompts request + * @return A configured [ListPromptsRequest] instance + * @see ListPromptsRequestBuilder + * @see ListPromptsRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +@Deprecated( + message = "Use ListPromptsRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("ListPromptsRequest{apply(block)}"), +) +public inline fun buildListPromptsRequest(block: ListPromptsRequestBuilder.() -> Unit): ListPromptsRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListPromptsRequestBuilder().apply(block).build() +} + /** * DSL builder for constructing [ListPromptsRequest] instances. * 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 e30e06c3f..dc615dc7c 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 @@ -4,6 +4,9 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.buildJsonObject +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * Creates a [ListResourcesRequest] using a type-safe DSL builder. @@ -34,6 +37,42 @@ public inline operator fun ListResourcesRequest.Companion.invoke( block: ListResourcesRequestBuilder.() -> Unit, ): ListResourcesRequest = ListResourcesRequestBuilder().apply(block).build() +/** + * Creates a [ListResourcesRequest] using a type-safe DSL builder. + * + * ## Optional + * - [cursor][ListResourcesRequestBuilder.cursor] - Pagination cursor for fetching next page + * - [meta][ListResourcesRequestBuilder.meta] - Metadata for the request + * + * Example without pagination: + * ```kotlin + * val request = buildListResourcesRequest { } + * ``` + * + * Example with pagination: + * ```kotlin + * val request = buildListResourcesRequest { + * cursor = "eyJwYWdlIjogMn0=" + * } + * ``` + * + * @param block Configuration lambda for setting up the list resources request + * @return A configured [ListResourcesRequest] instance + * @see ListResourcesRequestBuilder + * @see ListResourcesRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +@Deprecated( + message = "Use ListResourcesRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("ListResourcesRequest{apply(block)}"), +) +public inline fun buildListResourcesRequest(block: ListResourcesRequestBuilder.() -> Unit): ListResourcesRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListResourcesRequestBuilder().apply(block).build() +} + /** * DSL builder for constructing [ListResourcesRequest] instances. * @@ -83,155 +122,99 @@ public inline operator fun ReadResourceRequest.Companion.invoke( ): ReadResourceRequest = ReadResourceRequestBuilder().apply(block).build() /** - * DSL builder for constructing [ReadResourceRequest] instances. - * - * This builder reads the contents of a specific resource by URI. - * - * ## Required - * - [uri] - The URI of the resource to read - * - * ## Optional - * - [meta] - Metadata for the request - * - * @see ReadResourceRequest - */ -@McpDsl -public class ReadResourceRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { - /** - * The URI of the resource to read. This is a required field. - * - * Example: `uri = "file:///path/to/resource.txt"` - */ - public var uri: String? = null - - @PublishedApi - override fun build(): ReadResourceRequest { - val uri = requireNotNull(uri) { - "Missing required field 'uri'. Example: uri = \"file:///path/to/resource.txt\"" - } - - val params = ReadResourceRequestParams(uri = uri, meta = meta) - return ReadResourceRequest(params) - } -} - -/** - * Creates a [SubscribeRequest] using a type-safe DSL builder. + * Creates a [ReadResourceRequest] using a type-safe DSL builder. * * ## Required - * - [uri][SubscribeRequestBuilder.uri] - The URI of the resource to subscribe to + * - [uri][ReadResourceRequestBuilder.uri] - The URI of the resource to read * * ## Optional - * - [meta][SubscribeRequestBuilder.meta] - Metadata for the request + * - [meta][ReadResourceRequestBuilder.meta] - Metadata for the request * * Example: * ```kotlin - * val request = SubscribeRequest { + * val request = buildReadResourceRequest { * uri = "file:///path/to/resource.txt" * } * ``` * - * @param block Configuration lambda for setting up the subscribe request - * @return A configured [SubscribeRequest] instance - * @see SubscribeRequestBuilder - * @see SubscribeRequest + * @param block Configuration lambda for setting up the read resource request + * @return A configured [ReadResourceRequest] instance + * @see ReadResourceRequestBuilder + * @see ReadResourceRequest */ +@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline operator fun SubscribeRequest.Companion.invoke( - block: SubscribeRequestBuilder.() -> Unit, -): SubscribeRequest = SubscribeRequestBuilder().apply(block).build() +@Deprecated( + message = "Use ReadResourceRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("ReadResourceRequest{apply(block)}"), +) +public inline fun buildReadResourceRequest(block: ReadResourceRequestBuilder.() -> Unit): ReadResourceRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ReadResourceRequestBuilder().apply(block).build() +} /** - * DSL builder for constructing [SubscribeRequest] instances. + * DSL builder for constructing [ReadResourceRequest] instances. * - * This builder subscribes to updates for a specific resource by URI. + * This builder reads the contents of a specific resource by URI. * * ## Required - * - [uri] - The URI of the resource to subscribe to + * - [uri] - The URI of the resource to read * * ## Optional * - [meta] - Metadata for the request * - * @see SubscribeRequest + * @see ReadResourceRequest */ @McpDsl -public class SubscribeRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { +public class ReadResourceRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { /** - * The URI of the resource to subscribe to. This is a required field. + * The URI of the resource to read. This is a required field. * * Example: `uri = "file:///path/to/resource.txt"` */ public var uri: String? = null @PublishedApi - override fun build(): SubscribeRequest { + override fun build(): ReadResourceRequest { val uri = requireNotNull(uri) { "Missing required field 'uri'. Example: uri = \"file:///path/to/resource.txt\"" } - val params = SubscribeRequestParams(uri = uri, meta = meta) - return SubscribeRequest(params) + val params = ReadResourceRequestParams(uri = uri, meta = meta) + return ReadResourceRequest(params) } } /** - * Creates an [UnsubscribeRequest] using a type-safe DSL builder. - * - * ## Required - * - [uri][UnsubscribeRequestBuilder.uri] - The URI of the resource to unsubscribe from + * Creates a [ListResourceTemplatesRequest] using a type-safe DSL builder. * * ## Optional - * - [meta][UnsubscribeRequestBuilder.meta] - Metadata for the request + * - [cursor][ListResourceTemplatesRequestBuilder.cursor] - Pagination cursor for fetching next page + * - [meta][ListResourceTemplatesRequestBuilder.meta] - Metadata for the request * - * Example: + * Example without pagination: * ```kotlin - * val request = UnsubscribeRequest { - * uri = "file:///path/to/resource.txt" + * val request = ListResourceTemplatesRequest { } + * ``` + * + * Example with pagination: + * ```kotlin + * val request = ListResourceTemplatesRequest { + * cursor = "eyJwYWdlIjogMn0=" * } * ``` * - * @param block Configuration lambda for setting up the unsubscribe request - * @return A configured [UnsubscribeRequest] instance - * @see UnsubscribeRequestBuilder - * @see UnsubscribeRequest + * @param block Configuration lambda for setting up the list resource templates request + * @return A configured [ListResourceTemplatesRequest] instance + * @see ListResourceTemplatesRequestBuilder + * @see ListResourceTemplatesRequest */ @ExperimentalMcpApi -public inline operator fun UnsubscribeRequest.Companion.invoke( - block: UnsubscribeRequestBuilder.() -> Unit, -): UnsubscribeRequest = UnsubscribeRequestBuilder().apply(block).build() - -/** - * DSL builder for constructing [UnsubscribeRequest] instances. - * - * This builder unsubscribes from updates for a specific resource by URI. - * - * ## Required - * - [uri] - The URI of the resource to unsubscribe from - * - * ## Optional - * - [meta] - Metadata for the request - * - * @see UnsubscribeRequest - */ -@McpDsl -public class UnsubscribeRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { - /** - * The URI of the resource to unsubscribe from. This is a required field. - * - * Example: `uri = "file:///path/to/resource.txt"` - */ - public var uri: String? = null - - @PublishedApi - override fun build(): UnsubscribeRequest { - val uri = requireNotNull(uri) { - "Missing required field 'uri'. Example: uri = \"file:///path/to/resource.txt\"" - } - - val params = UnsubscribeRequestParams(uri = uri, meta = meta) - return UnsubscribeRequest(params) - } -} +public inline operator fun ListResourceTemplatesRequest.Companion.invoke( + block: ListResourceTemplatesRequestBuilder.() -> Unit, +): ListResourceTemplatesRequest = ListResourceTemplatesRequestBuilder().apply(block).build() /** * Creates a [ListResourceTemplatesRequest] using a type-safe DSL builder. @@ -242,12 +225,12 @@ public class UnsubscribeRequestBuilder @PublishedApi internal constructor() : Re * * Example without pagination: * ```kotlin - * val request = ListResourceTemplatesRequest { } + * val request = buildListResourceTemplatesRequest { } * ``` * * Example with pagination: * ```kotlin - * val request = ListResourceTemplatesRequest { + * val request = buildListResourceTemplatesRequest { * cursor = "eyJwYWdlIjogMn0=" * } * ``` @@ -257,10 +240,19 @@ public class UnsubscribeRequestBuilder @PublishedApi internal constructor() : Re * @see ListResourceTemplatesRequestBuilder * @see ListResourceTemplatesRequest */ +@OptIn(ExperimentalContracts::class) @ExperimentalMcpApi -public inline operator fun ListResourceTemplatesRequest.Companion.invoke( +@Deprecated( + message = "Use ListResourceTemplatesRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("ListResourceTemplatesRequest{apply(block)}"), +) +public inline fun buildListResourceTemplatesRequest( block: ListResourceTemplatesRequestBuilder.() -> Unit, -): ListResourceTemplatesRequest = ListResourceTemplatesRequestBuilder().apply(block).build() +): ListResourceTemplatesRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListResourceTemplatesRequestBuilder().apply(block).build() +} /** * DSL builder for constructing [ListResourceTemplatesRequest] instances. 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 dbc3e288f..406e44ccf 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 @@ -1,6 +1,9 @@ package io.modelcontextprotocol.kotlin.sdk.types import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * Creates a [ListRootsRequest] using a type-safe DSL builder. @@ -32,6 +35,43 @@ public inline operator fun ListRootsRequest.Companion.invoke( block: ListRootsRequestBuilder.() -> Unit, ): ListRootsRequest = ListRootsRequestBuilder().apply(block).build() +/** + * Creates a [ListRootsRequest] using a type-safe DSL builder. + * + * ## Optional + * - [meta][ListRootsRequestBuilder.meta] - Metadata for the request + * + * Example with no parameters: + * ```kotlin + * val request = buildListRootsRequest { } + * ``` + * + * Example with metadata: + * ```kotlin + * val request = buildListRootsRequest { + * meta { + * put("context", "initialization") + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the list roots request + * @return A configured [ListRootsRequest] instance + * @see ListRootsRequestBuilder + * @see ListRootsRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +@Deprecated( + message = "Use ListRootsRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("ListRootsRequest{apply(block)}"), +) +public inline fun buildListRootsRequest(block: ListRootsRequestBuilder.() -> Unit): ListRootsRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListRootsRequestBuilder().apply(block).build() +} + /** * DSL builder for constructing [ListRootsRequest] instances. * 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 b45ef825e..d0499fcd7 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 @@ -4,6 +4,9 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.buildJsonObject +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * Creates a [CreateMessageRequest] using a type-safe DSL builder. @@ -59,6 +62,67 @@ public inline operator fun CreateMessageRequest.Companion.invoke( block: CreateMessageRequestBuilder.() -> Unit, ): CreateMessageRequest = CreateMessageRequestBuilder().apply(block).build() +/** + * Creates a [CreateMessageRequest] using a type-safe DSL builder. + * + * ## Required + * - [maxTokens][CreateMessageRequestBuilder.maxTokens] - Maximum number of tokens to generate + * - [messages][CreateMessageRequestBuilder.messages] - List of conversation messages + * + * ## Optional + * - [systemPrompt][CreateMessageRequestBuilder.systemPrompt] - System-level instructions + * - [context][CreateMessageRequestBuilder.context] - Context inclusion settings + * - [temperature][CreateMessageRequestBuilder.temperature] - Sampling temperature + * - [stopSequences][CreateMessageRequestBuilder.stopSequences] - Sequences that stop generation + * - [preferences][CreateMessageRequestBuilder.preferences] - Model selection preferences + * - [metadata][CreateMessageRequestBuilder.metadata] - Additional metadata + * - [meta][CreateMessageRequestBuilder.meta] - Request metadata + * + * Example: + * ```kotlin + * val request = buildCreateMessageRequest { + * maxTokens = 1000 + * systemPrompt = "You are a helpful assistant" + * messages { + * user { "What is the capital of France?" } + * assistant { "The capital of France is Paris." } + * user { "What about Germany?" } + * } + * } + * ``` + * + * Example with preferences: + * ```kotlin + * val request = buildCreateMessageRequest { + * maxTokens = 500 + * temperature = 0.7 + * preferences( + * hints = listOf("claude-3-sonnet"), + * intelligence = 0.8 + * ) + * messages { + * user { "Explain quantum computing" } + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the create message request + * @return A configured [CreateMessageRequest] instance + * @see CreateMessageRequestBuilder + * @see CreateMessageRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +@Deprecated( + message = "Use CreateMessageRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("CreateMessageRequest{apply(block)}"), +) +public inline fun buildCreateMessageRequest(block: CreateMessageRequestBuilder.() -> Unit): CreateMessageRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return CreateMessageRequestBuilder().apply(block).build() +} + /** * DSL builder for constructing [CreateMessageRequest] instances. * diff --git a/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/subscriptions.dsl.kt b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/subscriptions.dsl.kt new file mode 100644 index 000000000..e1f6199a8 --- /dev/null +++ b/kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/subscriptions.dsl.kt @@ -0,0 +1,190 @@ +package io.modelcontextprotocol.kotlin.sdk.types + +import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * Creates a [SubscribeRequest] using a type-safe DSL builder. + * + * ## Required + * - [uri][SubscribeRequestBuilder.uri] - The URI of the resource to subscribe to + * + * ## Optional + * - [meta][SubscribeRequestBuilder.meta] - Metadata for the request + * + * Example: + * ```kotlin + * val request = SubscribeRequest { + * uri = "file:///path/to/resource.txt" + * } + * ``` + * + * @param block Configuration lambda for setting up the subscribe request + * @return A configured [SubscribeRequest] instance + * @see SubscribeRequestBuilder + * @see SubscribeRequest + */ +@ExperimentalMcpApi +public inline operator fun SubscribeRequest.Companion.invoke( + block: SubscribeRequestBuilder.() -> Unit, +): SubscribeRequest = SubscribeRequestBuilder().apply(block).build() + +/** + * Creates a [SubscribeRequest] using a type-safe DSL builder. + * + * ## Required + * - [uri][SubscribeRequestBuilder.uri] - The URI of the resource to subscribe to + * + * ## Optional + * - [meta][SubscribeRequestBuilder.meta] - Metadata for the request + * + * Example: + * ```kotlin + * val request = buildSubscribeRequest { + * uri = "file:///path/to/resource.txt" + * } + * ``` + * + * @param block Configuration lambda for setting up the subscribe request + * @return A configured [SubscribeRequest] instance + * @see SubscribeRequestBuilder + * @see SubscribeRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +@Deprecated( + message = "Use SubscribeRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("SubscribeRequest{apply(block)}"), +) +public inline fun buildSubscribeRequest(block: SubscribeRequestBuilder.() -> Unit): SubscribeRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return SubscribeRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [SubscribeRequest] instances. + * + * This builder subscribes to updates for a specific resource by URI. + * + * ## Required + * - [uri] - The URI of the resource to subscribe to + * + * ## Optional + * - [meta] - Metadata for the request + * + * @see SubscribeRequest + */ +@McpDsl +public class SubscribeRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + /** + * The URI of the resource to subscribe to. This is a required field. + * + * Example: `uri = "file:///path/to/resource.txt"` + */ + public var uri: String? = null + + @PublishedApi + override fun build(): SubscribeRequest { + val uri = requireNotNull(uri) { + "Missing required field 'uri'. Example: uri = \"file:///path/to/resource.txt\"" + } + + val params = SubscribeRequestParams(uri = uri, meta = meta) + return SubscribeRequest(params) + } +} + +/** + * Creates an [UnsubscribeRequest] using a type-safe DSL builder. + * + * ## Required + * - [uri][UnsubscribeRequestBuilder.uri] - The URI of the resource to unsubscribe from + * + * ## Optional + * - [meta][UnsubscribeRequestBuilder.meta] - Metadata for the request + * + * Example: + * ```kotlin + * val request = UnsubscribeRequest { + * uri = "file:///path/to/resource.txt" + * } + * ``` + * + * @param block Configuration lambda for setting up the unsubscribe request + * @return A configured [UnsubscribeRequest] instance + * @see UnsubscribeRequestBuilder + * @see UnsubscribeRequest + */ +@ExperimentalMcpApi +public inline operator fun UnsubscribeRequest.Companion.invoke( + block: UnsubscribeRequestBuilder.() -> Unit, +): UnsubscribeRequest = UnsubscribeRequestBuilder().apply(block).build() + +/** + * Creates an [UnsubscribeRequest] using a type-safe DSL builder. + * + * ## Required + * - [uri][UnsubscribeRequestBuilder.uri] - The URI of the resource to unsubscribe from + * + * ## Optional + * - [meta][UnsubscribeRequestBuilder.meta] - Metadata for the request + * + * Example: + * ```kotlin + * val request = buildUnsubscribeRequest { + * uri = "file:///path/to/resource.txt" + * } + * ``` + * + * @param block Configuration lambda for setting up the unsubscribe request + * @return A configured [UnsubscribeRequest] instance + * @see UnsubscribeRequestBuilder + * @see UnsubscribeRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +@Deprecated( + message = "Use UnsubscribeRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("UnsubscribeRequest{apply(block)}"), +) +public inline fun buildUnsubscribeRequest(block: UnsubscribeRequestBuilder.() -> Unit): UnsubscribeRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return UnsubscribeRequestBuilder().apply(block).build() +} + +/** + * DSL builder for constructing [UnsubscribeRequest] instances. + * + * This builder unsubscribes from updates for a specific resource by URI. + * + * ## Required + * - [uri] - The URI of the resource to unsubscribe from + * + * ## Optional + * - [meta] - Metadata for the request + * + * @see UnsubscribeRequest + */ +@McpDsl +public class UnsubscribeRequestBuilder @PublishedApi internal constructor() : RequestBuilder() { + /** + * The URI of the resource to unsubscribe from. This is a required field. + * + * Example: `uri = "file:///path/to/resource.txt"` + */ + public var uri: String? = null + + @PublishedApi + override fun build(): UnsubscribeRequest { + val uri = requireNotNull(uri) { + "Missing required field 'uri'. Example: uri = \"file:///path/to/resource.txt\"" + } + + val params = UnsubscribeRequestParams(uri = uri, meta = meta) + return UnsubscribeRequest(params) + } +} 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 a972caa60..5e8d769c6 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 @@ -4,6 +4,9 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObjectBuilder import kotlinx.serialization.json.buildJsonObject +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract /** * Creates a [CallToolRequest] using a type-safe DSL builder. @@ -42,6 +45,51 @@ import kotlinx.serialization.json.buildJsonObject public inline operator fun CallToolRequest.Companion.invoke(block: CallToolRequestBuilder.() -> Unit): CallToolRequest = CallToolRequestBuilder().apply(block).build() +/** + * Creates a [CallToolRequest] using a type-safe DSL builder. + * + * ## Required + * - [name][CallToolRequestBuilder.name] - The name of the tool to call + * + * ## Optional + * - [arguments][CallToolRequestBuilder.arguments] - Arguments to pass to the tool + * - [meta][CallToolRequestBuilder.meta] - Metadata for the request + * + * Example without arguments: + * ```kotlin + * val request = buildCallToolRequest { + * name = "getCurrentTime" + * } + * ``` + * + * Example with arguments: + * ```kotlin + * val request = buildCallToolRequest { + * name = "searchDatabase" + * arguments { + * put("query", "users") + * put("limit", 10) + * } + * } + * ``` + * + * @param block Configuration lambda for setting up the call tool request + * @return A configured [CallToolRequest] instance + * @see CallToolRequestBuilder + * @see CallToolRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +@Deprecated( + message = "Use CallToolRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("CallToolRequest{apply(block)}"), +) +public inline fun buildCallToolRequest(block: CallToolRequestBuilder.() -> Unit): CallToolRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return CallToolRequestBuilder().apply(block).build() +} + /** * DSL builder for constructing [CallToolRequest] instances. * @@ -139,6 +187,42 @@ public inline operator fun ListToolsRequest.Companion.invoke( block: ListToolsRequestBuilder.() -> Unit, ): ListToolsRequest = ListToolsRequestBuilder().apply(block).build() +/** + * Creates a [ListToolsRequest] using a type-safe DSL builder. + * + * ## Optional + * - [cursor][ListToolsRequestBuilder.cursor] - Pagination cursor for fetching next page + * - [meta][ListToolsRequestBuilder.meta] - Metadata for the request + * + * Example without pagination: + * ```kotlin + * val request = buildListToolsRequest { } + * ``` + * + * Example with pagination: + * ```kotlin + * val request = buildListToolsRequest { + * cursor = "eyJwYWdlIjogMn0=" + * } + * ``` + * + * @param block Configuration lambda for setting up the list tools request + * @return A configured [ListToolsRequest] instance + * @see ListToolsRequestBuilder + * @see ListToolsRequest + */ +@OptIn(ExperimentalContracts::class) +@ExperimentalMcpApi +@Deprecated( + message = "Use ListToolsRequest { } instead", + level = DeprecationLevel.WARNING, + replaceWith = ReplaceWith("ListToolsRequest{apply(block)}"), +) +public inline fun buildListToolsRequest(block: ListToolsRequestBuilder.() -> Unit): ListToolsRequest { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + return ListToolsRequestBuilder().apply(block).build() +} + /** * DSL builder for constructing [ListToolsRequest] instances. * 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 index f4067ce92..ac1ec3fab 100644 --- 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 @@ -7,6 +7,7 @@ import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.CompleteRequest import io.modelcontextprotocol.kotlin.sdk.types.PromptReference import io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplateReference +import io.modelcontextprotocol.kotlin.sdk.types.buildCompleteRequest import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlin.test.Test @@ -14,7 +15,7 @@ import kotlin.test.Test class CompletionDslTest { @Test fun `buildCompleteRequest should build with prompt reference and context`() { - val request = CompleteRequest { + val request = buildCompleteRequest { argument("query", "user input") ref(PromptReference("searchPrompt")) context { @@ -32,7 +33,7 @@ class CompletionDslTest { @Test fun `buildCompleteRequest should build with resource template reference and map context`() { - val request = CompleteRequest { + val request = buildCompleteRequest { argument("path", "/users/123") ref(ResourceTemplateReference("file:///{path}")) context(mapOf("role" to "admin")) @@ -49,7 +50,7 @@ class CompletionDslTest { @Test fun `buildCompleteRequest should throw if argument is missing`() { shouldThrow { - CompleteRequest { + buildCompleteRequest { ref(PromptReference("name")) } } @@ -57,6 +58,58 @@ class CompletionDslTest { @Test fun `buildCompleteRequest should throw if ref is missing`() { + shouldThrow { + buildCompleteRequest { + argument("name", "value") + } + } + } + + @Test + fun `CompleteRequest should build with prompt reference and context`() { + val request = CompleteRequest { + 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 `CompleteRequest should build with resource template reference and map context`() { + val request = CompleteRequest { + 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 `CompleteRequest should throw if argument is missing`() { + shouldThrow { + CompleteRequest { + ref(PromptReference("name")) + } + } + } + + @Test + fun `CompleteRequest should throw if ref is missing`() { shouldThrow { CompleteRequest { 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 index 8544696a5..86a0c2956 100644 --- 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 @@ -5,6 +5,7 @@ import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequest import io.modelcontextprotocol.kotlin.sdk.types.ElicitRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.buildElicitRequest import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put @@ -14,7 +15,7 @@ import kotlin.test.Test class ElicitationDslTest { @Test fun `buildElicitRequest should build with all fields`() { - val request = ElicitRequest { + val request = buildElicitRequest { message = "Provide info" requestedSchema { properties { @@ -31,6 +32,77 @@ class ElicitationDslTest { @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 `buildElicitRequest 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 `buildElicitRequest should throw if properties are missing`() { + shouldThrow { + buildElicitRequest { + message = "Test" + requestedSchema { } + } + } + } + + @Test + fun `ElicitRequest should build with all fields`() { + val request = ElicitRequest { + 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 `ElicitRequest should support direct requestedSchema`() { val schema = ElicitRequestParams.RequestedSchema( properties = buildJsonObject { put("name", buildJsonObject { put("type", "string") }) }, required = listOf("name"), @@ -56,7 +128,7 @@ class ElicitationDslTest { } @Test - fun `buildElicitRequest should throw if message is missing`() { + fun `ElicitRequest should throw if message is missing`() { shouldThrow { ElicitRequest { requestedSchema { properties { put("a", 1) } } @@ -65,7 +137,7 @@ class ElicitationDslTest { } @Test - fun `buildElicitRequest should throw if requestedSchema is missing`() { + fun `ElicitRequest should throw if requestedSchema is missing`() { shouldThrow { ElicitRequest { message = "Test" 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 index 589a3259f..47c2a0642 100644 --- 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 @@ -7,6 +7,7 @@ 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.InitializeRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int @@ -16,9 +17,10 @@ import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class InitializeDslTest { + @Test fun `buildInitializeRequest should create request with all fields`() { - val request = InitializeRequest { + val request = buildInitializeRequest { protocolVersion = "2024-11-05" capabilities { sampling { @@ -60,7 +62,7 @@ class InitializeDslTest { val capabilities = ClientCapabilities(roots = ClientCapabilities.Roots(listChanged = false)) val info = Implementation(name = "Direct", version = "0.1") - val request = InitializeRequest { + val request = buildInitializeRequest { protocolVersion = "1.0" capabilities(capabilities) info(info) @@ -76,7 +78,7 @@ class InitializeDslTest { val elicitationObj = buildJsonObject { put("key", "value") } val experimentalObj = buildJsonObject { put("key", "value") } - val request = InitializeRequest { + val request = buildInitializeRequest { protocolVersion = "1.0" capabilities { sampling(samplingObj) @@ -95,7 +97,7 @@ class InitializeDslTest { @Test fun `ClientCapabilitiesBuilder roots should support default arguments`() { - val request = InitializeRequest { + val request = buildInitializeRequest { protocolVersion = "1.0" capabilities { roots() @@ -110,7 +112,7 @@ class InitializeDslTest { @Test fun `buildInitializeRequest should throw if protocolVersion is missing`() { shouldThrow { - InitializeRequest { + buildInitializeRequest { capabilities { } info("Test", "1.0") } @@ -120,7 +122,7 @@ class InitializeDslTest { @Test fun `buildInitializeRequest should throw if capabilities are missing`() { shouldThrow { - InitializeRequest { + buildInitializeRequest { protocolVersion = "1.0" info("Test", "1.0") } @@ -129,6 +131,127 @@ class InitializeDslTest { @Test fun `buildInitializeRequest should throw if info is missing`() { + shouldThrow { + buildInitializeRequest { + protocolVersion = "1.0" + capabilities { } + } + } + } + + @Test + fun `InitializeRequest should create request with all fields`() { + val request = InitializeRequest { + 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 `InitializeRequest should support direct capabilities and info`() { + val capabilities = ClientCapabilities(roots = ClientCapabilities.Roots(listChanged = false)) + val info = Implementation(name = "Direct", version = "0.1") + + val request = InitializeRequest { + protocolVersion = "1.0" + capabilities(capabilities) + info(info) + } + + request.params.capabilities shouldBe capabilities + request.params.clientInfo shouldBe info + } + + @Test + fun `InitializeRequest 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 = InitializeRequest { + 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 `InitializeRequest ClientCapabilitiesBuilder roots should support default arguments`() { + val request = InitializeRequest { + protocolVersion = "1.0" + capabilities { + roots() + } + info("Test", "1.0") + } + request.params.capabilities.roots.shouldNotBeNull { + listChanged shouldBe null + } + } + + @Test + fun `InitializeRequest should throw if protocolVersion is missing`() { + shouldThrow { + InitializeRequest { + capabilities { } + info("Test", "1.0") + } + } + } + + @Test + fun `InitializeRequest should throw if capabilities are missing`() { + shouldThrow { + InitializeRequest { + protocolVersion = "1.0" + info("Test", "1.0") + } + } + } + + @Test + fun `InitializeRequest should throw if info is missing`() { shouldThrow { InitializeRequest { protocolVersion = "1.0" 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 index cf57e9bb7..1c8923997 100644 --- 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 @@ -5,14 +5,16 @@ import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.LoggingLevel import io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildSetLevelRequest import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class LoggingDslTest { + @Test fun `buildSetLevelRequest should create request with given level`() { - val request = SetLevelRequest { + val request = buildSetLevelRequest { loggingLevel = LoggingLevel.Info } @@ -21,6 +23,22 @@ class LoggingDslTest { @Test fun `buildSetLevelRequest should throw if loggingLevel is missing`() { + shouldThrow { + buildSetLevelRequest { } + } + } + + @Test + fun `SetLevelRequest should create request with given level`() { + val request = SetLevelRequest { + loggingLevel = LoggingLevel.Info + } + + request.params.level shouldBe LoggingLevel.Info + } + + @Test + fun `SetLevelRequest should throw if loggingLevel is missing`() { shouldThrow { SetLevelRequest { } } 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 index eddc19ccb..de2c8e8f6 100644 --- 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 @@ -4,6 +4,7 @@ import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.PingRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildPingRequest import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.add import kotlinx.serialization.json.boolean @@ -14,9 +15,10 @@ import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class PingRequestDslTest { + @Test fun `buildPingRequest should create request with meta containing all field types`() { - val request = PingRequest { + val request = buildPingRequest { meta { progressToken("token-123") put("string", "value") @@ -47,6 +49,60 @@ class PingRequestDslTest { @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 + } + + @Test + fun `PingRequest should create request with meta containing all field types`() { + val request = PingRequest { + 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 `PingRequest RequestMeta DSL should support numeric progress tokens`() { val requestInt = PingRequest { meta { progressToken(123) } } @@ -63,7 +119,7 @@ class PingRequestDslTest { } @Test - fun `buildPingRequest should create request without params if meta is empty`() { + fun `PingRequest should create request without params if meta is empty`() { val request = PingRequest { } 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 index 7df208d03..b686ad2c5 100644 --- 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 @@ -6,14 +6,17 @@ import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest import io.modelcontextprotocol.kotlin.sdk.types.ListPromptsRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildGetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildListPromptsRequest import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class PromptsDslTest { + @Test fun `buildGetPromptRequest should create request with name and arguments`() { - val request = GetPromptRequest { + val request = buildGetPromptRequest { name = "test-prompt" arguments = mapOf("key" to "value") } @@ -24,7 +27,7 @@ class PromptsDslTest { @Test fun `buildListPromptsRequest should create request with cursor`() { - val request = ListPromptsRequest { + val request = buildListPromptsRequest { cursor = "next-page" } @@ -36,12 +39,47 @@ class PromptsDslTest { @Test fun `buildGetPromptRequest should throw if name is missing`() { shouldThrow { - GetPromptRequest { } + buildGetPromptRequest { } } } @Test fun `buildListPromptsRequest should create request without params if empty`() { + val request = buildListPromptsRequest { } + request.params shouldBe null + } + + @Test + fun `GetPromptRequest should create request with name and arguments`() { + val request = GetPromptRequest { + name = "test-prompt" + arguments = mapOf("key" to "value") + } + + request.params.name shouldBe "test-prompt" + request.params.arguments shouldBe mapOf("key" to "value") + } + + @Test + fun `ListPromptsRequest should create request with cursor`() { + val request = ListPromptsRequest { + cursor = "next-page" + } + + request.params shouldNotBeNull { + cursor shouldBe "next-page" + } + } + + @Test + fun `GetPromptRequest should throw if name is missing`() { + shouldThrow { + GetPromptRequest { } + } + } + + @Test + fun `ListPromptsRequest should create request without params if empty`() { val request = ListPromptsRequest { } 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 index 9af88d82a..20505692f 100644 --- 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 @@ -9,14 +9,20 @@ import io.modelcontextprotocol.kotlin.sdk.types.ListResourcesRequest import io.modelcontextprotocol.kotlin.sdk.types.ReadResourceRequest import io.modelcontextprotocol.kotlin.sdk.types.SubscribeRequest import io.modelcontextprotocol.kotlin.sdk.types.UnsubscribeRequest +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 io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class ResourcesDslTest { + @Test fun `buildListResourcesRequest should create request with cursor`() { - val request = ListResourcesRequest { + val request = buildListResourcesRequest { cursor = "next" } request.params shouldNotBeNull { @@ -26,7 +32,7 @@ class ResourcesDslTest { @Test fun `buildReadResourceRequest should create request with uri`() { - val request = ReadResourceRequest { + val request = buildReadResourceRequest { uri = "test://resource" } request.params.uri shouldBe "test://resource" @@ -34,7 +40,7 @@ class ResourcesDslTest { @Test fun `buildSubscribeRequest should create request with uri`() { - val request = SubscribeRequest { + val request = buildSubscribeRequest { uri = "test://resource" } request.params.uri shouldBe "test://resource" @@ -42,7 +48,7 @@ class ResourcesDslTest { @Test fun `buildUnsubscribeRequest should create request with uri`() { - val request = UnsubscribeRequest { + val request = buildUnsubscribeRequest { uri = "test://resource" } request.params.uri shouldBe "test://resource" @@ -50,7 +56,7 @@ class ResourcesDslTest { @Test fun `buildListResourceTemplatesRequest should create request with cursor`() { - val request = ListResourceTemplatesRequest { + val request = buildListResourceTemplatesRequest { cursor = "template-cursor" } request.params shouldNotBeNull { @@ -61,32 +67,109 @@ class ResourcesDslTest { @Test fun `buildReadResourceRequest should throw if uri is missing`() { shouldThrow { - ReadResourceRequest { } + buildReadResourceRequest { } } } @Test fun `buildSubscribeRequest should throw if uri is missing`() { shouldThrow { - SubscribeRequest { } + buildSubscribeRequest { } } } @Test fun `buildUnsubscribeRequest should throw if uri is missing`() { shouldThrow { - UnsubscribeRequest { } + buildUnsubscribeRequest { } } } @Test fun `buildListResourcesRequest should create request without params if empty`() { - val request = ListResourcesRequest { } + val request = buildListResourcesRequest { } request.params shouldBe null } @Test fun `buildListResourceTemplatesRequest should create request without params if empty`() { + val request = buildListResourceTemplatesRequest { } + request.params shouldBe null + } + + @Test + fun `ListResourcesRequest should create request with cursor`() { + val request = ListResourcesRequest { + cursor = "next" + } + request.params shouldNotBeNull { + cursor shouldBe "next" + } + } + + @Test + fun `ReadResourceRequest should create request with uri`() { + val request = ReadResourceRequest { + uri = "test://resource" + } + request.params.uri shouldBe "test://resource" + } + + @Test + fun `SubscribeRequest should create request with uri`() { + val request = SubscribeRequest { + uri = "test://resource" + } + request.params.uri shouldBe "test://resource" + } + + @Test + fun `UnsubscribeRequest should create request with uri`() { + val request = UnsubscribeRequest { + uri = "test://resource" + } + request.params.uri shouldBe "test://resource" + } + + @Test + fun `ListResourceTemplatesRequest should create request with cursor`() { + val request = ListResourceTemplatesRequest { + cursor = "template-cursor" + } + request.params shouldNotBeNull { + cursor shouldBe "template-cursor" + } + } + + @Test + fun `ReadResourceRequest should throw if uri is missing`() { + shouldThrow { + ReadResourceRequest { } + } + } + + @Test + fun `SubscribeRequest should throw if uri is missing`() { + shouldThrow { + SubscribeRequest { } + } + } + + @Test + fun `UnsubscribeRequest should throw if uri is missing`() { + shouldThrow { + UnsubscribeRequest { } + } + } + + @Test + fun `ListResourcesRequest should create request without params if empty`() { + val request = ListResourcesRequest { } + request.params shouldBe null + } + + @Test + fun `ListResourceTemplatesRequest should create request without params if empty`() { val request = ListResourceTemplatesRequest { } 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 index 972c84534..313e24b89 100644 --- 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 @@ -3,6 +3,7 @@ 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.ListRootsRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildListRootsRequest import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlin.test.Test @@ -10,6 +11,16 @@ import kotlin.test.Test class RootsDslTest { @Test fun `buildListRootsRequest should create request with meta`() { + val request = buildListRootsRequest { + meta { + put("test", "value") + } + } + request.params.shouldNotBeNull() + } + + @Test + fun `ListRootsRequest should create request with meta`() { val request = ListRootsRequest { meta { put("test", "value") 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 index a970f08bb..c90c9840b 100644 --- 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 @@ -17,6 +17,7 @@ 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.invoke import io.modelcontextprotocol.kotlin.sdk.types.user import io.modelcontextprotocol.kotlin.sdk.types.userAudio @@ -28,10 +29,11 @@ import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class SamplingDslTest { + @Test @Suppress("LongMethod") fun `buildCreateMessageRequest should build with all fields`() { - val request = CreateMessageRequest { + val request = buildCreateMessageRequest { maxTokens = 1000 systemPrompt = "System prompt" temperature = 0.5 @@ -105,7 +107,7 @@ class SamplingDslTest { fun `buildCreateMessageRequest should support direct assignments`() { val messages = listOf(SamplingMessage(Role.User, TextContent("Hello"))) val preferences = ModelPreferences(costPriority = 0.1) - val request = CreateMessageRequest { + val request = buildCreateMessageRequest { maxTokens = 100 messages(messages) preferences(preferences) @@ -120,7 +122,7 @@ class SamplingDslTest { @Test fun `SamplingMessageBuilder should support direct content assignment`() { val content = TextContent("Direct") - val request = CreateMessageRequest { + val request = buildCreateMessageRequest { maxTokens = 100 messages { user(content) @@ -134,7 +136,7 @@ class SamplingDslTest { @Test fun `buildCreateMessageRequest should throw if maxTokens is missing`() { shouldThrow { - CreateMessageRequest { + buildCreateMessageRequest { messages { user { "Hi" } } } } @@ -143,7 +145,7 @@ class SamplingDslTest { @Test fun `buildCreateMessageRequest should throw if messages are missing`() { shouldThrow { - CreateMessageRequest { + buildCreateMessageRequest { maxTokens = 100 } } @@ -152,7 +154,7 @@ class SamplingDslTest { @Test fun `TextContentBuilder should throw if text is missing`() { shouldThrow { - CreateMessageRequest { + buildCreateMessageRequest { maxTokens = 100 messages { userText { } @@ -163,6 +165,179 @@ class SamplingDslTest { @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" } + } + } + } + } + + @Test + @Suppress("LongMethod") + fun `CreateMessageRequest should build with all fields`() { + val request = CreateMessageRequest { + 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 `CreateMessageRequest should support direct assignments`() { + val messages = listOf(SamplingMessage(Role.User, TextContent("Hello"))) + val preferences = ModelPreferences(costPriority = 0.1) + val request = CreateMessageRequest { + 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 `CreateMessageRequest SamplingMessageBuilder should support direct content assignment`() { + val content = TextContent("Direct") + val request = CreateMessageRequest { + maxTokens = 100 + messages { + user(content) + assistant(content) + } + } + request.params.messages[0].content shouldBe content + request.params.messages[1].content shouldBe content + } + + @Test + fun `CreateMessageRequest should throw if maxTokens is missing`() { + shouldThrow { + CreateMessageRequest { + messages { user { "Hi" } } + } + } + } + + @Test + fun `CreateMessageRequest should throw if messages are missing`() { + shouldThrow { + CreateMessageRequest { + maxTokens = 100 + } + } + } + + @Test + fun `CreateMessageRequest TextContentBuilder should throw if text is missing`() { + shouldThrow { + CreateMessageRequest { + maxTokens = 100 + messages { + userText { } + } + } + } + } + + @Test + fun `CreateMessageRequest ImageContentBuilder should throw if data or mimeType is missing`() { shouldThrow { CreateMessageRequest { maxTokens = 100 @@ -182,7 +357,7 @@ class SamplingDslTest { } @Test - fun `AudioContentBuilder should throw if data or mimeType is missing`() { + fun `CreateMessageRequest AudioContentBuilder should throw if data or mimeType is missing`() { shouldThrow { CreateMessageRequest { maxTokens = 100 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 index 346d310be..d8fd80c7e 100644 --- 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 @@ -6,6 +6,8 @@ import io.kotest.matchers.shouldBe import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi import io.modelcontextprotocol.kotlin.sdk.types.CallToolRequest import io.modelcontextprotocol.kotlin.sdk.types.ListToolsRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildCallToolRequest +import io.modelcontextprotocol.kotlin.sdk.types.buildListToolsRequest import io.modelcontextprotocol.kotlin.sdk.types.invoke import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.int @@ -15,9 +17,10 @@ import kotlin.test.Test @OptIn(ExperimentalMcpApi::class) class ToolsDslTest { + @Test fun `buildCallToolRequest should build with name and arguments`() { - val request = CallToolRequest { + val request = buildCallToolRequest { name = "test-tool" arguments { put("key", "value") @@ -34,7 +37,7 @@ class ToolsDslTest { @Test fun `buildListToolsRequest should build with cursor`() { - val request = ListToolsRequest { + val request = buildListToolsRequest { cursor = "tool-cursor" } @@ -46,7 +49,7 @@ class ToolsDslTest { @Test fun `buildCallToolRequest should support direct arguments assignment`() { val args = buildJsonObject { put("key", "value") } - val request = CallToolRequest { + val request = buildCallToolRequest { name = "test-tool" arguments(args) } @@ -56,12 +59,63 @@ class ToolsDslTest { @Test fun `buildCallToolRequest should throw if name is missing`() { shouldThrow { - CallToolRequest { } + buildCallToolRequest { } } } @Test fun `buildListToolsRequest should build without params if empty`() { + val request = buildListToolsRequest { } + request.params shouldBe null + } + + @Test + fun `CallToolRequest should build with name and arguments`() { + val request = CallToolRequest { + 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 `ListToolsRequest should build with cursor`() { + val request = ListToolsRequest { + cursor = "tool-cursor" + } + + request.params shouldNotBeNull { + cursor shouldBe "tool-cursor" + } + } + + @Test + fun `CallToolRequest should support direct arguments assignment`() { + val args = buildJsonObject { put("key", "value") } + val request = CallToolRequest { + name = "test-tool" + arguments(args) + } + request.params.arguments shouldBe args + } + + @Test + fun `CallToolRequest should throw if name is missing`() { + shouldThrow { + CallToolRequest { } + } + } + + @Test + fun `ListToolsRequest should build without params if empty`() { val request = ListToolsRequest { } request.params shouldBe null }