modelcontextprotocol / modelcontextprotocol/kotlin-sdk

[Proposal] SEP-2322: Multi Round-Trip Requests (MRTR) architecture & flow design (#808)

Open
#1,005 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Kotlin
Stars
1.5k
Forks
248
Avg merge
1d 20h
Merged PRs (30d)
23

Description

Proposal: SEP-2322 Multi Round-Trip Requests (MRTR) Architecture & Flow Design (#808)
Motivation & Context

Tracking implementation of SEP-2322 in kotlin-sdk for the 2026-07-28 MCP specification release (aligned with umbrella issue #842).

In the original MCP design, interactive flows (such as tool-driven elicitation or server-initiated sampling) relied on in-line coroutine suspension waiting on an active bidirectional SSE stream. While workable for single-process local servers, this model failed in modern distributed deployments:

  1. Stateless HTTP & Scaled Backends: Multi-instance servers behind load balancers cannot guarantee that the in-line callback returns to the identical server process holding the suspended thread/coroutine.
  2. Compatibility with SEP-2260: SEP-2260 strictly prohibits unsolicited server-to-client requests; intermediate requests must be formally linked to the parent client call.
  3. Connection Resilience: Network dropouts during long user elicitation prompts would terminate the entire tool execution and fail the operation unrecoverably.

SEP-2322 defines Multi Round-Trip Requests (MRTR):

  • When a tool requires additional client input (e.g. elicitation/create or sampling/createMessage), the server returns an intermediate response with resultType: "input_required", containing named inputRequests: Map<String, ServerRequest> and an opaque, tamper-evident requestState.
  • The client resolves the requested inputs and resumes the execution:
    • Ephemeral flow: Re-invokes tools/call with inputResponses: Map<String, InputResponse> and requestState.
    • Task-backed flow: Updates the task via tasks/update (under SEP-2663).
  • The server decrypts/validates requestState, processes inputResponses, and either produces the final CallToolResult or requests further input if needed.

This proposal outlines the Kotlin Multiplatform architecture to implement MRTR in kotlin-sdk across kotlin-sdk-core, kotlin-sdk-server, and kotlin-sdk-client.


Proposed Architecture & Component Design
1. Wire Models & Data Types (kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/)
  • Input Required Result Wire Shape:
    @Serializable
    @SerialName("input_required")
    public data class InputRequiredResult(
        val inputRequests: Map<String, ServerRequest>,
        val requestState: String? = null,
        @SerialName("_meta")
        override val meta: JsonObject? = null,
    ) : CallToolResponse
    
    @Serializable
    public data class InputResponse(
        val action: String, // "accept" | "decline" | "cancel"
        val content: JsonElement? = null,
    )
    
  • Extended CallToolRequestParams:
    Update CallToolRequestParams to support round-trip continuations:
    @Serializable
    public data class CallToolRequestParams(
        val name: String,
        val arguments: JsonObject? = null,
        val inputResponses: Map<String, InputResponse>? = null,
        val requestState: String? = null,
        @SerialName("_meta")
        override val meta: RequestMeta? = null,
    ) : RequestParams
    
2. State Serialization & Integrity (kotlin-sdk-core / kotlin-sdk-server)

Because requestState passes through untrusted client hands:

  • Stateless Continuation Token:
    Provide an encrypted or HMAC-signed state serializer interface:
    public interface RequestStateCodec {
        public fun <T> encode(state: T, serializer: KSerializer<T>): String
        public fun <T> decode(token: String, serializer: KSerializer<T>): T?
    }
    
    public class HmacSignedStateCodec(private val secretKey: ByteArray) : RequestStateCodec { ... }
    
    This ensures that state tampering by compromised clients is rejected immediately with an invalid parameter error (-32602).
3. Server Tool Authoring DSL & Programming Model (kotlin-sdk-server)

Support both continuation-based tools and high-level coroutine helpers:

  • Explicit Round-Trip Tool Handler:
    server.addTool(
        name = "book_flight",
        description = "Books a flight with optional confirmation"
    ) { params ->
        val state = params.requestState?.let { codec.decode(it, BookingState.serializer()) }
        val confirmation = params.inputResponses?.get("user_confirm")
        
        if (state == null) {
            // First round: ask for user confirmation via elicitation
            val initialBooking = createBookingDraft(params.arguments)
            InputRequiredResult(
                inputRequests = mapOf(
                    "user_confirm" to ElicitationCreateRequest(
                        message = "Confirm booking for ${initialBooking.destination} for \$${initialBooking.price}?"
                    )
                ),
                requestState = codec.encode(BookingState(initialBooking.id), BookingState.serializer())
            )
        } else {
            // Resumed round: finalize booking
            if (confirmation?.action == "accept") {
                val receipt = finalizeBooking(state.draftId)
                CallToolResult(content = listOf(TextContent(receipt)))
            } else {
                CallToolResult(content = listOf(TextContent("Booking cancelled by user.")), isError = true)
            }
        }
    }
    
4. Client-Side Round-Trip Automation (kotlin-sdk-client)

Provide automatic resolution options in ClientSession:

  • Automated Handler Hook:
    public suspend fun ClientSession.callToolAutoRoundTrip(
        name: String,
        arguments: JsonObject? = null,
        maxRounds: Int = 5,
        elicitationHandler: suspend (String, ElicitationCreateRequest) -> InputResponse
    ): CallToolResult
    
    • When the server returns InputRequiredResult, the client invokes elicitationHandler for each requested prompt, collects the inputResponses, and automatically re-issues tools/call with requestState and inputResponses.
    • Terminates gracefully when a final CallToolResult is received or maxRounds is reached.
5. Conformance & Verification Plan
  • Unit Tests:
    • Serialization and deserialization of InputRequiredResult and CallToolRequestParams with inputResponses.
    • HMAC state codec validation and tampering rejection.
    • Multi-round simulated tool invocation flow in in-memory transport.
  • Conformance Suite:

Next Steps

Upon review and consensus on the MRTR design:

  1. Introduce InputRequiredResult, InputResponse, and updated CallToolRequestParams in kotlin-sdk-core.
  2. Implement RequestStateCodec in kotlin-sdk-server.
  3. Provide callToolAutoRoundTrip in kotlin-sdk-client.
  4. Validate against official MRTR conformance tests.

AI assistance disclosure: AI was used to discover this opportunity and draft the change or text. The submission was checked against the prepared artifact and recorded verification evidence.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with SEP-2322 and the wire-model directory at kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/. Review the proposed boundaries across kotlin-sdk-core, kotlin-sdk-server, and kotlin-sdk-client, then inspect the listed serialization, HMAC, in-memory flow, and conformance tests. Done requires agreement on the architecture, implementation across the three modules, and successful MRTR verification.

Written by the indexing model from the issue text.

Assessment

Tech stack
kotlin
Domain
backend-api-design, distributed-systems, security, testing-qa
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.