Proposal: DSL Builders for request types (#399)

This PR introduces type-safe DSL builders for all MCP request types,
making it easier to construct requests
  
  ### Current Solution
  
  All request types now have DSL builders:
  ```kotlin
  // Initialize request
  val request = initializeRequest {
      protocolVersion = "2024-11-05"
      capabilities {
          sampling(ClientCapabilities.sampling)
          roots(listChanged = true)
      }
      info("MyClient", "1.0.0")
  }

  // Create message request with conversation
  val messageRequest = createMessageRequest {
      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?" }
      }
  }

  // Call tool request
  val toolRequest = callToolRequest {
      name = "searchDatabase"
      arguments {
          put("query", "users")
          put("limit", 10)
      }
  }

  // Elicit request with schema
  val elicitRequest = elicitationRequest {
      message = "Please provide your contact information"
      requestedSchema {
          properties {
              put("email", JsonObject(mapOf(
                  "type" to JsonPrimitive("string"),
                  "description" to JsonPrimitive("Your email address")
              )))
          }
          required = listOf("email")
      }
  }
```

Each DSL builder includes:
  - Clear separation of required (but runtime) vs optional fields
  - Multiple usage examples
  - Concise error messages with examples when required fields are missing
  - Cross-references to related types

### Design Considerations

1. Function Naming Convention

The current naming convention (e.g., initializeRequest, callToolRequest) might be confusing since these functions don't send requests, they just build request objects.

Alternative approach: Add a build prefix to clarify intent:
  - `initializeRequest` → `buildInitializeRequest`
  - `callToolRequest` → `buildCallToolRequest`
  - `createMessageRequest` → `buildCreateMessageRequest`

This would make it clearer that these are builder functions.

2. Runtime vs Compile-Time Validation

Currently, required fields are validated at runtime in the `build()` method:

```kotlin
val request = initializeRequest {
      protocolVersion = "2024-11-05"
      // Missing required fields: capabilities, info
      // Error thrown at runtime ❌
}
```

Alternative approach: Use Compose UI style where required parameters are in the function signature:

```kotlin
fun initializeRequest(
      protocolVersion: String,
      capabilities: ClientCapabilities,
      info: Implementation,
      block: InitializeRequestBuilder.() -> Unit = {}
  ): InitializeRequest
```

```kotlin
// Usage
val request = initializeRequest(
  protocolVersion = "2024-11-05",
capabilities = ClientCapabilities(sampling =
ClientCapabilities.sampling),
  info = Implementation("MyClient", "1.0.0")
 ) {
      // Optional fields only
      meta { put("key", "value") }
}
```

Pros:
- Compile-time safety for required fields ✅
- Clear which fields are required ✅
- Better IDE autocomplete ✅

Cons:
- Multiple trailing lambdas can be confusing:

```kotlin
// When required fields include lambdas
val request = elicitationRequest(
      message = "Enter info",
      requestedSchema = { /* lambda 1 */ }
) { /* lambda 2 for optional fields */ }
```

- Less consistent DSL feel
- More verbose for simple cases
This commit is contained in:
Pavel Gorgulov 2025-11-20 19:46:07 +01:00 • committed by GitHub
commit 74c93745cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 2744 additions and 0 deletions

View file

@ -0,0 +1,12 @@
package io.modelcontextprotocol.kotlin.sdk.types
/**
* DSL marker annotation for MCP builder classes.
*
* This annotation is used to prevent accidental access to outer DSL scopes
* within nested DSL blocks, ensuring type-safe and unambiguous builder usage.
*
* @see DslMarker
*/
@DslMarker
public annotation class McpDsl

View file

@ -0,0 +1,181 @@
package io.modelcontextprotocol.kotlin.sdk.types
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonObjectBuilder
import kotlinx.serialization.json.buildJsonObject
/**
* DSL builder for constructing [ClientCapabilities] instances.
*
* This builder is used within [InitializeRequestBuilder] to configure client capabilities.
* All capabilities are optional - the presence of a capability indicates support for that feature.
*
* ## Available Functions (all optional)
* - [sampling] - Indicates support for sampling from an LLM
* - [roots] - Indicates support for listing roots
* - [elicitation] - Indicates support for elicitation from the server
* - [experimental] - Defines experimental, non-standard capabilities
*
* Example usage within [buildInitializeRequest][buildInitializeRequest]:
* ```kotlin
* val request = buildInitializeRequest {
* protocolVersion = "1.0"
* capabilities {
* sampling(ClientCapabilities.sampling)
* roots(listChanged = true)
* experimental {
* put("customFeature", JsonPrimitive(true))
* }
* }
* info("MyClient", "1.0.0")
* }
* ```
*
* @see ClientCapabilities
* @see InitializeRequestBuilder.capabilities
*/
@McpDsl
public class ClientCapabilitiesBuilder @PublishedApi internal constructor() {
private var sampling: JsonObject? = null
private var roots: ClientCapabilities.Roots? = null
private var elicitation: JsonObject? = null
private var experimental: JsonObject? = null
/**
* Indicates that the client supports sampling from an LLM.
*
* Use [ClientCapabilities.sampling] for default empty configuration.
*
* Example:
* ```kotlin
* capabilities {
* sampling(ClientCapabilities.sampling)
* }
* ```
*
* @param value The sampling capability configuration
*/
public fun sampling(value: JsonObject) {
this.sampling = value
}
/**
* Indicates that the client supports sampling from an LLM with custom configuration.
*
* Example:
* ```kotlin
* capabilities {
* sampling {
* put("temperature", JsonPrimitive(0.7))
* }
* }
* ```
*
* @param block Lambda for building the sampling configuration
*/
public fun sampling(block: JsonObjectBuilder.() -> Unit): Unit = sampling(buildJsonObject(block))
/**
* Indicates that the client supports listing roots.
*
* Example with listChanged notification:
* ```kotlin
* capabilities {
* roots(listChanged = true)
* }
* ```
*
* Example without listChanged:
* ```kotlin
* capabilities {
* roots()
* }
* ```
*
* @param listChanged Whether the client will emit notifications when the list of roots changes
*/
public fun roots(listChanged: Boolean? = null) {
this.roots = ClientCapabilities.Roots(listChanged)
}
/**
* Indicates that the client supports elicitation from the server.
*
* Use [ClientCapabilities.elicitation] for default empty configuration.
*
* Example:
* ```kotlin
* capabilities {
* elicitation(ClientCapabilities.elicitation)
* }
* ```
*
* @param value The elicitation capability configuration
*/
public fun elicitation(value: JsonObject) {
this.elicitation = value
}
/**
* Indicates that the client supports elicitation from the server with custom configuration.
*
* Example:
* ```kotlin
* capabilities {
* elicitation {
* put("mode", JsonPrimitive("interactive"))
* }
* }
* ```
*
* @param block Lambda for building the elicitation configuration
*/
public fun elicitation(block: JsonObjectBuilder.() -> Unit): Unit = elicitation(buildJsonObject(block))
/**
* Defines experimental, non-standard capabilities that the client supports.
*
* Example:
* ```kotlin
* capabilities {
* experimental(buildJsonObject {
* put("customFeature", JsonPrimitive(true))
* put("version", JsonPrimitive("1.0"))
* })
* }
* ```
*
* @param value The experimental capabilities configuration
*/
public fun experimental(value: JsonObject) {
this.experimental = value
}
/**
* Defines experimental, non-standard capabilities that the client supports using a DSL builder.
*
* Example:
* ```kotlin
* capabilities {
* experimental {
* put("customFeature", JsonPrimitive(true))
* put("beta", JsonObject(mapOf(
* "enabled" to JsonPrimitive(true),
* "version" to JsonPrimitive("2.0")
* )))
* }
* }
* ```
*
* @param block Lambda for building the experimental capabilities configuration
*/
public fun experimental(block: JsonObjectBuilder.() -> Unit): Unit = experimental(buildJsonObject(block))
@PublishedApi
internal fun build(): ClientCapabilities = ClientCapabilities(
sampling = sampling,
roots = roots,
elicitation = elicitation,
experimental = experimental,
)
}

View file

@ -0,0 +1,169 @@
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.
*
* ## 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)
@ExperimentalMcpApi
internal inline fun buildCompleteRequest(block: CompleteRequestBuilder.() -> Unit): CompleteRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return CompleteRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [CompleteRequest] instances.
*
* This builder provides methods to configure completion requests for prompts or resource templates.
* Both [argument] and [ref] are required; [context] is optional.
*
* @see buildCompleteRequest
*/
@McpDsl
public class CompleteRequestBuilder @PublishedApi internal constructor() : RequestBuilder() {
private var arg: CompleteRequestParams.Argument? = null
private var ref: Reference? = null
private var ctx: CompleteRequestParams.Context? = null
/**
* Sets the argument for completion.
*
* This method specifies the name and value of the argument to be completed.
* This is a required field and must be called exactly once.
*
* Example:
* ```kotlin
* completeRequest {
* argument("query", "SELECT * FROM use")
* // ... other configuration
* }
* ```
*
* @param name The name of the argument to complete
* @param value The partial or full value of the argument
*/
public fun argument(name: String, value: String) {
arg = CompleteRequestParams.Argument(name, value)
}
/**
* Sets the reference to a prompt or resource template.
*
* This method specifies which prompt or resource template the completion request refers to.
*
* Example with prompt:
* ```kotlin
* completeRequest {
* ref(PromptReference("sqlQuery", "SQL Query Builder"))
* // ... other configuration
* }
* ```
*
* Example with resource template:
* ```kotlin
* completeRequest {
* ref(ResourceTemplateReference("file:///{path}"))
* // ... other configuration
* }
* ```
*
* @param value The [Reference] (either [PromptReference] or [ResourceTemplateReference])
*/
public fun ref(value: Reference) {
ref = value
}
/**
* Sets additional context for the completion request using a Map.
*
* This method allows providing additional key-value pairs that may be relevant
* for generating completions. This is an optional field.
*
* Example:
* ```kotlin
* completeRequest {
* context(mapOf("userId" to "123", "role" to "admin"))
* // ... other configuration
* }
* ```
*
* @param arguments A map of context key-value pairs
*/
public fun context(arguments: Map<String, String>) {
ctx = CompleteRequestParams.Context(arguments)
}
/**
* Sets additional context for the completion request using a DSL builder.
*
* This method allows providing additional key-value pairs that may be relevant
* for generating completions using a type-safe builder syntax. This is an optional field.
*
* Example:
* ```kotlin
* completeRequest {
* context {
* put("userId", "123")
* put("role", "admin")
* put("environment", "production")
* }
* // ... other configuration
* }
* ```
*
* @param block Lambda with receiver for building the context map
*/
public fun context(block: MutableMap<String, String>.() -> Unit) {
ctx = CompleteRequestParams.Context(buildMap(block))
}
@PublishedApi
override fun build(): CompleteRequest {
val argument = requireNotNull(arg) {
"Missing required field 'argument(name, value)'. Example: argument(\"query\", \"user input\")"
}
val reference = requireNotNull(ref) {
"Missing required field 'ref(Reference)'. Use ref(PromptReference(\"name\")) or ref(ResourceTemplateReference(\"uri\"))"
}
val params = CompleteRequestParams(argument = argument, ref = reference, context = ctx, meta = meta)
return CompleteRequest(params)
}
}

View file

@ -0,0 +1,272 @@
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 [MediaContent] instances.
*
* This abstract class provides common functionality for all media content builders:
* - [annotations] - Optional metadata annotations
* - [meta] - Optional additional metadata
*
* Concrete implementations:
* - [TextContentBuilder] - For text content
* - [ImageContentBuilder] - For image content (base64-encoded)
* - [AudioContentBuilder] - For audio content (base64-encoded)
*
* @see TextContentBuilder
* @see ImageContentBuilder
* @see AudioContentBuilder
*/
@McpDsl
public abstract class MediaContentBuilder {
protected var annotations: Annotations? = null
protected var meta: JsonObject? = null
/**
* Sets optional annotations for the content.
*
* Annotations provide additional metadata about the content such as audience,
* priority, and last modification time.
*
* Example:
* ```kotlin
* userText {
* text = "Hello"
* annotations(Annotations(priority = 0.8))
* }
* ```
*
* @param annotations The [Annotations] instance
*/
public fun annotations(annotations: Annotations) {
this.annotations = annotations
}
/**
* Sets optional annotations for the content with individual parameters.
*
* Example with all parameters:
* ```kotlin
* userText {
* text = "Important update"
* annotations(
* audience = listOf(Role.User, Role.Assistant),
* priority = 0.8,
* lastModified = "2025-01-12T15:00:58Z"
* )
* }
* ```
*
* @param audience The intended audience for this content (list of [Role])
* @param priority Priority hint for this content (0.0 to 1.0)
* @param lastModified ISO 8601 timestamp of the last modification
* @see Annotations
*/
public fun annotations(audience: List<Role>? = null, priority: Double? = null, lastModified: String? = null) {
this.annotations = Annotations(audience, priority, lastModified)
}
/**
* Sets optional metadata for the content using a DSL builder.
*
* Example:
* ```kotlin
* userText {
* text = "Hello"
* meta {
* put("source", JsonPrimitive("user-input"))
* }
* }
* ```
*
* @param block Lambda for building the metadata
*/
public fun meta(block: JsonObjectBuilder.() -> Unit) {
meta = buildJsonObject(block)
}
@PublishedApi
internal abstract fun build(): MediaContent
}
/**
* DSL builder for constructing [TextContent] instances.
*
* ## Required
* - [text] - The text content string
*
* ## Optional
* - [annotations] - Content annotations (inherited from [MediaContentBuilder])
* - [meta] - Additional metadata (inherited from [MediaContentBuilder])
*
* Example usage in sampling messages:
* ```kotlin
* createMessageRequest {
* maxTokens = 100
* messages {
* userText {
* text = "What is the capital of France?"
* }
* }
* }
* ```
*
* Example with annotations:
* ```kotlin
* userText {
* text = "Important message"
* annotations(Annotations(priority = 1.0))
* }
* ```
*
* @see TextContent
* @see MediaContentBuilder
*/
@McpDsl
public class TextContentBuilder @PublishedApi internal constructor() : MediaContentBuilder() {
/**
* The text content. This is a required field.
*/
public var text: String? = null
override fun build(): TextContent {
val text = requireNotNull(text) {
"Missing required field 'text'. Example: text = \"Hello, world!\""
}
return TextContent(text = text, annotations = annotations, meta = meta)
}
}
/**
* DSL builder for constructing [ImageContent] instances.
*
* ## Required
* - [data] - The base64-encoded image data
* - [mimeType] - The MIME type of the image (e.g., "image/png", "image/jpeg")
*
* ## Optional
* - [annotations] - Content annotations (inherited from [MediaContentBuilder])
* - [meta] - Additional metadata (inherited from [MediaContentBuilder])
*
* Example usage in sampling messages:
* ```kotlin
* createMessageRequest {
* maxTokens = 100
* messages {
* userImage {
* data = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB..."
* mimeType = "image/png"
* }
* }
* }
* ```
*
* Example with annotations:
* ```kotlin
* userImage {
* data = base64EncodedImageData
* mimeType = "image/jpeg"
* annotations(Annotations(priority = 0.9))
* }
* ```
*
* @see ImageContent
* @see MediaContentBuilder
*/
@McpDsl
public class ImageContentBuilder @PublishedApi internal constructor() : MediaContentBuilder() {
/**
* The base64-encoded image data. This is a required field.
*/
public var data: String? = null
/**
* The MIME type of the image (e.g., "image/png", "image/jpeg"). This is a required field.
*/
public var mimeType: String? = null
override fun build(): ImageContent {
val data = requireNotNull(data) {
"Missing required field 'data'. Provide base64-encoded image data"
}
val mime = requireNotNull(mimeType) {
"Missing required field 'mimeType'. Example: mimeType = \"image/png\""
}
return ImageContent(
data = data,
mimeType = mime,
annotations = annotations,
meta = meta,
)
}
}
/**
* DSL builder for constructing [AudioContent] instances.
*
* ## Required
* - [data] - The base64-encoded audio data
* - [mimeType] - The MIME type of the audio (e.g., "audio/wav", "audio/mpeg")
*
* ## Optional
* - [annotations] - Content annotations (inherited from [MediaContentBuilder])
* - [meta] - Additional metadata (inherited from [MediaContentBuilder])
*
* Example usage in sampling messages:
* ```kotlin
* createMessageRequest {
* maxTokens = 100
* messages {
* userAudio {
* data = "UklGRiQAAABXQVZFZm10IBAAAAABAAEA..."
* mimeType = "audio/wav"
* }
* }
* }
* ```
*
* Example with annotations:
* ```kotlin
* userAudio {
* data = base64EncodedAudioData
* mimeType = "audio/mpeg"
* annotations(Annotations(priority = 0.7))
* }
* ```
*
* @see AudioContent
* @see MediaContentBuilder
*/
@McpDsl
public class AudioContentBuilder @PublishedApi internal constructor() : MediaContentBuilder() {
/**
* The base64-encoded audio data. This is a required field.
*/
public var data: String? = null
/**
* The MIME type of the audio (e.g., "audio/wav", "audio/mpeg"). This is a required field.
*/
public var mimeType: String? = null
override fun build(): AudioContent {
val data = requireNotNull(data) {
"Missing required field 'data'. Provide base64-encoded audio data"
}
val mime = requireNotNull(mimeType) {
"Missing required field 'mimeType'. Example: mimeType = \"audio/wav\""
}
return AudioContent(
data = data,
mimeType = mime,
annotations = annotations,
meta = meta,
)
}
}

View file

@ -0,0 +1,228 @@
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
/**
* 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
internal inline fun buildElicitRequest(block: ElicitRequestBuilder.() -> Unit): ElicitRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ElicitRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [ElicitRequest] instances.
*
* This builder is used to create requests that prompt users for structured input
* through forms or dialogs presented by the client.
*
* ## Required
* - [message] - The message to present to the user explaining what is being requested
* - [requestedSchema] - Schema defining the structure of the data to collect
*
* ## Optional
* - [meta] - Metadata for the request
*
* @see buildElicitRequest
* @see ElicitRequest
*/
@McpDsl
public class ElicitRequestBuilder @PublishedApi internal constructor() : RequestBuilder() {
/**
* The message to present to the user. This should clearly explain what information
* is being requested and why. This is a required field.
*/
public var message: String? = null
private var requestedSchema: ElicitRequestParams.RequestedSchema? = null
/**
* Sets the requested schema directly.
*
* Example:
* ```kotlin
* buildElicitRequest {
* message = "Enter details"
* requestedSchema(ElicitRequestParams.RequestedSchema(
* properties = buildJsonObject {
* put("name", JsonObject(mapOf("type" to JsonPrimitive("string"))))
* }
* ))
* }
* ```
*
* @param schema The [ElicitRequestParams.RequestedSchema] instance
*/
public fun requestedSchema(schema: ElicitRequestParams.RequestedSchema) {
requestedSchema = schema
}
/**
* Sets the requested schema using a DSL builder.
*
* This is the recommended way to define schemas. The schema defines the structure
* of data to be collected from the user, supporting only top-level primitive properties.
*
* Example:
* ```kotlin
* buildElicitRequest {
* message = "Configure settings"
* requestedSchema {
* properties {
* put("enabled", JsonObject(mapOf(
* "type" to JsonPrimitive("boolean"),
* "description" to JsonPrimitive("Enable feature")
* )))
* put("apiKey", JsonObject(mapOf(
* "type" to JsonPrimitive("string")
* )))
* }
* required = listOf("apiKey")
* }
* }
* ```
*
* @param block Lambda for building the schema
* @see ElicitRequestedSchemaBuilder
*/
public fun requestedSchema(block: ElicitRequestedSchemaBuilder.() -> Unit) {
requestedSchema = ElicitRequestedSchemaBuilder().apply(block).build()
}
@PublishedApi
override fun build(): ElicitRequest {
val message = requireNotNull(message) {
"Missing required field 'message'. Example: message = \"Please enter your name\""
}
val requestedSchema = requireNotNull(requestedSchema) {
"Missing required field 'requestedSchema'. Use requestedSchema { properties { ... } }"
}
val params = ElicitRequestParams(message = message, requestedSchema = requestedSchema, meta = meta)
return ElicitRequest(params)
}
}
/**
* DSL builder for constructing [ElicitRequestParams.RequestedSchema] instances.
*
* Defines the JSON Schema structure for data to be collected from the user.
* Only supports top-level primitive properties.
*
* ## Required
* - [properties] - Schema definitions for each field
*
* ## Optional
* - [required] - List of required property names
*
* Example:
* ```kotlin
* requestedSchema {
* properties {
* put("username", JsonObject(mapOf(
* "type" to JsonPrimitive("string"),
* "description" to JsonPrimitive("Your username")
* )))
* }
* required = listOf("username")
* }
* ```
*
* @see ElicitRequestParams.RequestedSchema
*/
@McpDsl
public class ElicitRequestedSchemaBuilder @PublishedApi internal constructor() {
private var properties: JsonObject? = null
/**
* List of required property names. If null, all fields are optional.
*/
public var required: List<String>? = null
/**
* Sets the schema properties directly.
*
* @param properties JsonObject containing property schemas
*/
public fun properties(properties: JsonObject) {
this.properties = properties
}
/**
* Sets the schema properties using a DSL builder.
*
* Example:
* ```kotlin
* properties {
* put("email", JsonObject(mapOf(
* "type" to JsonPrimitive("string")
* )))
* }
* ```
*
* @param block Lambda for building the properties
*/
public fun properties(block: JsonObjectBuilder.() -> Unit): Unit = properties(buildJsonObject(block))
@PublishedApi
internal fun build(): ElicitRequestParams.RequestedSchema {
val properties = requireNotNull(properties) {
"Missing required field 'properties'. Use properties { put(\"fieldName\", schema) }"
}
return ElicitRequestParams.RequestedSchema(properties, required)
}
}

View file

@ -0,0 +1,206 @@
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.
*
* ## 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
internal inline fun buildInitializeRequest(block: InitializeRequestBuilder.() -> Unit): InitializeRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return InitializeRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [InitializeRequest] instances.
*
* This builder creates the initial handshake request to establish
* an MCP protocol connection between client and server.
*
* ## Required
* - [protocolVersion] - MCP protocol version string
* - [capabilities] - Client capabilities configuration
* - [info] - Client implementation details
*
* ## Optional
* - [meta] - Metadata for the request
*
* @see buildInitializeRequest
* @see InitializeRequest
*/
@McpDsl
public class InitializeRequestBuilder @PublishedApi internal constructor() : RequestBuilder() {
/**
* The MCP protocol version. This is a required field.
*
* Example: `protocolVersion = "2024-11-05"`
*/
public var protocolVersion: String? = null
private var capabilities: ClientCapabilities? = null
private var info: Implementation? = null
/**
* Sets client capabilities directly.
*
* Example:
* ```kotlin
* capabilities(ClientCapabilities(
* sampling = ClientCapabilities.sampling,
* roots = ClientCapabilities.Roots(listChanged = true)
* ))
* ```
*
* @param value The [ClientCapabilities] instance
*/
public fun capabilities(value: ClientCapabilities) {
capabilities = value
}
/**
* Sets client capabilities using a DSL builder.
*
* This is the recommended way to configure capabilities.
*
* Example:
* ```kotlin
* capabilities {
* sampling(ClientCapabilities.sampling)
* roots(listChanged = true)
* elicitation(ClientCapabilities.elicitation)
* }
* ```
*
* @param block Lambda for building capabilities
* @see ClientCapabilitiesBuilder
*/
public fun capabilities(block: ClientCapabilitiesBuilder.() -> Unit) {
capabilities = ClientCapabilitiesBuilder().apply(block).build()
}
/**
* Sets client implementation info directly.
*
* Example:
* ```kotlin
* info(Implementation(
* name = "MyClient",
* version = "1.0.0"
* ))
* ```
*
* @param value The [Implementation] instance
*/
public fun info(value: Implementation) {
info = value
}
/**
* Sets client implementation info using individual parameters.
*
* This is the recommended way to configure client info.
*
* Example with required fields only:
* ```kotlin
* info("MyClient", "1.0.0")
* ```
*
* Example with all fields:
* ```kotlin
* info(
* name = "MyClient",
* version = "2.0.0",
* title = "My MCP Client",
* websiteUrl = "https://example.com",
* icons = listOf(Icon(
* src = "https://example.com/icon.png",
* mimeType = "image/png"
* ))
* )
* ```
*
* @param name Client implementation name
* @param version Client version string
* @param title Optional human-readable title
* @param websiteUrl Optional URL to client website
* @param icons Optional list of client icons
*/
public fun info(
name: String,
version: String,
title: String? = null,
websiteUrl: String? = null,
icons: List<Icon>? = null,
) {
info = Implementation(name = name, version = version, title = title, websiteUrl = websiteUrl, icons = icons)
}
@PublishedApi
override fun build(): InitializeRequest {
val version = requireNotNull(protocolVersion) {
"Missing required field 'protocolVersion'. Example: protocolVersion = \"2024-11-05\""
}
val capabilities = requireNotNull(capabilities) {
"Missing required field 'capabilities'. Use capabilities { ... }"
}
val info = requireNotNull(info) {
"Missing required field 'info'. Use info(\"clientName\", \"1.0.0\")"
}
val params = InitializeRequestParams(
protocolVersion = version,
capabilities = capabilities,
clientInfo = info,
meta = meta,
)
return InitializeRequest(params = params)
}
}

View file

@ -0,0 +1,87 @@
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.
*
* ## 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
internal inline fun buildSetLevelRequest(block: SetLevelRequestBuilder.() -> Unit): SetLevelRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return SetLevelRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [SetLevelRequest] instances.
*
* This builder creates requests to set the logging level for the server.
*
* ## Required
* - [loggingLevel] - The logging level to set
*
* ## Optional
* - [meta] - Metadata for the request
*
* @see buildSetLevelRequest
* @see SetLevelRequest
* @see LoggingLevel
*/
@McpDsl
public class SetLevelRequestBuilder @PublishedApi internal constructor() : RequestBuilder() {
/**
* The logging level to set. This is a required field.
*
* Available levels (from least to most severe):
* - [LoggingLevel.Debug]
* - [LoggingLevel.Info]
* - [LoggingLevel.Notice]
* - [LoggingLevel.Warning]
* - [LoggingLevel.Error]
* - [LoggingLevel.Critical]
* - [LoggingLevel.Alert]
* - [LoggingLevel.Emergency]
*
* Example: `loggingLevel = LoggingLevel.Warning`
*/
public var loggingLevel: LoggingLevel? = null
@PublishedApi
override fun build(): SetLevelRequest {
val level = requireNotNull(loggingLevel) {
"Missing required field 'loggingLevel'. Example: loggingLevel = LoggingLevel.Info"
}
val params = SetLevelRequestParams(level = level, meta = meta)
return SetLevelRequest(params = params)
}
}

View file

@ -0,0 +1,59 @@
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.
*
* ## 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
internal inline fun buildPingRequest(block: PingRequestBuilder.() -> Unit): PingRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return PingRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [PingRequest] instances.
*
* This builder creates ping requests to check connection status with the server.
* All fields are optional.
*
* ## Optional
* - [meta] - Metadata for the request
*
* @see buildPingRequest
* @see PingRequest
*/
@McpDsl
public class PingRequestBuilder @PublishedApi internal constructor() : RequestBuilder() {
@PublishedApi
override fun build(): PingRequest {
val params = meta?.let { BaseRequestParams(meta = it) }
return PingRequest(params)
}
}

View file

@ -0,0 +1,145 @@
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 [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)
@ExperimentalMcpApi
internal inline fun buildGetPromptRequest(block: GetPromptRequestBuilder.() -> Unit): GetPromptRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return GetPromptRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [GetPromptRequest] instances.
*
* This builder retrieves a specific prompt by name, optionally with arguments.
*
* ## Required
* - [name] - The name of the prompt to retrieve
*
* ## Optional
* - [arguments] - Arguments to pass to the prompt
* - [meta] - Metadata for the request
*
* @see buildGetPromptRequest
* @see GetPromptRequest
*/
@McpDsl
public class GetPromptRequestBuilder @PublishedApi internal constructor() : RequestBuilder() {
/**
* The name of the prompt to retrieve. This is a required field.
*
* Example: `name = "greeting"`
*/
public var name: String? = null
/**
* Optional arguments to pass to the prompt.
*
* Example:
* ```kotlin
* arguments = mapOf("userId" to "123", "lang" to "en")
* ```
*/
public var arguments: Map<String, String>? = null
@PublishedApi
override fun build(): GetPromptRequest {
val name = requireNotNull(name) {
"Missing required field 'name'. Example: name = \"promptName\""
}
val params = GetPromptRequestParams(name = name, arguments = arguments, meta = meta)
return GetPromptRequest(params)
}
}
/**
* 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
internal inline fun buildListPromptsRequest(block: ListPromptsRequestBuilder.() -> Unit): ListPromptsRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ListPromptsRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [ListPromptsRequest] instances.
*
* This builder retrieves a list of available prompts, with optional pagination support.
* All fields are optional.
*
* ## Optional
* - [cursor] - Pagination cursor (inherited from [PaginatedRequestBuilder])
* - [meta] - Metadata for the request (inherited from [RequestBuilder])
*
* @see buildListPromptsRequest
* @see ListPromptsRequest
* @see PaginatedRequestBuilder
*/
@McpDsl
public class ListPromptsRequestBuilder @PublishedApi internal constructor() : PaginatedRequestBuilder() {
@PublishedApi
override fun build(): ListPromptsRequest {
val params = paginatedRequestParams(cursor = cursor, meta = meta)
return ListPromptsRequest(params)
}
}

View file

@ -0,0 +1,208 @@
package io.modelcontextprotocol.kotlin.sdk.types
import kotlinx.serialization.json.JsonArrayBuilder
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonObjectBuilder
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonArray
import kotlinx.serialization.json.buildJsonObject
/**
* Base DSL builder for constructing MCP request instances.
*
* This abstract class provides common functionality for all request builders,
* including optional metadata support.
*
* All concrete request builder classes extend this base to inherit [meta] functionality.
*
* @see RequestMetaBuilder
* @see PaginatedRequestBuilder
*/
@McpDsl
public abstract class RequestBuilder {
protected var meta: RequestMeta? = null
/**
* Sets request metadata using a DSL builder.
*
* Metadata can include progress tokens and custom fields for tracking requests.
*
* Example:
* ```kotlin
* initializeRequest {
* protocolVersion = "2024-11-05"
* meta {
* progressToken("progress-123")
* put("customField", "value")
* }
* // ... other configuration
* }
* ```
*
* @param builderAction Lambda for building request metadata
* @see RequestMetaBuilder
*/
public fun meta(builderAction: RequestMetaBuilder.() -> Unit) {
meta = RequestMetaBuilder().apply(builderAction).build()
}
internal abstract fun build(): Request
}
/**
* DSL builder for constructing [RequestMeta] instances.
*
* This builder creates metadata for MCP requests, supporting progress tokens
* and custom key-value pairs.
*
* Example:
* ```kotlin
* meta {
* progressToken("progress-123")
* put("requestId", "req-456")
* put("priority", 1)
* putJsonObject("context") {
* put("source", "api")
* }
* }
* ```
*
* @see RequestMeta
* @see RequestBuilder.meta
*/
@McpDsl
public class RequestMetaBuilder internal constructor() {
private val content: MutableMap<String, JsonElement> = linkedMapOf()
/**
* Sets the progress token as a string.
*
* Progress tokens are used for out-of-band progress notifications.
*
* Example: `progressToken("progress-123")`
*/
public fun progressToken(value: String) {
content["progressToken"] = JsonPrimitive(value)
}
/**
* Sets the progress token as a `Long`.
*
* Example: `progressToken(123L)`
*/
public fun progressToken(value: Long) {
content["progressToken"] = JsonPrimitive(value)
}
/**
* Sets the progress token as an Int.
*
* Example: `progressToken(123)`
*/
public fun progressToken(value: Int) {
content["progressToken"] = JsonPrimitive(value)
}
/**
* Adds a custom metadata field with a JsonElement value.
*
* @param key The metadata field name
* @param value The JsonElement value
* @return The previous value associated with the key, or null
*/
public fun put(key: String, value: JsonElement): JsonElement? = content.put(key, value)
/**
* Adds a custom metadata field with a String value.
*
* Example: `put("requestId", "req-456")`
*/
public fun put(key: String, value: String): JsonElement? = put(key, JsonPrimitive(value))
/**
* Adds a custom metadata field with a Number value.
*
* Example: `put("priority", 1)`
*/
public fun put(key: String, value: Number): JsonElement? = put(key, JsonPrimitive(value))
/**
* Adds a custom metadata field with a Boolean value.
*
* Example: `put("urgent", true)`
*/
public fun put(key: String, value: Boolean): JsonElement? = put(key, JsonPrimitive(value))
/**
* Adds a custom metadata field with a null value.
*
* Example: `put("optionalField", null)`
*/
@Suppress("UNUSED_PARAMETER")
public fun put(key: String, value: Nothing?): JsonElement? = put(key, JsonNull)
/**
* Adds a custom metadata field with a JsonObject value using a DSL builder.
*
* Example:
* ```kotlin
* putJsonObject("context") {
* put("source", "api")
* put("version", 2)
* }
* ```
*
* @param key The metadata field name
* @param builderAction Lambda for building the JsonObject
*/
public fun putJsonObject(key: String, builderAction: JsonObjectBuilder.() -> Unit): JsonElement? =
put(key, buildJsonObject(builderAction))
/**
* Adds a custom metadata field with a JsonArray value using a DSL builder.
*
* Example:
* ```kotlin
* putJsonArray("tags") {
* add("important")
* add("urgent")
* }
* ```
*
* @param key The metadata field name
* @param builderAction Lambda for building the JsonArray
*/
public fun putJsonArray(key: String, builderAction: JsonArrayBuilder.() -> Unit): JsonElement? =
put(key, buildJsonArray(builderAction))
internal fun build(): RequestMeta = RequestMeta(JsonObject(content))
}
/**
* Base DSL builder for constructing paginated MCP request instances.
*
* This abstract class extends [RequestBuilder] and adds pagination support
* through an optional cursor field.
*
* Example:
* ```kotlin
* listPromptsRequest {
* cursor = "eyJwYWdlIjogMn0="
* }
* ```
*
* @see RequestBuilder
*/
@McpDsl
public abstract class PaginatedRequestBuilder : RequestBuilder() {
/**
* Optional pagination cursor for fetching the next page of results.
*
* The cursor value is typically obtained from the previous page's response.
*
* Example: `cursor = "eyJwYWdlIjogMn0="`
*/
public var cursor: String? = null
}

View file

@ -0,0 +1,302 @@
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 [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
internal inline fun buildListResourcesRequest(block: ListResourcesRequestBuilder.() -> Unit): ListResourcesRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ListResourcesRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [ListResourcesRequest] instances.
*
* This builder retrieves a list of available resources, with optional pagination support.
* All fields are optional.
*
* ## Optional
* - [cursor] - Pagination cursor (inherited from [PaginatedRequestBuilder])
* - [meta] - Metadata for the request (inherited from [RequestBuilder])
*
* @see buildListResourcesRequest
* @see ListResourcesRequest
* @see PaginatedRequestBuilder
*/
@McpDsl
public class ListResourcesRequestBuilder @PublishedApi internal constructor() : PaginatedRequestBuilder() {
@PublishedApi
override fun build(): ListResourcesRequest {
val params = paginatedRequestParams(cursor = cursor, meta = meta)
return ListResourcesRequest(params)
}
}
/**
* Creates a [ReadResourceRequest] using a type-safe DSL builder.
*
* ## Required
* - [uri][ReadResourceRequestBuilder.uri] - The URI of the resource to read
*
* ## Optional
* - [meta][ReadResourceRequestBuilder.meta] - Metadata for the request
*
* Example:
* ```kotlin
* val request = buildReadResourceRequest {
* uri = "file:///path/to/resource.txt"
* }
* ```
*
* @param block Configuration lambda for setting up the read resource request
* @return A configured [ReadResourceRequest] instance
* @see ReadResourceRequestBuilder
* @see ReadResourceRequest
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildReadResourceRequest(block: ReadResourceRequestBuilder.() -> Unit): ReadResourceRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return 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 buildReadResourceRequest
* @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.
*
* ## 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
internal 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 buildSubscribeRequest
* @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 = 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
internal 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 buildUnsubscribeRequest
* @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)
}
}
/**
* Creates a [ListResourceTemplatesRequest] using a type-safe DSL builder.
*
* ## Optional
* - [cursor][ListResourceTemplatesRequestBuilder.cursor] - Pagination cursor for fetching next page
* - [meta][ListResourceTemplatesRequestBuilder.meta] - Metadata for the request
*
* Example without pagination:
* ```kotlin
* val request = buildListResourceTemplatesRequest { }
* ```
*
* Example with pagination:
* ```kotlin
* val request = buildListResourceTemplatesRequest {
* cursor = "eyJwYWdlIjogMn0="
* }
* ```
*
* @param block Configuration lambda for setting up the list resource templates request
* @return A configured [ListResourceTemplatesRequest] instance
* @see ListResourceTemplatesRequestBuilder
* @see ListResourceTemplatesRequest
*/
@OptIn(ExperimentalContracts::class)
@ExperimentalMcpApi
internal inline fun buildListResourceTemplatesRequest(
block: ListResourceTemplatesRequestBuilder.() -> Unit,
): ListResourceTemplatesRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ListResourceTemplatesRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [ListResourceTemplatesRequest] instances.
*
* This builder retrieves a list of available resource templates, with optional pagination support.
* All fields are optional.
*
* ## Optional
* - [cursor] - Pagination cursor (inherited from [PaginatedRequestBuilder])
* - [meta] - Metadata for the request (inherited from [RequestBuilder])
*
* @see buildListResourceTemplatesRequest
* @see ListResourceTemplatesRequest
* @see PaginatedRequestBuilder
*/
@McpDsl
public class ListResourceTemplatesRequestBuilder @PublishedApi internal constructor() : PaginatedRequestBuilder() {
@PublishedApi
override fun build(): ListResourceTemplatesRequest {
val params = paginatedRequestParams(cursor = cursor, meta = meta)
return ListResourceTemplatesRequest(params)
}
}

View file

@ -0,0 +1,59 @@
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.
*
* ## 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
internal inline fun buildListRootsRequest(block: ListRootsRequestBuilder.() -> Unit): ListRootsRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ListRootsRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [ListRootsRequest] instances.
*
* This builder retrieves the list of root URIs provided by the client.
* All fields are optional.
*
* ## Optional
* - [meta] - Metadata for the request
*
* @see buildListRootsRequest
* @see ListRootsRequest
*/
@McpDsl
public class ListRootsRequestBuilder @PublishedApi internal constructor() : RequestBuilder() {
@PublishedApi
override fun build(): ListRootsRequest {
val params = meta?.let { BaseRequestParams(meta = it) }
return ListRootsRequest(params)
}
}

View file

@ -0,0 +1,394 @@
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
/**
* 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
internal inline fun buildCreateMessageRequest(block: CreateMessageRequestBuilder.() -> Unit): CreateMessageRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return CreateMessageRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [CreateMessageRequest] instances.
*
* This builder creates LLM sampling requests with conversation history.
*
* ## Required
* - [maxTokens] - Maximum number of tokens to generate
* - [messages] - List of conversation messages
*
* ## Optional
* - [systemPrompt] - System-level instructions
* - [context] - Context inclusion settings
* - [temperature] - Sampling temperature (0.0-1.0)
* - [stopSequences] - Sequences that stop generation
* - [preferences] - Model selection preferences
* - [metadata] - Additional metadata
* - [meta] - Request metadata
*
* @see buildCreateMessageRequest
* @see CreateMessageRequest
*/
@McpDsl
public class CreateMessageRequestBuilder @PublishedApi internal constructor() : RequestBuilder() {
/** Maximum number of tokens to generate. This is a required field. */
public var maxTokens: Int? = null
/** Optional system-level instructions for the model. */
public var systemPrompt: String? = null
/** Optional context inclusion settings. */
public var context: IncludeContext? = null
/** Optional sampling temperature (0.0-1.0). Lower values are more deterministic. */
public var temperature: Double? = null
/** Optional sequences that will stop generation when encountered. */
public var stopSequences: List<String>? = null
private var messages: List<SamplingMessage>? = null
private var preferences: ModelPreferences? = null
private var metadata: JsonObject? = null
/**
* Sets messages directly from a list.
*
* Example:
* ```kotlin
* messages(listOf(
* SamplingMessage(Role.User, TextContent("Hello")),
* SamplingMessage(Role.Assistant, TextContent("Hi!"))
* ))
* ```
*/
public fun messages(messages: List<SamplingMessage>) {
this.messages = messages
}
/**
* Sets messages using a DSL builder.
*
* This is the recommended way to define conversation messages.
*
* Example:
* ```kotlin
* messages {
* user { "What is 2+2?" }
* assistant { "2+2 equals 4." }
* user { "What about 3+3?" }
* }
* ```
*/
public fun messages(block: SamplingMessageBuilder.() -> Unit) {
this.messages = SamplingMessageBuilder().apply(block).build()
}
/**
* Sets model preferences directly.
*/
public fun preferences(value: ModelPreferences) {
this.preferences = value
}
/**
* Sets model selection preferences using individual parameters.
*
* Example:
* ```kotlin
* preferences(
* hints = listOf("claude-3-sonnet"),
* intelligence = 0.9,
* speed = 0.5
* )
* ```
*
* @param hints Model name hints for selection
* @param cost Cost optimization priority (0.0-1.0)
* @param speed Speed optimization priority (0.0-1.0)
* @param intelligence Intelligence optimization priority (0.0-1.0)
*/
public fun preferences(
hints: List<String>? = null,
cost: Double? = null,
speed: Double? = null,
intelligence: Double? = null,
) {
this.preferences = ModelPreferences(
hints = hints?.map { ModelHint(it) },
costPriority = cost,
speedPriority = speed,
intelligencePriority = intelligence,
)
}
/**
* Sets additional metadata using a DSL builder.
*
* Example:
* ```kotlin
* metadata {
* put("userId", "123")
* put("sessionId", "abc")
* }
* ```
*/
public fun metadata(block: JsonObjectBuilder.() -> Unit) {
this.metadata = buildJsonObject(block)
}
@PublishedApi
override fun build(): CreateMessageRequest {
val maxTokens = requireNotNull(maxTokens) {
"Missing required field 'maxTokens'. Example: maxTokens = 1000"
}
val messages = requireNotNull(messages) {
"Missing required field 'messages'. Use messages { user { \"text\" } }"
}
val params = CreateMessageRequestParams(
maxTokens = maxTokens,
messages = messages,
modelPreferences = preferences,
systemPrompt = systemPrompt,
includeContext = context,
temperature = temperature,
stopSequences = stopSequences,
metadata = metadata,
meta = meta,
)
return CreateMessageRequest(params)
}
}
/**
* DSL builder for constructing lists of [SamplingMessage] instances.
*
* This builder creates a conversation history for LLM sampling requests.
*
* Example:
* ```kotlin
* messages {
* user { "Hello!" }
* assistant { "Hi! How can I help?" }
* user { "What's 2+2?" }
* }
* ```
*
* @see SamplingMessage
* @see CreateMessageRequestBuilder.messages
*/
@McpDsl
public class SamplingMessageBuilder @PublishedApi internal constructor() {
private val messages = mutableListOf<SamplingMessage>()
/**
* Adds a user message with the specified content.
*
* Example:
* ```kotlin
* user(TextContent("Hello"))
* ```
*/
public fun user(content: MediaContent) {
messages.add(SamplingMessage(Role.User, content))
}
/**
* Adds an assistant message with the specified content.
*
* Example:
* ```kotlin
* assistant(TextContent("Hi there!"))
* ```
*/
public fun assistant(content: MediaContent) {
messages.add(SamplingMessage(Role.Assistant, content))
}
@PublishedApi
internal fun build(): List<SamplingMessage> = messages
}
/**
* Adds a user message with simple text content.
*
* Example:
* ```kotlin
* messages {
* user { "What is the weather today?" }
* }
* ```
*/
@ExperimentalMcpApi
public fun SamplingMessageBuilder.user(block: () -> String): Unit = this.user(TextContent(text = block()))
/**
* Adds a user message with text content using a DSL builder.
*
* Example:
* ```kotlin
* messages {
* userText {
* text = "Important message"
* annotations(priority = 1.0)
* }
* }
* ```
*/
@ExperimentalMcpApi
public fun SamplingMessageBuilder.userText(block: TextContentBuilder.() -> Unit): Unit =
this.user(TextContentBuilder().apply(block).build())
/**
* Adds a user message with image content.
*
* Example:
* ```kotlin
* messages {
* userImage {
* data = base64ImageData
* mimeType = "image/png"
* }
* }
* ```
*/
@ExperimentalMcpApi
public fun SamplingMessageBuilder.userImage(block: ImageContentBuilder.() -> Unit): Unit =
this.user(ImageContentBuilder().apply(block).build())
/**
* Adds a user message with audio content.
*
* Example:
* ```kotlin
* messages {
* userAudio {
* data = base64AudioData
* mimeType = "audio/wav"
* }
* }
* ```
*/
@ExperimentalMcpApi
public fun SamplingMessageBuilder.userAudio(block: AudioContentBuilder.() -> Unit): Unit =
this.user(AudioContentBuilder().apply(block).build())
/**
* Adds an assistant message with simple text content.
*
* Example:
* ```kotlin
* messages {
* assistant { "The weather is sunny today." }
* }
* ```
*/
@ExperimentalMcpApi
public fun SamplingMessageBuilder.assistant(block: () -> String): Unit = this.assistant(TextContent(text = block()))
/**
* Adds an assistant message with text content using a DSL builder.
*
* Example:
* ```kotlin
* messages {
* assistantText {
* text = "Here's the answer"
* annotations(priority = 0.8)
* }
* }
* ```
*/
@ExperimentalMcpApi
public fun SamplingMessageBuilder.assistantText(block: TextContentBuilder.() -> Unit): Unit =
this.assistant(TextContentBuilder().apply(block).build())
/**
* Adds an assistant message with image content.
*
* Example:
* ```kotlin
* messages {
* assistantImage {
* data = generatedImageData
* mimeType = "image/jpeg"
* }
* }
* ```
*/
@ExperimentalMcpApi
public fun SamplingMessageBuilder.assistantImage(block: ImageContentBuilder.() -> Unit): Unit =
this.assistant(ImageContentBuilder().apply(block).build())
/**
* Adds an assistant message with audio content.
*
* Example:
* ```kotlin
* messages {
* assistantAudio {
* data = generatedAudioData
* mimeType = "audio/mpeg"
* }
* }
* ```
*/
@ExperimentalMcpApi
public fun SamplingMessageBuilder.assistantAudio(block: AudioContentBuilder.() -> Unit): Unit =
this.assistant(AudioContentBuilder().apply(block).build())

View file

@ -0,0 +1,172 @@
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
/**
* 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
internal inline fun buildCallToolRequest(block: CallToolRequestBuilder.() -> Unit): CallToolRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return CallToolRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [CallToolRequest] instances.
*
* This builder invokes a specific tool by name with optional arguments.
*
* ## Required
* - [name] - The name of the tool to call
*
* ## Optional
* - [arguments] - Arguments to pass to the tool
* - [meta] - Metadata for the request
*
* @see buildCallToolRequest
* @see CallToolRequest
*/
@McpDsl
public class CallToolRequestBuilder @PublishedApi internal constructor() : RequestBuilder() {
/**
* The name of the tool to call. This is a required field.
*
* Example: `name = "getCurrentTime"`
*/
public var name: String? = null
private var arguments: JsonObject? = null
/**
* Sets tool arguments directly from a JsonObject.
*
* Example:
* ```kotlin
* arguments(buildJsonObject {
* put("query", "users")
* })
* ```
*/
public fun arguments(arguments: JsonObject) {
this.arguments = arguments
}
/**
* Sets tool arguments using a DSL builder.
*
* This is the recommended way to provide tool arguments.
*
* Example:
* ```kotlin
* arguments {
* put("query", "SELECT * FROM users")
* put("limit", 100)
* put("includeDeleted", false)
* }
* ```
*
* @param block Lambda for building the arguments JsonObject
*/
public fun arguments(block: JsonObjectBuilder.() -> Unit): Unit = arguments(buildJsonObject(block))
@PublishedApi
override fun build(): CallToolRequest {
val name = requireNotNull(name) {
"Missing required field 'name'. Example: name = \"toolName\""
}
val params = CallToolRequestParams(name = name, arguments = arguments, meta = meta)
return CallToolRequest(params)
}
}
/**
* 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
internal inline fun buildListToolsRequest(block: ListToolsRequestBuilder.() -> Unit): ListToolsRequest {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return ListToolsRequestBuilder().apply(block).build()
}
/**
* DSL builder for constructing [ListToolsRequest] instances.
*
* This builder retrieves a list of available tools, with optional pagination support.
* All fields are optional.
*
* ## Optional
* - [cursor] - Pagination cursor (inherited from [PaginatedRequestBuilder])
* - [meta] - Metadata for the request (inherited from [RequestBuilder])
*
* @see buildListToolsRequest
* @see ListToolsRequest
* @see PaginatedRequestBuilder
*/
@McpDsl
public class ListToolsRequestBuilder @PublishedApi internal constructor() : PaginatedRequestBuilder() {
@PublishedApi
override fun build(): ListToolsRequest {
val params = paginatedRequestParams(cursor = cursor, meta = meta)
return ListToolsRequest(params)
}
}