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