Skip to content

Conversation

@devcrocod
Copy link
Contributor

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:

// 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:

  • initializeRequestbuildInitializeRequest
  • callToolRequestbuildCallToolRequest
  • createMessageRequestbuildCreateMessageRequest

This would make it clearer that these are builder functions.

  1. Runtime vs Compile-Time Validation

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

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:

fun initializeRequest(
      protocolVersion: String,
      capabilities: ClientCapabilities,
      info: Implementation,
      block: InitializeRequestBuilder.() -> Unit = {}
  ): InitializeRequest
// 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:
// 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

Copy link
Contributor

@kpavlov kpavlov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@devcrocod, this looks waaaaay nicer than the current näive API!

Let's also hide requests behind API DSL methods, as curring lambda parameters, like:

@OptIn(ExperimentalMcpApi::class)
public suspend fun listTools(options: RequestOptions? = null, block: ListToolsRequestBuilder.() -> Unit) {
    listTools(listToolsRequest(block), options)
}

with usage:

client.listTools {
    meta {
        put("foo", "bar")
    }
    cursor = "cccccclulbkukecgbkvvlevelhvejdlitfdutevlgueh"
}

❗️Let's ask our silicone friends 🤖 to create a comprehensive test suite verifying that DSL api fully translates to our standard API. Mockk tests will be sufficient.
Tests will serve as the first examples of how the new API can be utilized.

@kpavlov kpavlov added the enhancement New feature or request label Nov 19, 2025
@devcrocod devcrocod force-pushed the devcrocod/request-dsl-builders branch from 33ac9f3 to 59759ed Compare November 20, 2025 18:03
@devcrocod devcrocod marked this pull request as ready for review November 20, 2025 18:03
Copilot AI review requested due to automatic review settings November 20, 2025 18:03
Copilot finished reviewing on behalf of devcrocod November 20, 2025 18:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces type-safe DSL builders for all MCP request types, providing a more ergonomic way to construct requests with runtime validation of required fields. The implementation includes builders for initialization, tools, prompts, resources, sampling, elicitation, completion, logging, roots, and ping requests.

Key Changes

  • Adds DSL builder functions (buildXxxRequest) and builder classes for all MCP request types
  • Introduces @McpDsl annotation to prevent scope leakage in nested builders
  • Provides base classes RequestBuilder and PaginatedRequestBuilder for common functionality
  • Implements runtime validation with helpful error messages for required fields

Reviewed Changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated no comments.

Show a summary per file
File Description
McpDsl.kt Introduces DSL marker annotation for type-safe builders
request.dsl.kt Base builder classes and request metadata builder
capabilities.dsl.kt Client capabilities builder for initialize requests
content.dsl.kt Media content builders (text, image, audio)
initialize.dsl.kt Initialize request builder with capability configuration
tools.dsl.kt Call tool and list tools request builders
prompts.dsl.kt Get prompt and list prompts request builders
resources.dsl.kt Resource-related request builders (list, read, subscribe, unsubscribe, templates)
sampling.dsl.kt Create message request builder with conversation messages
elicitation.dsl.kt Elicit request builder with schema validation
completion.dsl.kt Completion request builder
logging.dsl.kt Set logging level request builder
roots.dsl.kt List roots request builder
pingRequest.dsl.kt Ping request builder
kotlin-sdk-core.api API surface changes for new public builder classes

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@kpavlov kpavlov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool-cool, let's merge it!

@devcrocod devcrocod force-pushed the devcrocod/request-dsl-builders branch from 59759ed to 7e0d4fc Compare November 20, 2025 18:21
@devcrocod devcrocod merged commit 74c9374 into main Nov 20, 2025
4 checks passed
@devcrocod devcrocod deleted the devcrocod/request-dsl-builders branch November 20, 2025 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants