modelcontextprotocol / modelcontextprotocol/kotlin-sdk

[Proposal] SEP-2663: Tasks Extension architecture & migration design (#817)

Open
#1,003 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-2663 Tasks Extension Architecture & Migration Design (#817)
Motivation & Context

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

The experimental tasks mechanism specified in 2025-11-25 served as an initial asynchronous execution mechanism for tool calls. Real-world experience surfaced critical structural issues:

  1. Fragile Handshake: Method-level capabilities (tasks.requests.tools.call) combined with per-tool execution.taskSupport required clients to prime cache with tools/list before issuing task requests, preventing isomorphic request handling.
  2. The tasks/result Blocking Trap: Forcing clients to open a long-lived blocking stream on tasks/result to receive out-of-band events conflicts with SEP-2260 (prohibiting unsolicited server-to-client requests) and modern streamable transports.
  3. Undefinable Scoping under SEP-2567: With sessions removed from the core specification, server-wide tasks/list cannot be safely scoped or authorized without bespoke perimeter isolation, risking data leakage across unrelated callers.
  4. No Client-Hosted Tasks: Under SEP-2260, client-hosted sampling/elicitation tasks would require unsolicited polling from server to client, making client-hosted tasks inexpressible.

SEP-2663 formally moves Tasks into an official MCP Extension under the SEP-2133 Extensions Framework (io.modelcontextprotocol/tasks), streamlining the polling lifecycle to tasks/get, tasks/update, and tasks/cancel, while directly embedding final results in the Task object.

This proposal outlines the Kotlin Multiplatform architecture to migrate kotlin-sdk from legacy experimental tasks to the SEP-2663 Tasks Extension across kotlin-sdk-core, kotlin-sdk-server, and kotlin-sdk-client.


Proposed Architecture & Component Design
1. Extensions Framework Registration (kotlin-sdk-core)

Under the SEP-2133 Extensions Framework:

  • Define the official extension identifier:
    public object TasksExtension {
        public const val EXTENSION_ID: String = "io.modelcontextprotocol/tasks"
        public const val EXTENSION_VERSION: String = "1.0.0"
    }
    
  • Client and server capability declarations register support via extensions:
    // In ClientCapabilities / ServerCapabilities
    val extensions: Map<String, JsonElement>? = mapOf(
        TasksExtension.EXTENSION_ID to buildJsonObject {
            put("version", TasksExtension.EXTENSION_VERSION)
        }
    )
    
  • Server-directed task creation: When the client advertises TasksExtension.EXTENSION_ID, the server may return a task handle for any tool execution without requiring per-tool flags.
2. Wire Data Models & Discriminators (kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt)

Migrate and expand tasks.kt:

  • Task Status Enumeration:
    @Serializable
    public enum class TaskStatus {
        @SerialName("working") WORKING,
        @SerialName("input_required") INPUT_REQUIRED,
        @SerialName("completed") COMPLETED,
        @SerialName("failed") FAILED,
        @SerialName("cancelled") CANCELLED
    }
    
  • Redesigned Task Entity:
    Final payloads and errors are carried directly within the task representation, eliminating the separate tasks/result stream:
    @Serializable
    public data class Task(
        val taskId: String,
        val status: TaskStatus,
        val statusMessage: String? = null,
        val createdAt: String,
        val lastUpdatedAt: String,
        val ttl: Long? = null,
        val pollInterval: Long? = null,
        val result: JsonElement? = null,
        val error: JsonRpcError? = null,
        val inputRequests: List<ServerRequest>? = null,
        @SerialName("_meta")
        override val meta: JsonObject? = null,
    ) : WithMeta
    
  • Polymorphic Tool Call Response:
    tools/call results can return either standard content or a task handle (resultType: "task"):
    @Serializable
    public sealed interface CallToolResponse : ClientResult {
        public val meta: JsonObject?
    }
    
    @Serializable
    @SerialName("call")
    public data class CallToolResult(
        val content: List<ContentBlock> = emptyList(),
        val isError: Boolean = false,
        @SerialName("_meta")
        override val meta: JsonObject? = null,
    ) : CallToolResponse
    
    @Serializable
    @SerialName("task")
    public data class CallToolTaskResult(
        val task: Task,
        @SerialName("_meta")
        override val meta: JsonObject? = null,
    ) : CallToolResponse
    
  • Lifecycle Methods:
    • tasks/get: Retain GetTaskRequest / GetTaskResult for status polling.
    • tasks/update: Add UpdateTaskRequest(taskId, inputResponses) allowing clients to respond to multi round-trip requests (SEP-2322) when task status is input_required.
    • tasks/cancel: Retain CancelTaskRequest / CancelTaskResult.
    • Deprecation / Removal:
      • Mark legacy GetTaskPayloadRequest (tasks/result) as @Deprecated(message = "Removed in SEP-2663; result is now embedded in Task", level = DeprecationLevel.WARNING).
      • Mark legacy ListTasksRequest (tasks/list) as @Deprecated(message = "Removed in SEP-2663 per SEP-2567 sessionless scoping", level = DeprecationLevel.WARNING).
3. Server Task Management & Execution (kotlin-sdk-server)
  • Task Runner & Storage SPI:
    Define a thread-safe task store interface with coroutine job lifecycle management:
    public interface TaskStore {
        public suspend fun get(taskId: String): Task?
        public suspend fun save(task: Task)
        public suspend fun update(taskId: String, block: (Task) -> Task): Task
        public suspend fun cancel(taskId: String): Boolean
    }
    
    public class InMemoryTaskStore(
        private val clock: Clock = Clock.System
    ) : TaskStore { ... }
    
  • Server Handler Integration:
    Allow tool handlers to yield execution to a background task:
    public suspend fun ServerSession.launchTask(
        pollInterval: Long = 1000L,
        ttl: Long? = 3600_000L,
        block: suspend (TaskExecutionContext) -> JsonElement
    ): CallToolTaskResult
    
    The context automatically coordinates coroutine cancellation with tasks/cancel and stores intermediate status messages.
4. Client Polling & Ergonomics (kotlin-sdk-client)
  • High-Level Polling Utility:
    Provide an ergonomic suspend extension on ClientSession:
    public suspend fun ClientSession.awaitTaskCompletion(
        taskId: String,
        timeout: Duration = 5.minutes,
        pollInterval: Duration? = null,
        onInputRequired: (suspend (Task, List<ServerRequest>) -> List<ClientResult>)? = null
    ): Task
    
    • Automatically respects server's pollInterval from Task.pollInterval if specified.
    • Automatically dispatches tasks/update when input_required is observed and a handler is provided.
    • Times out cleanly with TimeoutCancellationException.
5. Conformance & Verification Plan
  • Unit Tests (kotlin-sdk-core):
    • Serialization / deserialization of polymorphic CallToolResponse (CallToolResult vs CallToolTaskResult).
    • Wire validation of tasks/update and updated Task model containing result and inputRequests.
  • Functional Tests (kotlin-sdk-server & kotlin-sdk-client):
    • End-to-end task execution: client initiates tool call, receives task handle, polls tasks/get until COMPLETED, and accesses embedded result.
    • Multi round-trip task update: working -> input_required -> tasks/update -> completed.
    • Cancellation test: client issues tasks/cancel, server cancels underlying Kotlin coroutine Job, task transitions to CANCELLED.
    • TTL eviction: verified using virtual time.
  • Official Conformance Suite:

Next Steps

Upon review and alignment on this migration design:

  1. Land core data models (Task, CallToolResponse, UpdateTaskRequest) in kotlin-sdk-core.
  2. Implement TaskStore and server-side task execution runners in kotlin-sdk-server.
  3. Provide high-level awaitTaskCompletion client helpers in kotlin-sdk-client.
  4. Validate against the official conformance test suite.

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 by reading kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/tasks.kt and the existing capability and task request/result models. Run the proposed core serialization and wire-validation tests first, then implement and verify the core models, server TaskStore and execution flow, client polling helper, functional lifecycle tests, and the SEP-2663 conformance scenarios.

Written by the indexing model from the issue text.

Assessment

Tech stack
kotlin
Domain
api, backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.