From b4f29a636d669a932f833ad8921e416511c378a2 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Fri, 20 Feb 2026 10:36:57 -0800 Subject: [PATCH 01/27] add SEP doc from transport-wg --- seps/XXXX-MRTR.md | 1175 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1175 insertions(+) create mode 100644 seps/XXXX-MRTR.md diff --git a/seps/XXXX-MRTR.md b/seps/XXXX-MRTR.md new file mode 100644 index 000000000..f64e7bd54 --- /dev/null +++ b/seps/XXXX-MRTR.md @@ -0,0 +1,1175 @@ +# SEP-XXXX: Multi Round-Trip Requests + +- **Status**: Draft +- **Type**: Standards Track +- **Created**: 2026-20-03 +- **Author(s)**: Mark D. Roth (@markdroth), Caitie McCaffrey (@CaitieM20), + Gabriel Zimmerman (@gjz22) +- **Sponsor**: Caitie McCaffrey (@CaitieM20) +- **PR**: https://github.com/modelcontextprotocol/specification/pull/{NUMBER} + +## Abstract + +This proposal specifies a simple way to handle server-initiated requests +in the context of a client-initiated request (e.g., an elicitation +request in the context of a tool call) without requiring a shared +storage layer shared across server instances or statefulness in +load balancing, which will significantly reduce the cost of operating +MCP servers at scale in the common case. It also reduces the HTTP +transport's dependence on SSE streams, which cause problems in a lot of +environments that cannot support long-lived connections. + +## Motivation + +Note: This SEP is intended to provide a generic mechanism for handling +any server-initiated request in the context of any client-initiated +request. For clarity, throughout this document, we will specifically +discuss tool calls as a proxy for any client-initiated request, but it +should be read as applying equally to (e.g.) resource or prompt +requests; similarly, we will discuss elicitation requests as a proxy for +any server-initiated request, but it should be read as applying equally +to (e.g.) sampling requests. + +We start with the observation that there are two types of MCP tools: +1. **Ephemeral**: No state is accumulated on the server side. + - If server needs more info to process the tool call, it can start from + scratch when it gets that additional info. + - Examples: weather app, accessing email +2. **Persistent**: State is accumulated on the server side. + - Server may generate a large amount of state before requesting more + info from the client, and it may need to pick up that state to + continue processing after it receives the info from the client. + - Server may need to continue processing in the background while + waiting for more info from the client, in which case server-side + state is needed to track that ongoing processing. + - Examples: accessing an agent, spinning up a VM and needing user + interaction to manipulate the VM + +The vast majority of MCP tools will be ephemeral, and it is extremely +common for tools to be deployed in a horizontally scaled, load balanced +service, so we need to optimize for this case. + +Today, if a tool needs to send an elicitation request in order to make +progress, the workflow works like this: + +1. Client sends tool call request. For this example, let's assume that + the load balancers happen to send this request to server instance A. +2. Server A opens an SSE stream and sends the elicitation response on that + stream. +3. Client sends the elicitation response as a separate request, for which + the load balancers will choose a server instance completely + independently of the one they chose in step 1. In this example, + let's assume that the load balancers happen to send this request to + server instance B. +4. Server A must somehow discover the elicitation result delivered to + server B. +5. Server A then sends the tool call result on the SSE stream opened in + step 2. + +The difficult part here is step 4, which requires some sort of +statefulness on the server side. The main way to solve this problem +today is to have a storage layer shared across all server instances, so +that multiple server instances can match up the elicitation response +on one server instance with the original ongoing tool call on a +different server instance. + +There are two main approaches that can be used to solve this problem today: +- **Persistent Storage Layer Shared Across Server Instances**: Servers can + deploy and manage a persistent storage layer (e.g., PostgreSQL, Redis, + DynamoDB), which allow multiple server instances to match up the + elicitation response on one server instance with the original ongoing + tool call on a different server instance. This approach has a number + of drawbacks: + - The persistent storage layer is **extremely expensive**, especially for + ephemeral tools that may not already have such a layer (e.g., a weather + tool). + - The persistent storage layer imposes significant reliability concerns: + it becomes a critical dependency and therefore a potential single + point of failure. To avoid that, it must provide high availability, + replication, and backup mechanisms. + - The persistent storage layer becomes a bottleneck, limiting horizontal + scalability. Geographic distribution requires either expensive + global replication or sticky routing. + - The persistent storage layer also imposes significant operational + complexity. In horizontally scaled deployments, it requires + distributed locking or consensus protocols. It also requires special + garbage collection logic to determine when shared can be cleaned up, + which requires careful trade-offs: cleaning up state too aggressively + can reduce storage costs but limit how long users have to respond, + whereas cleaning up less aggressively accommodates slow users but + increases storage costs. + - This approach requires special behavior in the tool implementation to + integrate with the persistent storage layer. The MCP SDKs today do + not have any special hooks for this sort of storage layer integration, + which means that it's very hard to write in-line code via the SDKs. +- **Statefulness in Load Balancing**: With the use of cookies, it is + possible for the load balancing layer to ensure that the elicitation + request in step 3 is delivered to the same server instance that the + original request was delivered to in step 1. This approach, while + often cheaper than a persistent storage layer, has the following + drawbacks: + - It requires special configuration and behavior in the load + balancers, which is often difficult to manage. + - It breaks normal load balancing models, resulting in uneven load + distribution, thus increasing the cost of running the service. + - It requires special behavior in clients to propagate the cookies + used for statefulness. + - It requires the tool implementation to match up the elicitation + request with the ongoing tool call. (The MCP SDKs have some code to + handle this, but it's still a very strange pattern in the HTTP + world.) + - It is not fault tolerant. If the server instance goes down, all + state is lost, and the tool call would need to start over from + scratch. (This doesn't necessarily matter for ephemeral tools, + but it is an issue for persistent tools.) + +Also, both of these approaches rely on the use of an SSE stream, which +causes problems in environments that cannot support long-lived +connections. + +The goal of this SEP is to propose a simpler way to handle the pattern +of server-initiated requests within the context of a client-initiated +request. Specifically, we need to make it cheaper to support this pattern +in the common case of an ephemeral tool in a horizontally scaled, load +balanced deployment. This means that we need a solution that does not +depend on an SSE stream and does not require either a persistent storage +layer or stateful load balancing, which in turn means that we need to +avoid dependencies between requests: servers must be able to process +each individual request using no information other than what is present +in that individual request. + +Note that while the goal here is to optimize the common case of ephemeral +tools, we do want to continue to support persistent tools, which generally +already require a persistent storage layer. + +## Specification + +This SEP proposes a new mechanism for handling server requests in the +context of a client request. This new mechanism will have a slightly +different workflow for ephemeral tools and persistent tools, the latter +of which will leverage Tasks. However, both workflows will use the same +data structures. + +First, we introduce the notion of "input requests", which represents +a set of one or more server-initiated request to be sent to the client, +and "input responses", which represents the client's responses to +those requests. Both requests and responses are stored in a map with +string keys. For input requests, the map values are server-initiated +requests (e.g., elicitation or sampling requests), whereas for input +responses, the map values are the responses to those requests. Here's +how that would look in the typescript MCP schema: + +```typescript +export interface InputRequests { [key: string]: ServerRequest; } + +export interface InputResponses { [key: string]: ClientResult; } +``` + +TODO: The above schema definitions are not quite right, because +ServerRequest and ServerResult include the JSON-RPC request id field, +which is not necessary here. Figure out what schema refactoring is +needed to get the types without that field. + +The keys are assigned by the server when issuing the requests. The client +will send the response for each request using the corresponding key. +For example, a server might send the following input requests: + +```json5 +"inputRequests": { + // Elicitation request. + "github_login": { + "method": "elicitation/create", + "params": { + "mode": "form", + "message": "Please provide your GitHub username", + "requestedSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + } + } + }, + // Sampling request. + "capital_of_france" : { + "method": "sampling/createMessage", + "params": { + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "What is the capital of France?" + } + } + ], + "modelPreferences": { + "hints": [ + { + "name": "claude-3-sonnet" + } + ], + "intelligencePriority": 0.8, + "speedPriority": 0.5 + }, + "systemPrompt": "You are a helpful assistant.", + "maxTokens": 100 + } + } +} +``` + +The client would then send the responses in the following form: + +```json5 +"inputResponses": { + // Elicitation response. + "github_login": { + "result": { + "action": "accept", + "content": { + "name": "octocat" + } + } + }, + // Sampling response. + "capital_of_france": { + "result": { + "role": "assistant", + "content": { + "type": "text", + "text": "The capital of France is Paris." + }, + "model": "claude-3-sonnet-20240307", + "stopReason": "endTurn" + } + } +} +``` + +These types will be used in two different workflows, one for ephemeral +tools and another for persistent tools. + +### Ephemeral Tool Workflow + +For ephemeral tools, we will adopt the following workflow: + +1. Client sends tool call request. +2. Server sends back a single response indicating that the request is + incomplete. The response may include input requests that the client + must complete. It may also include some request state that the client + must return back to the server. This response terminates the original + request. It will normally be sent as a single response, not on an + SSE stream, although for now (this may change in a future SEP) it is + also legal to send this response on an SSE stream following (e.g.) + progress notifications. If this incomplete response is sent on an + SSE stream, it must be the last message on the SSE stream, just as if + it were a normal response. +3. Client sends a new tool call request, completely independent of the + original one. This new tool call includes responses to the input + requests from step 2. It also includes the request state specified by + the server in step 2. +4. Server sends back a CallToolResponse. + +Note that the requests in steps 1 and 3 are completely independent: the +server that processes the request in step 3 does not need any +information that is not directly present in the request. To support this decoupling the JsonRPC Id MUST be different between the requests sent in step 1 and step 3. + +The schema would look something like this: + +```typescript +// Incomplete response. May be sent in response to any client-initiated +// request. +// At least one of the the inputRequests and requestState fields must be +// present, since the presence of these fields allow the client to +// determine that the result is incomplete. +export interface IncompleteResult extends Result { + // Requests issued by the server that must be complete before the + // client can retry the original request. + inputRequests?: InputRequests; + // Request state to be passed back to the server when the client + // retries the original request. + // Note: The client must treat this as an opaque blob; it must not + // interpret it in any way. + requestState?: string; +} + +// New request parameter type that includes fields in a retried request. +// These parameters may be included in any client-initiated request. +export interface RetryAugmentedRequestParams extends RequestParams { + // New field to carry the responses for the server's requests from the + // JSONRPCIncompleteResultResponse message. For each key in the + // response's inputRequests field, the same key must appear here + // with the associated response. + inputResponses?: InputResponses; + // Request state passed back to the server from the client. + requestState?: string; +} +``` + +Note that both the "inputRequests" and "requestState" fields affect +only the client's next retry of the original request. They will not +be used for any other request that the client may be sending in parallel +(e.g., a tool list or even another tool call). + +Note that this workflow eliminates the need for the +`URLElicitationRequiredError` error code. That code will be removed +from the spec. + +#### Example Flow for Ephemeral Tools + +Note: This is a contrived example, just to illustrate the flow. + +1. The client sends the initial call tool request: +```json +{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "get_weather", + "arguments": { + "location": "New York" + } + } +} +``` + +2. The server responds with an incomplete response, indicating that the + client needs to respond to an elicitation request in order for the tool + call to complete, and including request state to be passed back: +```json +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "inputRequests": { + "github_login": { + "method": "elicitation/create", + "params": { + "mode": "form", + "message": "Please provide your GitHub username", + "requestedSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + } + } + } + }, + "requestState": "foo" + } +} +``` + +3. The client then retries the original tool call, this time including the + responses to the input server request and the request state: +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "get_weather", + "arguments": { + "location": "New York" + } + "inputResponses": { + "github_login": { + "result": { + "action": "accept", + "content": { + "name": "octocat" + } + } + } + }, + "requestState": "foo" + } +} +``` + +4. Finally, the server completes the tool call: +```json +{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "content": [ + { + "type": "text", + "text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy" + } + ], + "isError": false + } +} +``` + +#### Real-World Example for Ephemeral Workflow + +This example demonstrates how `requestState` enables a multi-round-trip +elicitation flow driven by [Azure DevOps custom +rules](https://learn.microsoft.com/en-us/azure/devops/organizations/settings/work/custom-rules?view=azure-devops). +The scenario involves an `update_work_item` tool that transitions a Bug +work item to "Resolved." ADO custom rules require specific fields when +certain state transitions occur, and the server uses iterative +elicitation to gather them — accumulating context in `requestState` +across rounds so that the final update can be executed without any +server-side storage. + +**Background — ADO Custom Rules in effect:** +- *Rule 1:* When State changes to "Resolved" → require the "Resolution" + field (e.g., Fixed, Won't Fix, Duplicate, By Design). +- *Rule 2:* When Resolution is "Duplicate" → require the "Duplicate Of" + field (a link to the original work item). + +##### Round 1 — Tool call triggers state change, server elicits Resolution + +1. The client invokes the `update_work_item` tool to resolve Bug #4522: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "update_work_item", + "arguments": { + "workItemId": 4522, + "fields": { "System.State": "Resolved" } + } + } +} +``` + +2. The server recognizes that setting State to "Resolved" triggers + Rule 1, which requires a Resolution value. Rather than failing the + call, the server returns an incomplete response with an elicitation + request. No `requestState` is needed yet, since the original tool + call arguments will be re-sent on retry: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "inputRequests": { + "resolution": { + "method": "elicitation/create", + "params": { + "message": "Resolving Bug #4522 requires a resolution. How was this bug resolved?", + "requestedSchema": { + "type": "object", + "properties": { + "resolution": { + "type": "string", + "enum": ["Fixed", "Won't Fix", "Duplicate", "By Design"], + "description": "Resolution type for this bug" + } + }, + "required": ["resolution"] + } + } + } + } + } +} +``` + +3. The user selects "Duplicate". The client retries the original tool + call with the elicitation response: + +```json +{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "update_work_item", + "arguments": { + "workItemId": 4522, + "fields": { "System.State": "Resolved" } + }, + "inputResponses": { + "resolution": { + "result": { + "action": "accept", + "content": { "resolution": "Duplicate" } + } + } + } + } +} +``` + +##### Round 2 — Resolution triggers another rule, server elicits Duplicate Of + +4. The server merges the user's response and sees that Resolution = + "Duplicate" triggers Rule 2, requiring a "Duplicate Of" link. It + returns another incomplete response, this time encoding the + already-gathered resolution in `requestState` so it is available + regardless of which server instance handles the next retry: + +```json +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "inputRequests": { + "duplicate_of": { + "method": "elicitation/create", + "params": { + "message": "Since this is a duplicate, which work item is the original?", + "requestedSchema": { + "type": "object", + "properties": { + "duplicateOfId": { + "type": "number", + "description": "Work item ID of the original bug" + } + }, + "required": ["duplicateOfId"] + } + } + } + }, + "requestState": "eyJyZXNvbHV0aW9uIjoiRHVwbGljYXRlIn0..." + } +} +``` + +5. The user provides the original work item ID. The client retries the + tool call, echoing back the `requestState` and including the new + elicitation response: + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "update_work_item", + "arguments": { + "workItemId": 4522, + "fields": { "System.State": "Resolved" } + }, + "inputResponses": { + "duplicate_of": { + "result": { + "action": "accept", + "content": { "duplicateOfId": 4301 } + } + } + }, + "requestState": "eyJyZXNvbHV0aW9uIjoiRHVwbGljYXRlIn0..." + } +} +``` + +##### Final — Server completes the update + +6. The server decodes the `requestState` (which contains the + resolution), reads the `inputResponses` (which contains the + duplicate ID), and now has all required fields. It completes the + tool call: + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "content": [ + { + "type": "text", + "text": "Bug #4522 resolved as Duplicate of Bug #4301. State set to Resolved and duplicate link created." + } + ], + "isError": false + } +} +``` + +**Key takeaway:** Across both elicitation rounds, the server held no +in-memory or persisted state. The `requestState` field carried the +accumulated context through the client, and any server instance could +have handled any individual round. + +#### Use Cases for Request State + +The "requestState" mechanism provides a mechanism for doing multiple +round trips on the same logical request. There are two main use-cases +for this. + +##### Use Case 1: Rolling Upgrades + +Let's say that you are doing a rolling upgrade of your horizontally +scaled server instances to deploy a new version of a tool implementation. +The old version had two input requests with keys "github_login" and +"google_login". However, in the new version of the tool implementation, +it still uses the "github_login" input request, but it replaces the +"google_login" input request with a new "microsoft_login" input request. + +If the first request goes to an old version of the server but the second +attempt (that includes the input responses) goes to a new version +of the server, then the server will see the result for "github_login", +which it needs, but it won't see the result for "microsoft_login". +(It will also see the result for "google_login", but it no longer needs +that, so it doesn't matter.) At this point, the server needs to send a +new input request for "microsoft_login", but it also doesn't want +to lose the answer that it's already gotten for "github_login", so it +would use the kind of state proposed in 1685 to retain that information +without having to store the state on the server side. + +The workflow here would look like this: + +1. Client sends tool call request that hits a server instance running + the old version. +2. Server sends back an incomplete response indicating the input + requests for "github_login" and "google_login". +3. Client sends a new tool call request that includes the responses to + the input requests for "github_login" and "google_login". This + time it hits a server instance running the new version. +4. Server sends back another incomplete response indicating the + input request for "microsoft_login", which the client has not + already provided. However, the response also includes request state + containing the already-provided "github_login" response, so that the + client does not need to prompt the user for the same information a + second time. +5. Client sends a third tool call request that includes the response to + the "microsoft_login" input request as well as echoing back the + request state provided by the server in step 4. +6. Server now sees the "github_login" info in the request state and the + "microsoft_login" state in the input responses, so the request + now contains everything the server needs to perform the tool call and + send back a complete response. + +##### Use Case 2: Load Shedding + +Let's say that you have an MCP server instance that is processing a bunch +of tool calls and notices that it's too heavily loaded, so it wants to +move one of the ongoing tool calls to a different server instance. +However, it has already done a significant amount of processing on that +tool call, so it does not want to simply fail the call and have the +client start over from scratch on another server instance; instead, it +wants to preserve the state it has already accumulated, so that +whichever server instance resumes processing can pick up from where the +original server instance left off. This can be accomplished by sending +an incomplete request that contains request state but does not contain +any input requests. + +The workflow here would look like this: + +1. Client sends the original request, which the load balancers route + to server instance A. +2. Server instance A does a bunch of computation before deciding that it + needs to shed load. It sends an incomplete response with its + accumulated state in the `requestState` field but without the + `inputRequests` field. +3. Client retries the request with the `requestState` field attached. + The load balancers route this request to server instance B. +4. Server instance B starts from the state it sees in the `requestState` + field, thus picking up the computation from where server instance A + left off, and eventually returning a complete response. + +#### Protocol Requirements for Ephemeral Workflow + +1. **Server Behavior:** + - Servers MAY respond to any client-initiated request with a + `JSONRPCIncompleteResultResponse`. This message MAY be sent either + as a standalone response or as the final message on an SSE stream, + although implementations are encouraged to prefer the former. + If using an SSE stream, servers MUST NOT send any message on the + stream after the incomplete response message. + - The `JSONRPCIncompleteResultResponse` message MAY include an + `inputRequests` field. + - The `JSONRPCIncompleteResultResponse` message MAY include a + `requestState` field. If specified, this field is an opaque + string that is meaningful only to the server. Servers are free to + encode the state in any format (e.g., plain JSON, base64-encoded + JSON, encrypted JWT, serialized binary, etc.). + - If a request contains a `requestState` field, servers MUST always + validate that state, as the client is an untrusted intermediary. + If tampering is a concern, servers SHOULD encrypt the `requestState` + field using an encryption algorithm of their choice (e.g., they can + use AES-GCM or a signed JWT) to ensure both confidentiality and + integrity. Note that there is also a risk of replaying/hijacking + attacks, where an authenticated attacker resends state that was + originally sent to a different user. Therefore, if the request + state contains any data that is specific to the original user, the + server MUST use some mechanism to cryptographically bind the data + to the original user and MUST verify that the `requestState` data + sent by the client is associated with the currently authenticated + user. Servers using plaintext state MUST treat the decoded + values as untrusted input and validate them the same way they would + validate any client-supplied data. + +2. **Client Behavior:** + - If a client receives a `JSONRPCIncompleteResultResponse` message, + if the message contains the `inputRequests` field, then the client + MUST construct the requested input before retrying the original + request. In contrast, if the message does *not* contain the + `inputRequests` field, then the client MAY retry the original + request immediately. + - If a client receives a `JSONRPCIncompleteResultResponse` message + that contains the `requestState` field, it MUST echo back the + exact value of that field when retrying the original request. + Clients MUST NOT inspect, parse, modify, or make any assumptions + about the `requestState` contents. If the incomplete response does + not contain a `requestState` field, the client MUST NOT include one + in the retry. + +### Persistent Tool Workflow + +The persistent tool workflow will leverage Tasks. [`Tasks`](https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks) already provide a mechanism to indicate that more information is needed to complete the request. The `input_required` Task Status allows the server to indicate that additional information is needed to complete processing the task. + +The workflow for `Tasks` is as follows: + +1. Server sets Task Status to `input_required`. The server can pause + processing the request at this point. +2. Client retrieves the Task Status by calling `tasks/get` and sees that more information is needed. +3. Client calls `task/result` +4. Server returns the `InputRequests` object. +5. Client calls `tasks/input_response` request that includes an `InputResponses` object along with `Task` metadata field. +6. Server resumes processing sets TaskStatus back to `Working`. + +Since `Tasks` are likely longer running, have state associated with them, and are likely more costly to compute, the request for more information does not end the originally requested operation (e.g., the tool call). Instead, the server can resume processing once the necessary information is provided. + +To align with MRTR semantics, the server will respond to the `task/result` request with a `InputRequests` object. Both of these will have the same JsonRPC `id`. When the client responds with a `InputResponses` object this is a new client request wit a new JSONRPC `id` and therefore needs a new method name. We propose `tasks/input_response`. + +The above workflow and below example do not leverage any of the optional Task Status Notifications although this SEP does not preclude their use. + +#### Example Flow for Persistent Tools +The below example walks through the entire Task Message flow for a Echo Tool which can request additional information from the client via Elicitation. + +1. Client Request to invoke EchoTool. +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params":{ + "name": "echo", + "task":{ + "ttl": 60000 + } + } +} +``` + +2. Server Response with a `Task` +```json +{ + "id": 1, + "jsonrpc": "2.0", + "result":{ + "task":{ + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5", + "status": "Working", + "statusMessage": "Task has been created for echo tool invocation.", + "createdAt": "2026-01-27T03:32:48.3148180Z", + "ttl": 60000, + "pollInterval": 100 + } + } +} +``` + +3. Client Request periodically checks the status of the `Task` using `tasks/get`. +```json +{ + "jsonrpc": "2.0", + "id": 2, + "method": "tasks/get", + "params":{ + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } +} +``` + +4. Server Response with Task status `input_required` +```json +{ + "id": 2, + "jsonrpc": "2.0", + "result":{ + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5", + "status": "input_required", + "statusMessage": "Input Required to Proceed call tasks/result", + "createdAt": "2026-01-27T03:38:07.7534643Z", + "ttl": 60000, + "pollInterval": 100 + }, +} +``` + +5. Client Request sends message `tasks/result` to discover what input is required to proceed. +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "tasks/result", + "params":{ + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } +} +``` + +6. Server Response returns `inputRequests` to request additional input +```json +{ + "id": 3, + "jsonrpc": "2.0", + "result": { + "inputRequests":{ + "echo_input":{ + "method": "elicitation/create", + "params":{ + "mode": "form", + "message": "Please provide the input string to echo back", + "requestedSchema":{ + "type": "object", + "properties":{ + "input": { "type": "string"} + }, + "required": ["input"] + } + } + } + }, + "_meta":{ + "io.modelcontextprotocol/related-task":{ + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} +``` + +7. Client Request presents the Elicitation to the user and collects the input, then sends message to the server. +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "tasks/input_response", + "params": { + "inputResponses":{ + "echo_input":{ + "result":{ + "action": "accept", + "content":{ + "input": "Hello World!" + } + } + } + }, + "_meta":{ + "io.modelcontextprotocol/related-task":{ + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} +``` + +8. Server Response Server should acknowledge the receipt of the 'tasks/input_response' message by sending a 'JSONRPCResponse'. If the message was successfully received a `JSONRPCResultResponse` is sent including the `taskId`. If an error occurs, a `JSONRPCErrorResponse` is sent. The server can now proceed to complete the `Task` using the provided input, and the `Task` status changes to `Working`. +```json +{ + "id": 4, + "jsonrpc": "2.0", + "result": { + "_meta":{ + "io.modelcontextprotocol/related-task":{ + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} +``` + +9. Client Request continues to poll the input status using `tasks/get` until server responds with Task Status of `Completed` +Client Request +```json +{ + "jsonrpc": "2.0", + "id": 5, + "method": "tasks/get", + "params": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } +} +``` +10. Server Response with Task status `Completed` +```json +{ + "id": 5, + "jsonrpc": "2.0", + "result":{ + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5", + "status": "Completed", + "statusMessage": "Task has been completed successfully, call get/result", + "createdAt": "2026-01-27T03:38:07.7534643Z", + "ttl": 60000, + "pollInterval": 100 + }, +} +``` + +11. Client Request calls `tasks/result` to get the final result of the `Task` from the server. +Client Message +```json +{ + "id": 6, + "jsonrpc": "2.0", + "method": "tasks/result", + "params":{ + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + }, +} +``` +12. Server Response with the final result of the `Task` +```json +{ + "id": 6, + "jsonrpc": "2.0", + "result":{ + "isError": false, + "content":[{ + "type": "text", + "text": "Echo: Hello World!" + }], + "_meta":{ + "io.modelcontextprotocol/related-task":{ + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + }, +} +``` + +#### Protocol Requirements for Persistent Workflow + +1. **Server Behavior:** + - Servers MAY respond to `tasks/get` by indicating that the task + is in state `input_required`. + - Servers MAY include an `inputRequests` field in the + `tasks/result` response. + +2. **Client Behavior:** + - When `tasks/status` shows state `input_required`, clients MUST call + `tasks/result` to get the input requests, construct the results of + those requests, and then call `tasks/input_response` with the input + responses, unless the client is going to cancel the task. + +### Interactions Between Ephemeral and Persistent Workflows + +If a tool implementation needs the client to respond to a set of +input requests before it can even start processing but then later +needs to do persistent processing, it can start using the ephemeral +workflow and then switch to the persistent workflow by creating a task +at that point. This avoids the need for the server to store state until +it actually has the information needed to start processing the request. +This workflow would look like this: + +1. Client sends tool call request with task metadata. +2. Server sends back `inputRequests` response indicating that more information is needed to process the request. This terminates the original request. +3. Client sends a new tool call request, completely independent of the + original one, which includes the `inputResponses` object along with the task metadata. +4. Server sends back a task ID, indicating that it will be processing the + request in the background. All subsequent interaction will be done + via the Tasks API. + +Note that the opposite is not true: Once a tool implementation returns a +task, it has committed to storing state on the server side for the +duration of the task, and there is no way to transition back to the +ephemeral model. All subsequent interactions must be performed via the +Tasks API. + +## Error Handling Note +In both the ephemeral & persistent workflows, the client may send back malformed or invalid responses to the server's requests for more information as part of the `inputResponses` object. + +The server should validate the data provided by the client is a valid `inputResponses` object and that the information inside can be correctly parsed. Protocol errors, like malformed JSON, invalid schema, or internal server errors which prevent the processing of the request should return a `JSONRPCErrorResponse` with an appropriate error code and message. + +If the client provides a well-formed `inputResponses` object but the data provided is unexpected or missing required information, the server MUST treat these as optional parameters. The server should ignore any unexpected information and proceed with processing the request using the information that is present. If required information is missing, the server should not proceed with processing the request and instead should respond with a new incomplete response. + +We discussed having a specific application level error code returned, however the client may not have enough information to recover in all scenarios. For example, if a Server upgrade happens and the new version requires additional information, the client has no knowledge of this and must request the necessary information again. Therefore, we decided to rely on the existing mechanics of epheremal workflows and the existing state machine of `Tasks` to ensure a client can always recover and request the necessary information again. + +In the ephemeral workflow, this would look like the following: +1. The client retries the original tool call, this time including the `inputResponses` object, but the response is missing required information that the server needs to process the request. +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "get_weather", + "arguments": { + "location": "New York" + } + "inputResponses": { + "not_requested_info": { + "result": { + "action": "accept", + "content": { + "not_requested_param_name": "Information the server did not request" + } + } + } + } + } +} +``` + +2. The server responds with an incomplete response, indicating that the client needs to respond to an elicitation request in order for the tool call to complete, and including request state to be passed back: +```json +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "inputRequests": { + "github_login": { + "method": "elicitation/create", + "params": { + "mode": "form", + "message": "Please provide your GitHub username", + "requestedSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + } + } + } + } + } +} +``` + +2. The Server responds wit an incomplete response, indicating that the client needs to provide missing information for the request to succeed. + + +In the persistent workflow, this would look like the following: +Step 7 from above: Client Request The client mistakenly or maliciously sends unexpected, but well-formed data to the server in response to the input request. +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "tasks/input_response", + "params": { + "inputResponses":{ + "echo_input":{ + "result":{ + "action": "accept", + "content":{ + "not_requested_parameter": "Information the server did not request." + } + } + } + }, + "_meta":{ + "io.modelcontextprotocol/related-task":{ + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} +``` + +Step 8 from above. Server Response Server acknowledges the receipt of the response by sending a `JSONRPCResultResponse`. However, since the response is missing required information, the server does not proceed with processing the taks and leaves the Task status as `input_required`. The next time the client calls `task/result`, the server responds with a new `inputRequest` requesting the necessary information again. +```json +{ + "id": 4, + "jsonrpc": "2.0", + "result": { + "_meta":{ + "io.modelcontextprotocol/related-task":{ + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} +``` + +## Rationale + +We considered a bidirectional stream approach to replace SSE streams. +However, that approach would have made the wire protocol more +complicated (e.g., it would have required HTTP/2 or HTTP/3). Also, it +would not have eliminated problems for environments that cannot support +long-lived connections, nor would it have addressed fault tolerance +issues. + +There was discussion about whether the input requests should be a +map or just a single object, possibly leveraging some field inside of +the requests (e.g., the elicitation ID) to differentiate between them. +We decided that the map makes sense, since it structurally guarantees +the uniqueness of keys, which will avoid the need for explicit checks in +SDKs and applications to avoid conflicts. + +In the persistent workflow, we considered including the input requests +directly in the `tasks/get` response, rather than requiring the client +to see the `input_required` status and then call `tasks/result` to get +the input requests. We decided to keep those two things separate in +deference to implementations that use separate infrastructure for task +state and for the actual tool implementation; the idea is that the +`tasks/get` call should have a consistent latency profile, regardless of +what the task state actually is. We recognize that this requires an +extra round-trip to the server, but we can optimize this in the future +if becomes a problem. + +## Backward Compatibility + +Today there may be ephemeral tools written in an in-line but async +fashion to wait for the elicitation response before sending the tool +call response on the original SSE stream: + +```python +def my_tool(): + do_mutation1() + await elicit_more_info() + do_mutation2() +``` + +For new tools, we want to instead suggest that they be written like +this: + +```python +def my_tool(request): + github_login = request.inputResponses().get('github_login', None) + if github_login is None: + return IncompleteResponse({'github_login': elicitation_request}) + result = GetResult(github_login) + return Result(result) +``` + +However, we need to consider how to avoid breaking things for existing +tools that are written the old way. Ideally, we will be able to modify +the SDKs to support the old tool implementations via some sort of +backward compatibility layer. + +## Security Implications + +Because `requestState` passes through the client, malicious or +compromised clients could attempt to modify it to alter server behavior, +bypass authorization checks, or corrupt server logic. To mitigate this, +we require servers to validate this state as described in the protocol +requirements above. + +## Reference Implementation + +TBD + +### Acknowledgments + +Thanks to Luca Chang (@LucaButBoring) for his valuable input on how to +integrate input requests into Tasks. From a1b65dc8ced1eecda87462b6523f0019114af065 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Fri, 20 Feb 2026 16:37:39 -0800 Subject: [PATCH 02/27] copilot initial pass, still WIP and under my review --- docs/specification/$(basename | 0 ...incomplete-result-with-input-requests.json | 20 ++ ...mplete-result-with-request-state-only.json | 3 + .../elicitation-input-request.json | 15 ++ .../InputRequest/sampling-input-request.json | 15 ++ .../task-input-response-request.json | 22 ++ .../task-input-response-params.json | 17 ++ schema/draft/schema.json | 190 ++++++++++++++++++ schema/draft/schema.ts | 166 ++++++++++++++- 9 files changed, 446 insertions(+), 2 deletions(-) create mode 100644 docs/specification/$(basename create mode 100644 schema/draft/examples/IncompleteResult/incomplete-result-with-input-requests.json create mode 100644 schema/draft/examples/IncompleteResult/incomplete-result-with-request-state-only.json create mode 100644 schema/draft/examples/InputRequest/elicitation-input-request.json create mode 100644 schema/draft/examples/InputRequest/sampling-input-request.json create mode 100644 schema/draft/examples/TaskInputResponseRequest/task-input-response-request.json create mode 100644 schema/draft/examples/TaskInputResponseRequestParams/task-input-response-params.json diff --git a/docs/specification/$(basename b/docs/specification/$(basename new file mode 100644 index 000000000..e69de29bb diff --git a/schema/draft/examples/IncompleteResult/incomplete-result-with-input-requests.json b/schema/draft/examples/IncompleteResult/incomplete-result-with-input-requests.json new file mode 100644 index 000000000..49254b9ea --- /dev/null +++ b/schema/draft/examples/IncompleteResult/incomplete-result-with-input-requests.json @@ -0,0 +1,20 @@ +{ + "inputRequests": { + "github_login": { + "method": "elicitation/create", + "params": { + "message": "Please provide your GitHub username", + "requestedSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + } + } + } + }, + "requestState": "eyJsb2NhdGlvbiI6Ik5ldyBZb3JrIn0" +} diff --git a/schema/draft/examples/IncompleteResult/incomplete-result-with-request-state-only.json b/schema/draft/examples/IncompleteResult/incomplete-result-with-request-state-only.json new file mode 100644 index 000000000..7f169a17b --- /dev/null +++ b/schema/draft/examples/IncompleteResult/incomplete-result-with-request-state-only.json @@ -0,0 +1,3 @@ +{ + "requestState": "eyJwcm9ncmVzcyI6IjUwJSIsInN0YXRlIjoicHJvY2Vzc2luZyJ9" +} diff --git a/schema/draft/examples/InputRequest/elicitation-input-request.json b/schema/draft/examples/InputRequest/elicitation-input-request.json new file mode 100644 index 000000000..acf32ed0c --- /dev/null +++ b/schema/draft/examples/InputRequest/elicitation-input-request.json @@ -0,0 +1,15 @@ +{ + "method": "elicitation/create", + "params": { + "message": "Please provide your GitHub username", + "requestedSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + } + } +} diff --git a/schema/draft/examples/InputRequest/sampling-input-request.json b/schema/draft/examples/InputRequest/sampling-input-request.json new file mode 100644 index 000000000..77e74bc18 --- /dev/null +++ b/schema/draft/examples/InputRequest/sampling-input-request.json @@ -0,0 +1,15 @@ +{ + "method": "sampling/createMessage", + "params": { + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "What is the capital of France?" + } + } + ], + "maxTokens": 100 + } +} diff --git a/schema/draft/examples/TaskInputResponseRequest/task-input-response-request.json b/schema/draft/examples/TaskInputResponseRequest/task-input-response-request.json new file mode 100644 index 000000000..610dd5661 --- /dev/null +++ b/schema/draft/examples/TaskInputResponseRequest/task-input-response-request.json @@ -0,0 +1,22 @@ +{ + "jsonrpc": "2.0", + "id": 4, + "method": "tasks/input_response", + "params": { + "inputResponses": { + "echo_input": { + "result": { + "action": "accept", + "content": { + "input": "Hello World!" + } + } + } + }, + "_meta": { + "io.modelcontextprotocol/related-task": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} diff --git a/schema/draft/examples/TaskInputResponseRequestParams/task-input-response-params.json b/schema/draft/examples/TaskInputResponseRequestParams/task-input-response-params.json new file mode 100644 index 000000000..60e8cd1bc --- /dev/null +++ b/schema/draft/examples/TaskInputResponseRequestParams/task-input-response-params.json @@ -0,0 +1,17 @@ +{ + "inputResponses": { + "echo_input": { + "result": { + "action": "accept", + "content": { + "input": "Hello World!" + } + } + } + }, + "_meta": { + "io.modelcontextprotocol/related-task": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } +} diff --git a/schema/draft/schema.json b/schema/draft/schema.json index 6757370cf..5b50aa455 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -156,10 +156,18 @@ "description": "Arguments to use for the tool call.", "type": "object" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult} for this tool call. Present only when\nretrying a request after receiving an incomplete result." + }, "name": { "description": "The name of the tool.", "type": "string" }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" + }, "task": { "$ref": "#/$defs/TaskMetadata", "description": "If specified, the caller is requesting task-augmented execution for this request.\nThe request will return a {@link CreateTaskResult} immediately, and the actual result can be\nretrieved later via {@link GetTaskPayloadRequesttasks/result}.\n\nTask augmentation is subject to capability negotiation - receivers MUST declare support\nfor task augmentation of specific request types in their capabilities." @@ -507,6 +515,9 @@ { "$ref": "#/$defs/ListTasksRequest" }, + { + "$ref": "#/$defs/TaskInputResponseRequest" + }, { "$ref": "#/$defs/SetLevelRequest" }, @@ -1130,6 +1141,23 @@ ], "type": "object" }, + "ElicitationInputRequest": { + "description": "An elicitation input request that the server wants the client to fulfill\nas part of a multi round-trip interaction.", + "properties": { + "method": { + "const": "elicitation/create", + "type": "string" + }, + "params": { + "$ref": "#/$defs/ElicitRequestParams" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, "EmbeddedResource": { "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit\nof the LLM and/or the user.", "properties": { @@ -1544,6 +1572,23 @@ ], "type": "object" }, + "IncompleteResult": { + "description": "An incomplete result sent by the server to indicate that additional input is needed\nbefore the request can be completed. This is used in the ephemeral multi round-trip\nworkflow.\n\nAt least one of `inputRequests` or `requestState` MUST be present.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "inputRequests": { + "$ref": "#/$defs/InputRequests", + "description": "Requests issued by the server that the client must fulfill before retrying\nthe original request." + }, + "requestState": { + "description": "Opaque state to be passed back to the server when the client retries the\noriginal request. Clients MUST treat this as an opaque blob and MUST NOT\ninspect, parse, or modify it.", + "type": "string" + } + }, + "type": "object" + }, "InitializeRequest": { "description": "This request is sent from the client to the server when it first connects, asking it to begin initialization.", "properties": { @@ -1664,6 +1709,40 @@ ], "type": "object" }, + "InputRequest": { + "anyOf": [ + { + "$ref": "#/$defs/ElicitationInputRequest" + }, + { + "$ref": "#/$defs/SamplingInputRequest" + } + ], + "description": "A request that the server wants the client to fulfill as part of a multi round-trip\ninteraction. The object contains the method name and params of the server-initiated\nrequest (e.g., an elicitation or sampling request), but without the JSON-RPC `id` field." + }, + "InputRequests": { + "additionalProperties": { + "$ref": "#/$defs/InputRequest" + }, + "description": "A map of server-initiated requests that the client must fulfill.\nKeys are server-assigned identifiers; values are the request objects.", + "type": "object" + }, + "InputResponses": { + "additionalProperties": { + "properties": { + "result": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "result" + ], + "type": "object" + }, + "description": "A map of client responses to server-initiated requests.\nKeys correspond to the keys in the {@link InputRequests} map;\nvalues are the client's result for each request.", + "type": "object" + }, "InternalError": { "description": "A JSON-RPC error indicating that an internal error occurred on the receiver. This error is returned when the receiver encounters an unexpected condition that prevents it from fulfilling the request.", "properties": { @@ -1750,6 +1829,27 @@ ], "type": "object" }, + "JSONRPCIncompleteResultResponse": { + "description": "A response to a request that indicates the result is incomplete and\nadditional input is needed.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "$ref": "#/$defs/IncompleteResult" + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, "JSONRPCMessage": { "anyOf": [ { @@ -1763,6 +1863,9 @@ }, { "$ref": "#/$defs/JSONRPCErrorResponse" + }, + { + "$ref": "#/$defs/JSONRPCIncompleteResultResponse" } ], "description": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent." @@ -1820,6 +1923,9 @@ }, { "$ref": "#/$defs/JSONRPCErrorResponse" + }, + { + "$ref": "#/$defs/JSONRPCIncompleteResultResponse" } ], "description": "A response to a request, containing either the result or error." @@ -3269,6 +3375,23 @@ ], "type": "object" }, + "SamplingInputRequest": { + "description": "A sampling input request that the server wants the client to fulfill\nas part of a multi round-trip interaction.", + "properties": { + "method": { + "const": "sampling/createMessage", + "type": "string" + }, + "params": { + "$ref": "#/$defs/CreateMessageRequestParams" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, "SamplingMessage": { "description": "Describes a message issued to or received from an LLM API.", "properties": { @@ -3772,6 +3895,73 @@ }, "type": "object" }, + "TaskInputResponseRequest": { + "description": "A request from the client to deliver input responses for a task\nthat is in `input_required` status.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tasks/input_response", + "type": "string" + }, + "params": { + "$ref": "#/$defs/TaskInputResponseRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "TaskInputResponseRequestParams": { + "description": "Parameters for a `tasks/input_response` request.\nUsed to deliver client responses to server-initiated input requests\nfor a task in the persistent multi round-trip workflow.", + "properties": { + "_meta": { + "$ref": "#/$defs/RequestMetaObject" + }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "The client's responses to the server's input requests." + } + }, + "required": [ + "inputResponses" + ], + "type": "object" + }, + "TaskInputResponseResult": { + "$ref": "#/$defs/Result", + "description": "Common result fields." + }, + "TaskInputResponseResultResponse": { + "description": "A successful response for a {@link TaskInputResponseRequesttasks/input_response} request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "$ref": "#/$defs/Result" + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, "TaskMetadata": { "description": "Metadata for augmenting a request with task execution.\nInclude this in the `task` field of the request parameters.", "properties": { diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index f89665acc..7df2b7224 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -196,7 +196,7 @@ export interface JSONRPCErrorResponse { * * @category JSON-RPC */ -export type JSONRPCResponse = JSONRPCResultResponse | JSONRPCErrorResponse; +export type JSONRPCResponse = JSONRPCResultResponse | JSONRPCErrorResponse | JSONRPCIncompleteResultResponse; // Standard JSON-RPC error codes export const PARSE_ERROR = -32700; @@ -1580,6 +1580,19 @@ export interface CallToolRequestParams extends TaskAugmentedRequestParams { * Arguments to use for the tool call. */ arguments?: { [key: string]: unknown }; + /** + * Responses to server-initiated input requests from a previous + * {@link IncompleteResult} for this tool call. Present only when + * retrying a request after receiving an incomplete result. + */ + inputResponses?: InputResponses; + /** + * Opaque request state echoed back from a previous {@link IncompleteResult}. + * Clients MUST return this value exactly as received. Present only when + * retrying a request after receiving an incomplete result that included + * a `requestState` field. + */ + requestState?: string; } /** @@ -1993,6 +2006,154 @@ export interface TaskStatusNotification extends JSONRPCNotification { params: TaskStatusNotificationParams; } +/* Multi Round-Trip Requests */ + +/** + * An elicitation input request that the server wants the client to fulfill + * as part of a multi round-trip interaction. + * + * @example Elicitation input request + * {@includeCode ./examples/InputRequest/elicitation-input-request.json} + * + * @category Multi Round-Trip + */ +export interface ElicitationInputRequest { + method: "elicitation/create"; + params: ElicitRequestParams; +} + +/** + * A sampling input request that the server wants the client to fulfill + * as part of a multi round-trip interaction. + * + * @example Sampling input request + * {@includeCode ./examples/InputRequest/sampling-input-request.json} + * + * @category Multi Round-Trip + */ +export interface SamplingInputRequest { + method: "sampling/createMessage"; + params: CreateMessageRequestParams; +} + +/** + * A request that the server wants the client to fulfill as part of a multi round-trip + * interaction. The object contains the method name and params of the server-initiated + * request (e.g., an elicitation or sampling request), but without the JSON-RPC `id` field. + * + * @category Multi Round-Trip + */ +export type InputRequest = ElicitationInputRequest | SamplingInputRequest; + +/** + * A map of server-initiated requests that the client must fulfill. + * Keys are server-assigned identifiers; values are the request objects. + * + * @category Multi Round-Trip + */ +export interface InputRequests { + [key: string]: InputRequest; +} + +/** + * A map of client responses to server-initiated requests. + * Keys correspond to the keys in the {@link InputRequests} map; + * values are the client's result for each request. + * + * @category Multi Round-Trip + */ +export interface InputResponses { + [key: string]: { result: { [key: string]: unknown } }; +} + +/** + * An incomplete result sent by the server to indicate that additional input is needed + * before the request can be completed. This is used in the ephemeral multi round-trip + * workflow. + * + * At least one of `inputRequests` or `requestState` MUST be present. + * + * @example Incomplete result with input requests + * {@includeCode ./examples/IncompleteResult/incomplete-result-with-input-requests.json} + * + * @example Incomplete result with request state only (load shedding) + * {@includeCode ./examples/IncompleteResult/incomplete-result-with-request-state-only.json} + * + * @category Multi Round-Trip + */ +export interface IncompleteResult extends Result { + /** + * Requests issued by the server that the client must fulfill before retrying + * the original request. + */ + inputRequests?: InputRequests; + /** + * Opaque state to be passed back to the server when the client retries the + * original request. Clients MUST treat this as an opaque blob and MUST NOT + * inspect, parse, or modify it. + */ + requestState?: string; +} + +/** + * A response to a request that indicates the result is incomplete and + * additional input is needed. + * + * @category JSON-RPC + */ +export interface JSONRPCIncompleteResultResponse { + jsonrpc: typeof JSONRPC_VERSION; + id: RequestId; + result: IncompleteResult; +} + +/** + * Parameters for a `tasks/input_response` request. + * Used to deliver client responses to server-initiated input requests + * for a task in the persistent multi round-trip workflow. + * + * @example Task input response params + * {@includeCode ./examples/TaskInputResponseRequestParams/task-input-response-params.json} + * + * @category `tasks/input_response` + */ +export interface TaskInputResponseRequestParams extends RequestParams { + /** + * The client's responses to the server's input requests. + */ + inputResponses: InputResponses; +} + +/** + * A request from the client to deliver input responses for a task + * that is in `input_required` status. + * + * @example Task input response request + * {@includeCode ./examples/TaskInputResponseRequest/task-input-response-request.json} + * + * @category `tasks/input_response` + */ +export interface TaskInputResponseRequest extends JSONRPCRequest { + method: "tasks/input_response"; + params: TaskInputResponseRequestParams; +} + +/** + * The result returned for a {@link TaskInputResponseRequest | tasks/input_response} request. + * + * @category `tasks/input_response` + */ +export type TaskInputResponseResult = Result; + +/** + * A successful response for a {@link TaskInputResponseRequest | tasks/input_response} request. + * + * @category `tasks/input_response` + */ +export interface TaskInputResponseResultResponse extends JSONRPCResultResponse { + result: TaskInputResponseResult; +} + /* Logging */ /** @@ -3191,7 +3352,8 @@ export type ClientRequest = | GetTaskRequest | GetTaskPayloadRequest | ListTasksRequest - | CancelTaskRequest; + | CancelTaskRequest + | TaskInputResponseRequest; /** @internal */ export type ClientNotification = From 21b44818646b310c28da98591a4da090bf4909e8 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 23 Feb 2026 10:07:45 -0800 Subject: [PATCH 03/27] hand edits --- schema/draft/schema.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 7df2b7224..afeb83eaf 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -2067,9 +2067,8 @@ export interface InputResponses { } /** - * An incomplete result sent by the server to indicate that additional input is needed - * before the request can be completed. This is used in the ephemeral multi round-trip - * workflow. + * An IncompleteResult sent by the server to indicate that additional input is needed + * before the request can be completed. * * At least one of `inputRequests` or `requestState` MUST be present. * @@ -2117,7 +2116,7 @@ export interface JSONRPCIncompleteResultResponse { * * @category `tasks/input_response` */ -export interface TaskInputResponseRequestParams extends RequestParams { +export interface TaskInputResponseRequestParams extends TaskAugmentedRequestParams { /** * The client's responses to the server's input requests. */ @@ -2138,20 +2137,13 @@ export interface TaskInputResponseRequest extends JSONRPCRequest { params: TaskInputResponseRequestParams; } -/** - * The result returned for a {@link TaskInputResponseRequest | tasks/input_response} request. - * - * @category `tasks/input_response` - */ -export type TaskInputResponseResult = Result; - /** * A successful response for a {@link TaskInputResponseRequest | tasks/input_response} request. * * @category `tasks/input_response` */ export interface TaskInputResponseResultResponse extends JSONRPCResultResponse { - result: TaskInputResponseResult; + result: Result; } /* Logging */ From df843c7b75eaec752a07a5df5a39eb41f73451d3 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 23 Feb 2026 10:33:05 -0800 Subject: [PATCH 04/27] add Incomplete Result to server result, add sections to schema.mdx, remove garbage file --- docs/specification/$(basename | 0 schema/draft/schema.json | 13 ++++++++----- schema/draft/schema.mdx | 8 ++++++++ schema/draft/schema.ts | 3 ++- 4 files changed, 18 insertions(+), 6 deletions(-) delete mode 100644 docs/specification/$(basename diff --git a/docs/specification/$(basename b/docs/specification/$(basename deleted file mode 100644 index e69de29bb..000000000 diff --git a/schema/draft/schema.json b/schema/draft/schema.json index 5b50aa455..e9d9acb84 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -1573,7 +1573,7 @@ "type": "object" }, "IncompleteResult": { - "description": "An incomplete result sent by the server to indicate that additional input is needed\nbefore the request can be completed. This is used in the ephemeral multi round-trip\nworkflow.\n\nAt least one of `inputRequests` or `requestState` MUST be present.", + "description": "An IncompleteResult sent by the server to indicate that additional input is needed\nbefore the request can be completed. \n\nAt least one of `inputRequests` or `requestState` MUST be present.", "properties": { "_meta": { "$ref": "#/$defs/MetaObject" @@ -3663,6 +3663,9 @@ { "$ref": "#/$defs/ListTasksResult" }, + { + "$ref": "#/$defs/IncompleteResult" + }, { "$ref": "#/$defs/CompleteResult" } @@ -3930,6 +3933,10 @@ "inputResponses": { "$ref": "#/$defs/InputResponses", "description": "The client's responses to the server's input requests." + }, + "task": { + "$ref": "#/$defs/TaskMetadata", + "description": "If specified, the caller is requesting task-augmented execution for this request.\nThe request will return a {@link CreateTaskResult} immediately, and the actual result can be\nretrieved later via {@link GetTaskPayloadRequesttasks/result}.\n\nTask augmentation is subject to capability negotiation - receivers MUST declare support\nfor task augmentation of specific request types in their capabilities." } }, "required": [ @@ -3937,10 +3944,6 @@ ], "type": "object" }, - "TaskInputResponseResult": { - "$ref": "#/$defs/Result", - "description": "Common result fields." - }, "TaskInputResponseResultResponse": { "description": "A successful response for a {@link TaskInputResponseRequesttasks/input_response} request.", "properties": { diff --git a/schema/draft/schema.mdx b/schema/draft/schema.mdx index a2a4d9336..5ec2a5bda 100644 --- a/schema/draft/schema.mdx +++ b/schema/draft/schema.mdx @@ -104,6 +104,14 @@ title: Schema Reference {/* @category `tasks/cancel` */} +## `tasks/input_response` + +{/* @category `tasks/input_response` */} + +## Multi Round-Trip + +{/* @category Multi Round-Trip */} + ## `prompts/get` {/* @category `prompts/get` */} diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index afeb83eaf..46600d4d8 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -3406,4 +3406,5 @@ export type ServerResult = | GetTaskResult | GetTaskPayloadResult | ListTasksResult - | CancelTaskResult; + | CancelTaskResult + | IncompleteResult; From 2bec19bcc1f790f9d72e866ab80dfa5aceed10eb Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 23 Feb 2026 10:53:34 -0800 Subject: [PATCH 05/27] =?UTF-8?q?1.=20Moved=20inputResponses/requestState?= =?UTF-8?q?=20from=20CallToolRequestParams=20=E2=86=92=20RequestParams=20?= =?UTF-8?q?=E2=80=94=20now=20available=20on=20any=20=20=20client-initiated?= =?UTF-8?q?=20request,=20not=20just=20tool=20calls=20=20=20=202.=20Added?= =?UTF-8?q?=20typed=20InputResponse=20=3D=20ElicitResult=20|=20CreateMessa?= =?UTF-8?q?geResult=20=E2=80=94=20InputResponses=20values=20are=20now=20pr?= =?UTF-8?q?operly=20typed=20=20=20instead=20of=20{=20result:=20{=20[key:?= =?UTF-8?q?=20string]:=20unknown=20}=20}=20=20=20=203.=20Updated=20example?= =?UTF-8?q?s=20=E2=80=94=20removed=20the=20extra=20result=20wrapper=20from?= =?UTF-8?q?=20TaskInputResponseRequest=20and=20=20=20TaskInputResponseRequ?= =?UTF-8?q?estParams=20examples=20to=20match=20the=20new=20typed=20schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All checks pass: ✅ TypeScript compiles, ✅ JSON schema up to date, ✅ 140/140 examples valid. Caitie Note: Need to take a look at task-input-response-params.json looks wrong on cursory inspection. --- .../task-input-response-request.json | 8 +- .../task-input-response-params.json | 8 +- schema/draft/schema.json | 146 ++++++++++++++++-- schema/draft/schema.ts | 35 +++-- 4 files changed, 159 insertions(+), 38 deletions(-) diff --git a/schema/draft/examples/TaskInputResponseRequest/task-input-response-request.json b/schema/draft/examples/TaskInputResponseRequest/task-input-response-request.json index 610dd5661..f919cbdc5 100644 --- a/schema/draft/examples/TaskInputResponseRequest/task-input-response-request.json +++ b/schema/draft/examples/TaskInputResponseRequest/task-input-response-request.json @@ -5,11 +5,9 @@ "params": { "inputResponses": { "echo_input": { - "result": { - "action": "accept", - "content": { - "input": "Hello World!" - } + "action": "accept", + "content": { + "input": "Hello World!" } } }, diff --git a/schema/draft/examples/TaskInputResponseRequestParams/task-input-response-params.json b/schema/draft/examples/TaskInputResponseRequestParams/task-input-response-params.json index 60e8cd1bc..e95070aab 100644 --- a/schema/draft/examples/TaskInputResponseRequestParams/task-input-response-params.json +++ b/schema/draft/examples/TaskInputResponseRequestParams/task-input-response-params.json @@ -1,11 +1,9 @@ { "inputResponses": { "echo_input": { - "result": { - "action": "accept", - "content": { - "input": "Hello World!" - } + "action": "accept", + "content": { + "input": "Hello World!" } } }, diff --git a/schema/draft/schema.json b/schema/draft/schema.json index e9d9acb84..3d23e6025 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -158,7 +158,7 @@ }, "inputResponses": { "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult} for this tool call. Present only when\nretrying a request after receiving an incomplete result." + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." }, "name": { "description": "The name of the tool.", @@ -546,13 +546,13 @@ "$ref": "#/$defs/ListTasksResult" }, { - "$ref": "#/$defs/CreateMessageResult" + "$ref": "#/$defs/ElicitResult" }, { - "$ref": "#/$defs/ListRootsResult" + "$ref": "#/$defs/CreateMessageResult" }, { - "$ref": "#/$defs/ElicitResult" + "$ref": "#/$defs/ListRootsResult" } ] }, @@ -619,6 +619,10 @@ }, "type": "object" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, "ref": { "anyOf": [ { @@ -628,6 +632,10 @@ "$ref": "#/$defs/ResourceTemplateReference" } ] + }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" } }, "required": [ @@ -752,6 +760,10 @@ ], "type": "string" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, "maxTokens": { "description": "The requested maximum number of tokens to sample (to prevent runaway completions).\n\nThe client MAY choose to sample fewer tokens than the requested maximum.", "type": "integer" @@ -772,6 +784,10 @@ "$ref": "#/$defs/ModelPreferences", "description": "The server's preferences for which model to select. The client MAY ignore these preferences." }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" + }, "stopSequences": { "items": { "type": "string" @@ -950,6 +966,10 @@ "_meta": { "$ref": "#/$defs/RequestMetaObject" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, "message": { "description": "The message to present to the user describing what information is being requested.", "type": "string" @@ -959,6 +979,10 @@ "description": "The elicitation mode.", "type": "string" }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" + }, "requestedSchema": { "description": "A restricted subset of JSON Schema.\nOnly top-level properties are allowed, without nesting.", "properties": { @@ -1020,6 +1044,10 @@ "description": "The ID of the elicitation, which must be unique within the context of the server.\nThe client MUST treat this ID as an opaque value.", "type": "string" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, "message": { "description": "The message to present to the user explaining why the interaction is needed.", "type": "string" @@ -1029,6 +1057,10 @@ "description": "The elicitation mode.", "type": "string" }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" + }, "task": { "$ref": "#/$defs/TaskMetadata", "description": "If specified, the caller is requesting task-augmented execution for this request.\nThe request will return a {@link CreateTaskResult} immediately, and the actual result can be\nretrieved later via {@link GetTaskPayloadRequesttasks/result}.\n\nTask augmentation is subject to capability negotiation - receivers MUST declare support\nfor task augmentation of specific request types in their capabilities." @@ -1271,9 +1303,17 @@ "description": "Arguments to use for templating the prompt.", "type": "object" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, "name": { "description": "The name of the prompt or prompt template.", "type": "string" + }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" } }, "required": [ @@ -1627,9 +1667,17 @@ "clientInfo": { "$ref": "#/$defs/Implementation" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, "protocolVersion": { "description": "The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well.", "type": "string" + }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" } }, "required": [ @@ -1727,18 +1775,20 @@ "description": "A map of server-initiated requests that the client must fulfill.\nKeys are server-assigned identifiers; values are the request objects.", "type": "object" }, + "InputResponse": { + "anyOf": [ + { + "$ref": "#/$defs/ElicitResult" + }, + { + "$ref": "#/$defs/CreateMessageResult" + } + ], + "description": "The client's response to a single server-initiated input request." + }, "InputResponses": { "additionalProperties": { - "properties": { - "result": { - "additionalProperties": {}, - "type": "object" - } - }, - "required": [ - "result" - ], - "type": "object" + "$ref": "#/$defs/InputResponse" }, "description": "A map of client responses to server-initiated requests.\nKeys correspond to the keys in the {@link InputRequests} map;\nvalues are the client's result for each request.", "type": "object" @@ -2614,6 +2664,14 @@ "cursor": { "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", "type": "string" + }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" } }, "type": "object" @@ -2939,6 +2997,14 @@ "_meta": { "$ref": "#/$defs/RequestMetaObject" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" + }, "uri": { "description": "The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it.", "format": "uri", @@ -3046,6 +3112,14 @@ "properties": { "_meta": { "$ref": "#/$defs/RequestMetaObject" + }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" } }, "type": "object" @@ -3201,6 +3275,14 @@ "_meta": { "$ref": "#/$defs/RequestMetaObject" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" + }, "uri": { "description": "The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it.", "format": "uri", @@ -3703,9 +3785,17 @@ "_meta": { "$ref": "#/$defs/RequestMetaObject" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, "level": { "$ref": "#/$defs/LoggingLevel", "description": "The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as {@link LoggingMessageNotificationnotifications/message}." + }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" } }, "required": [ @@ -3812,6 +3902,14 @@ "_meta": { "$ref": "#/$defs/RequestMetaObject" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" + }, "uri": { "description": "The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it.", "format": "uri", @@ -3891,6 +3989,14 @@ "_meta": { "$ref": "#/$defs/RequestMetaObject" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" + }, "task": { "$ref": "#/$defs/TaskMetadata", "description": "If specified, the caller is requesting task-augmented execution for this request.\nThe request will return a {@link CreateTaskResult} immediately, and the actual result can be\nretrieved later via {@link GetTaskPayloadRequesttasks/result}.\n\nTask augmentation is subject to capability negotiation - receivers MUST declare support\nfor task augmentation of specific request types in their capabilities." @@ -3934,6 +4040,10 @@ "$ref": "#/$defs/InputResponses", "description": "The client's responses to the server's input requests." }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" + }, "task": { "$ref": "#/$defs/TaskMetadata", "description": "If specified, the caller is requesting task-augmented execution for this request.\nThe request will return a {@link CreateTaskResult} immediately, and the actual result can be\nretrieved later via {@link GetTaskPayloadRequesttasks/result}.\n\nTask augmentation is subject to capability negotiation - receivers MUST declare support\nfor task augmentation of specific request types in their capabilities." @@ -4516,6 +4626,14 @@ "_meta": { "$ref": "#/$defs/RequestMetaObject" }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" + }, "uri": { "description": "The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it.", "format": "uri", diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 46600d4d8..5273934a2 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -88,6 +88,19 @@ export interface TaskAugmentedRequestParams extends RequestParams { */ export interface RequestParams { _meta?: RequestMetaObject; + /** + * Responses to server-initiated input requests from a previous + * {@link IncompleteResult}. Present only when retrying a request + * after receiving an incomplete result. + */ + inputResponses?: InputResponses; + /** + * Opaque request state echoed back from a previous {@link IncompleteResult}. + * Clients MUST return this value exactly as received. Present only when + * retrying a request after receiving an incomplete result that included + * a `requestState` field. + */ + requestState?: string; } /** @internal */ @@ -1580,19 +1593,6 @@ export interface CallToolRequestParams extends TaskAugmentedRequestParams { * Arguments to use for the tool call. */ arguments?: { [key: string]: unknown }; - /** - * Responses to server-initiated input requests from a previous - * {@link IncompleteResult} for this tool call. Present only when - * retrying a request after receiving an incomplete result. - */ - inputResponses?: InputResponses; - /** - * Opaque request state echoed back from a previous {@link IncompleteResult}. - * Clients MUST return this value exactly as received. Present only when - * retrying a request after receiving an incomplete result that included - * a `requestState` field. - */ - requestState?: string; } /** @@ -2055,6 +2055,13 @@ export interface InputRequests { [key: string]: InputRequest; } +/** + * The client's response to a single server-initiated input request. + * + * @category Multi Round-Trip + */ +export type InputResponse = ElicitResult | CreateMessageResult; + /** * A map of client responses to server-initiated requests. * Keys correspond to the keys in the {@link InputRequests} map; @@ -2063,7 +2070,7 @@ export interface InputRequests { * @category Multi Round-Trip */ export interface InputResponses { - [key: string]: { result: { [key: string]: unknown } }; + [key: string]: InputResponse; } /** From 7519d64bd2169a6381ec65db58d51090f1dbd06e Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 23 Feb 2026 11:04:28 -0800 Subject: [PATCH 06/27] =?UTF-8?q?renamed=20ElicitationInputRequest=20?= =?UTF-8?q?=E2=86=92=20ElicitationCreateRequest=20and=20SamplingInputReque?= =?UTF-8?q?st=20=E2=86=92=20SamplingCreateRequest.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- schema/draft/schema.json | 8 ++++---- schema/draft/schema.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/schema/draft/schema.json b/schema/draft/schema.json index 3d23e6025..5f2f2a0d3 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -1173,7 +1173,7 @@ ], "type": "object" }, - "ElicitationInputRequest": { + "ElicitationCreateRequest": { "description": "An elicitation input request that the server wants the client to fulfill\nas part of a multi round-trip interaction.", "properties": { "method": { @@ -1760,10 +1760,10 @@ "InputRequest": { "anyOf": [ { - "$ref": "#/$defs/ElicitationInputRequest" + "$ref": "#/$defs/ElicitationCreateRequest" }, { - "$ref": "#/$defs/SamplingInputRequest" + "$ref": "#/$defs/SamplingCreateRequest" } ], "description": "A request that the server wants the client to fulfill as part of a multi round-trip\ninteraction. The object contains the method name and params of the server-initiated\nrequest (e.g., an elicitation or sampling request), but without the JSON-RPC `id` field." @@ -3457,7 +3457,7 @@ ], "type": "object" }, - "SamplingInputRequest": { + "SamplingCreateRequest": { "description": "A sampling input request that the server wants the client to fulfill\nas part of a multi round-trip interaction.", "properties": { "method": { diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 5273934a2..fd1f4e07c 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -2017,7 +2017,7 @@ export interface TaskStatusNotification extends JSONRPCNotification { * * @category Multi Round-Trip */ -export interface ElicitationInputRequest { +export interface ElicitationCreateRequest { method: "elicitation/create"; params: ElicitRequestParams; } @@ -2031,7 +2031,7 @@ export interface ElicitationInputRequest { * * @category Multi Round-Trip */ -export interface SamplingInputRequest { +export interface SamplingCreateRequest { method: "sampling/createMessage"; params: CreateMessageRequestParams; } @@ -2043,7 +2043,7 @@ export interface SamplingInputRequest { * * @category Multi Round-Trip */ -export type InputRequest = ElicitationInputRequest | SamplingInputRequest; +export type InputRequest = ElicitationCreateRequest | SamplingCreateRequest; /** * A map of server-initiated requests that the client must fulfill. From 8b66f20d4bc451cdfdd6658348feba6cf2013ef5 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 23 Feb 2026 11:30:15 -0800 Subject: [PATCH 07/27] removing standalone ServerRequests for CreateMessageRequests and ElicitRequets these should only be sent as part of IncompleteResponse Objects now --- schema/draft/schema.json | 112 ++------------------------------------- schema/draft/schema.ts | 62 ++-------------------- 2 files changed, 7 insertions(+), 167 deletions(-) diff --git a/schema/draft/schema.json b/schema/draft/schema.json index 5f2f2a0d3..924459c5d 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -421,7 +421,7 @@ "properties": { "create": { "additionalProperties": true, - "description": "Whether the client supports task-augmented {@link ElicitRequestelicitation/create} requests.", + "description": "Whether the client supports task-augmented {@link ElicitationCreateRequestelicitation/create} requests.", "properties": {}, "type": "object" } @@ -545,12 +545,6 @@ { "$ref": "#/$defs/ListTasksResult" }, - { - "$ref": "#/$defs/ElicitResult" - }, - { - "$ref": "#/$defs/CreateMessageResult" - }, { "$ref": "#/$defs/ListRootsResult" } @@ -719,32 +713,6 @@ } ] }, - "CreateMessageRequest": { - "description": "A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.", - "properties": { - "id": { - "$ref": "#/$defs/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "method": { - "const": "sampling/createMessage", - "type": "string" - }, - "params": { - "$ref": "#/$defs/CreateMessageRequestParams" - } - }, - "required": [ - "id", - "jsonrpc", - "method", - "params" - ], - "type": "object" - }, "CreateMessageRequestParams": { "description": "Parameters for a `sampling/createMessage` request.", "properties": { @@ -824,7 +792,7 @@ "type": "object" }, "CreateMessageResult": { - "description": "The result returned by the client for a {@link CreateMessageRequestsampling/createMessage} request.\nThe client should inform the user before returning the sampled message, to allow them\nto inspect the response (human in the loop) and decide whether to allow the server to see it.", + "description": "The result returned by the client for a {@link SamplingCreateRequestsampling/createMessage} request.\nThe client should inform the user before returning the sampled message, to allow them\nto inspect the response (human in the loop) and decide whether to allow the server to see it.", "properties": { "_meta": { "$ref": "#/$defs/MetaObject" @@ -873,27 +841,6 @@ ], "type": "object" }, - "CreateMessageResultResponse": { - "description": "A successful response from the client for a {@link CreateMessageRequestsampling/createMessage} request.", - "properties": { - "id": { - "$ref": "#/$defs/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "result": { - "$ref": "#/$defs/CreateMessageResult" - } - }, - "required": [ - "id", - "jsonrpc", - "result" - ], - "type": "object" - }, "CreateTaskResult": { "description": "The result returned for a task-augmented request.", "properties": { @@ -934,32 +881,6 @@ "description": "An opaque token used to represent a cursor for pagination.", "type": "string" }, - "ElicitRequest": { - "description": "A request from the server to elicit additional information from the user via the client.", - "properties": { - "id": { - "$ref": "#/$defs/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "method": { - "const": "elicitation/create", - "type": "string" - }, - "params": { - "$ref": "#/$defs/ElicitRequestParams" - } - }, - "required": [ - "id", - "jsonrpc", - "method", - "params" - ], - "type": "object" - }, "ElicitRequestFormParams": { "description": "The parameters for a request to elicit non-sensitive information from the user via a form in the client.", "properties": { @@ -1080,7 +1001,7 @@ "type": "object" }, "ElicitResult": { - "description": "The result returned by the client for an {@link ElicitRequestelicitation/create} request.", + "description": "The result returned by the client for an {@link ElicitationCreateRequestelicitation/create} request.", "properties": { "_meta": { "$ref": "#/$defs/MetaObject" @@ -1121,27 +1042,6 @@ ], "type": "object" }, - "ElicitResultResponse": { - "description": "A successful response from the client for a {@link ElicitRequestelicitation/create} request.", - "properties": { - "id": { - "$ref": "#/$defs/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "result": { - "$ref": "#/$defs/ElicitResult" - } - }, - "required": [ - "id", - "jsonrpc", - "result" - ], - "type": "object" - }, "ElicitationCompleteNotification": { "description": "An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request.", "properties": { @@ -3688,14 +3588,8 @@ { "$ref": "#/$defs/ListTasksRequest" }, - { - "$ref": "#/$defs/CreateMessageRequest" - }, { "$ref": "#/$defs/ListRootsRequest" - }, - { - "$ref": "#/$defs/ElicitRequest" } ] }, diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index fd1f4e07c..21aaae53f 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -553,7 +553,7 @@ export interface ClientCapabilities { */ sampling?: { /** - * Whether the client supports task-augmented `sampling/createMessage` requests. + * Whether the client supports task-augmented {@link SamplingCreateRequest | sampling/createMessage} requests. */ createMessage?: object; }; @@ -562,7 +562,7 @@ export interface ClientCapabilities { */ elicitation?: { /** - * Whether the client supports task-augmented {@link ElicitRequest | elicitation/create} requests. + * Whether the client supports task-augmented {@link ElicitationCreateRequest | elicitation/create} requests. */ create?: object; }; @@ -2326,20 +2326,7 @@ export interface ToolChoice { } /** - * A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it. - * - * @example Sampling request - * {@includeCode ./examples/CreateMessageRequest/sampling-request.json} - * - * @category `sampling/createMessage` - */ -export interface CreateMessageRequest extends JSONRPCRequest { - method: "sampling/createMessage"; - params: CreateMessageRequestParams; -} - -/** - * The result returned by the client for a {@link CreateMessageRequest | sampling/createMessage} request. + * The result returned by the client for a {@link SamplingCreateRequest | sampling/createMessage} request. * The client should inform the user before returning the sampled message, to allow them * to inspect the response (human in the loop) and decide whether to allow the server to see it. * @@ -2374,18 +2361,6 @@ export interface CreateMessageResult extends Result, SamplingMessage { stopReason?: "endTurn" | "stopSequence" | "maxTokens" | "toolUse" | string; } -/** - * A successful response from the client for a {@link CreateMessageRequest | sampling/createMessage} request. - * - * @example Sampling result response - * {@includeCode ./examples/CreateMessageResultResponse/sampling-result-response.json} - * - * @category `sampling/createMessage` - */ -export interface CreateMessageResultResponse extends JSONRPCResultResponse { - result: CreateMessageResult; -} - /** * Describes a message issued to or received from an LLM API. * @@ -3001,19 +2976,6 @@ export type ElicitRequestParams = | ElicitRequestFormParams | ElicitRequestURLParams; -/** - * A request from the server to elicit additional information from the user via the client. - * - * @example Elicitation request - * {@includeCode ./examples/ElicitRequest/elicitation-request.json} - * - * @category `elicitation/create` - */ -export interface ElicitRequest extends JSONRPCRequest { - method: "elicitation/create"; - params: ElicitRequestParams; -} - /** * Restricted schema definitions that only allow primitive types * without nested objects or arrays. @@ -3272,7 +3234,7 @@ export type EnumSchema = | LegacyTitledEnumSchema; /** - * The result returned by the client for an {@link ElicitRequest | elicitation/create} request. + * The result returned by the client for an {@link ElicitationCreateRequest | elicitation/create} request. * * @example Input single field * {@includeCode ./examples/ElicitResult/input-single-field.json} @@ -3302,18 +3264,6 @@ export interface ElicitResult extends Result { content?: { [key: string]: string | number | boolean | string[] }; } -/** - * A successful response from the client for a {@link ElicitRequest | elicitation/create} request. - * - * @example Elicitation result response - * {@includeCode ./examples/ElicitResultResponse/elicitation-result-response.json} - * - * @category `elicitation/create` - */ -export interface ElicitResultResponse extends JSONRPCResultResponse { - result: ElicitResult; -} - /** * An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request. * @@ -3365,9 +3315,7 @@ export type ClientNotification = /** @internal */ export type ClientResult = | EmptyResult - | CreateMessageResult | ListRootsResult - | ElicitResult | GetTaskResult | GetTaskPayloadResult | ListTasksResult @@ -3377,9 +3325,7 @@ export type ClientResult = /** @internal */ export type ServerRequest = | PingRequest - | CreateMessageRequest | ListRootsRequest - | ElicitRequest | GetTaskRequest | GetTaskPayloadRequest | ListTasksRequest From ca03c4eeb521554de875bc1d65f4490e49998927 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 23 Feb 2026 14:05:16 -0800 Subject: [PATCH 08/27] update examples --- .../sampling-request.json | 27 -------------- .../ElicitRequest/elicitation-request.json | 18 ---------- .../elicitation-result-response.json | 10 ------ ...-result-with-elicitation-and-sampling.json | 35 +++++++++++++++++++ ...icitation-and-sampling-input-requests.json | 32 +++++++++++++++++ .../elicitation-input-response.json | 6 ++++ .../sampling-input-response.json | 9 +++++ ...itation-and-sampling-input-responses.json} | 10 ++++-- schema/draft/schema.json | 2 +- schema/draft/schema.ts | 15 ++++++++ 10 files changed, 105 insertions(+), 59 deletions(-) delete mode 100644 schema/draft/examples/CreateMessageRequest/sampling-request.json delete mode 100644 schema/draft/examples/ElicitRequest/elicitation-request.json delete mode 100644 schema/draft/examples/ElicitResultResponse/elicitation-result-response.json create mode 100644 schema/draft/examples/IncompleteResult/incomplete-result-with-elicitation-and-sampling.json create mode 100644 schema/draft/examples/InputRequests/elicitation-and-sampling-input-requests.json create mode 100644 schema/draft/examples/InputResponse/elicitation-input-response.json create mode 100644 schema/draft/examples/InputResponse/sampling-input-response.json rename schema/draft/examples/{CreateMessageResultResponse/sampling-result-response.json => InputResponses/elicitation-and-sampling-input-responses.json} (61%) diff --git a/schema/draft/examples/CreateMessageRequest/sampling-request.json b/schema/draft/examples/CreateMessageRequest/sampling-request.json deleted file mode 100644 index 99398b8ea..000000000 --- a/schema/draft/examples/CreateMessageRequest/sampling-request.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": "sampling-example", - "method": "sampling/createMessage", - "params": { - "messages": [ - { - "role": "user", - "content": { - "type": "text", - "text": "What is the capital of France?" - } - } - ], - "modelPreferences": { - "hints": [ - { - "name": "claude-3-sonnet" - } - ], - "intelligencePriority": 0.8, - "speedPriority": 0.5 - }, - "systemPrompt": "You are a helpful assistant.", - "maxTokens": 100 - } -} diff --git a/schema/draft/examples/ElicitRequest/elicitation-request.json b/schema/draft/examples/ElicitRequest/elicitation-request.json deleted file mode 100644 index 5b48b7f9c..000000000 --- a/schema/draft/examples/ElicitRequest/elicitation-request.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": "elicitation-example", - "method": "elicitation/create", - "params": { - "mode": "form", - "message": "Please provide your GitHub username", - "requestedSchema": { - "type": "object", - "properties": { - "name": { - "type": "string" - } - }, - "required": ["name"] - } - } -} diff --git a/schema/draft/examples/ElicitResultResponse/elicitation-result-response.json b/schema/draft/examples/ElicitResultResponse/elicitation-result-response.json deleted file mode 100644 index 762c2fc21..000000000 --- a/schema/draft/examples/ElicitResultResponse/elicitation-result-response.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": "elicitation-example", - "result": { - "action": "accept", - "content": { - "name": "octocat" - } - } -} diff --git a/schema/draft/examples/IncompleteResult/incomplete-result-with-elicitation-and-sampling.json b/schema/draft/examples/IncompleteResult/incomplete-result-with-elicitation-and-sampling.json new file mode 100644 index 000000000..5dd7629cb --- /dev/null +++ b/schema/draft/examples/IncompleteResult/incomplete-result-with-elicitation-and-sampling.json @@ -0,0 +1,35 @@ +{ + "inputRequests": { + "github_login": { + "method": "elicitation/create", + "params": { + "message": "Please provide your GitHub username", + "requestedSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + } + } + }, + "capital_of_france": { + "method": "sampling/createMessage", + "params": { + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "What is the capital of France?" + } + } + ], + "maxTokens": 100 + } + } + }, + "requestState": "eyJsb2NhdGlvbiI6Ik5ldyBZb3JrIn0" +} diff --git a/schema/draft/examples/InputRequests/elicitation-and-sampling-input-requests.json b/schema/draft/examples/InputRequests/elicitation-and-sampling-input-requests.json new file mode 100644 index 000000000..fe14634a5 --- /dev/null +++ b/schema/draft/examples/InputRequests/elicitation-and-sampling-input-requests.json @@ -0,0 +1,32 @@ +{ + "github_login": { + "method": "elicitation/create", + "params": { + "message": "Please provide your GitHub username", + "requestedSchema": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + }, + "required": ["name"] + } + } + }, + "capital_of_france": { + "method": "sampling/createMessage", + "params": { + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "What is the capital of France?" + } + } + ], + "maxTokens": 100 + } + } +} diff --git a/schema/draft/examples/InputResponse/elicitation-input-response.json b/schema/draft/examples/InputResponse/elicitation-input-response.json new file mode 100644 index 000000000..4798da663 --- /dev/null +++ b/schema/draft/examples/InputResponse/elicitation-input-response.json @@ -0,0 +1,6 @@ +{ + "action": "accept", + "content": { + "name": "octocat" + } +} diff --git a/schema/draft/examples/InputResponse/sampling-input-response.json b/schema/draft/examples/InputResponse/sampling-input-response.json new file mode 100644 index 000000000..529f683f5 --- /dev/null +++ b/schema/draft/examples/InputResponse/sampling-input-response.json @@ -0,0 +1,9 @@ +{ + "role": "assistant", + "content": { + "type": "text", + "text": "The capital of France is Paris." + }, + "model": "claude-3-sonnet-20240307", + "stopReason": "endTurn" +} diff --git a/schema/draft/examples/CreateMessageResultResponse/sampling-result-response.json b/schema/draft/examples/InputResponses/elicitation-and-sampling-input-responses.json similarity index 61% rename from schema/draft/examples/CreateMessageResultResponse/sampling-result-response.json rename to schema/draft/examples/InputResponses/elicitation-and-sampling-input-responses.json index 1597d566f..1f5cbcf0d 100644 --- a/schema/draft/examples/CreateMessageResultResponse/sampling-result-response.json +++ b/schema/draft/examples/InputResponses/elicitation-and-sampling-input-responses.json @@ -1,7 +1,11 @@ { - "jsonrpc": "2.0", - "id": "sampling-example", - "result": { + "github_login": { + "action": "accept", + "content": { + "name": "octocat" + } + }, + "capital_of_france": { "role": "assistant", "content": { "type": "text", diff --git a/schema/draft/schema.json b/schema/draft/schema.json index 924459c5d..c99dc8026 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -433,7 +433,7 @@ "properties": { "createMessage": { "additionalProperties": true, - "description": "Whether the client supports task-augmented `sampling/createMessage` requests.", + "description": "Whether the client supports task-augmented {@link SamplingCreateRequestsampling/createMessage} requests.", "properties": {}, "type": "object" } diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 21aaae53f..49d062bde 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -2049,6 +2049,9 @@ export type InputRequest = ElicitationCreateRequest | SamplingCreateRequest; * A map of server-initiated requests that the client must fulfill. * Keys are server-assigned identifiers; values are the request objects. * + * @example Elicitation and sampling input requests + * {@includeCode ./examples/InputRequests/elicitation-and-sampling-input-requests.json} + * * @category Multi Round-Trip */ export interface InputRequests { @@ -2058,6 +2061,12 @@ export interface InputRequests { /** * The client's response to a single server-initiated input request. * + * @example Elicitation input response + * {@includeCode ./examples/InputResponse/elicitation-input-response.json} + * + * @example Sampling input response + * {@includeCode ./examples/InputResponse/sampling-input-response.json} + * * @category Multi Round-Trip */ export type InputResponse = ElicitResult | CreateMessageResult; @@ -2067,6 +2076,9 @@ export type InputResponse = ElicitResult | CreateMessageResult; * Keys correspond to the keys in the {@link InputRequests} map; * values are the client's result for each request. * + * @example Elicitation and sampling input responses + * {@includeCode ./examples/InputResponses/elicitation-and-sampling-input-responses.json} + * * @category Multi Round-Trip */ export interface InputResponses { @@ -2082,6 +2094,9 @@ export interface InputResponses { * @example Incomplete result with input requests * {@includeCode ./examples/IncompleteResult/incomplete-result-with-input-requests.json} * + * @example Incomplete result with elicitation and sampling input requests + * {@includeCode ./examples/IncompleteResult/incomplete-result-with-elicitation-and-sampling.json} + * * @example Incomplete result with request state only (load shedding) * {@includeCode ./examples/IncompleteResult/incomplete-result-with-request-state-only.json} * From 944a6a52450844f1a9f5d2999778b4feb921b931 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 23 Feb 2026 14:08:25 -0800 Subject: [PATCH 09/27] moving JSONRPCIncompleteResultResponse location in schema.ts --- schema/draft/schema.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 49d062bde..87cecd0ff 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -193,6 +193,18 @@ export interface JSONRPCResultResponse { result: Result; } +/** + * A response to a request that indicates the result is incomplete and + * additional input is needed. + * + * @category JSON-RPC + */ +export interface JSONRPCIncompleteResultResponse { + jsonrpc: typeof JSONRPC_VERSION; + id: RequestId; + result: IncompleteResult; +} + /** * A response to a request that indicates an error occurred. * @@ -2116,18 +2128,6 @@ export interface IncompleteResult extends Result { requestState?: string; } -/** - * A response to a request that indicates the result is incomplete and - * additional input is needed. - * - * @category JSON-RPC - */ -export interface JSONRPCIncompleteResultResponse { - jsonrpc: typeof JSONRPC_VERSION; - id: RequestId; - result: IncompleteResult; -} - /** * Parameters for a `tasks/input_response` request. * Used to deliver client responses to server-initiated input requests From ac8b3e6b5a7d2a79c5929ea767f377419ef42a21 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 23 Feb 2026 14:21:08 -0800 Subject: [PATCH 10/27] update GetTaskPayloadResultResponse to be a JSONRPCResultResponse or a JSONRPCIncompleteResultResponse and add examples --- .../get-task-payload-request.json | 8 ++++ .../completed-task-payload-response.json | 18 +++++++ .../input-required-task-payload-response.json | 28 +++++++++++ schema/draft/schema.json | 47 ++++++++++--------- schema/draft/schema.ts | 30 ++++++++++-- 5 files changed, 106 insertions(+), 25 deletions(-) create mode 100644 schema/draft/examples/GetTaskPayloadRequest/get-task-payload-request.json create mode 100644 schema/draft/examples/GetTaskPayloadResultResponse/completed-task-payload-response.json create mode 100644 schema/draft/examples/GetTaskPayloadResultResponse/input-required-task-payload-response.json diff --git a/schema/draft/examples/GetTaskPayloadRequest/get-task-payload-request.json b/schema/draft/examples/GetTaskPayloadRequest/get-task-payload-request.json new file mode 100644 index 000000000..632846ebd --- /dev/null +++ b/schema/draft/examples/GetTaskPayloadRequest/get-task-payload-request.json @@ -0,0 +1,8 @@ +{ + "jsonrpc": "2.0", + "id": 3, + "method": "tasks/result", + "params": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } +} diff --git a/schema/draft/examples/GetTaskPayloadResultResponse/completed-task-payload-response.json b/schema/draft/examples/GetTaskPayloadResultResponse/completed-task-payload-response.json new file mode 100644 index 000000000..b53b013db --- /dev/null +++ b/schema/draft/examples/GetTaskPayloadResultResponse/completed-task-payload-response.json @@ -0,0 +1,18 @@ +{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "isError": false, + "content": [ + { + "type": "text", + "text": "Echo: Hello World!" + } + ], + "_meta": { + "io.modelcontextprotocol/related-task": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} diff --git a/schema/draft/examples/GetTaskPayloadResultResponse/input-required-task-payload-response.json b/schema/draft/examples/GetTaskPayloadResultResponse/input-required-task-payload-response.json new file mode 100644 index 000000000..3624faa7f --- /dev/null +++ b/schema/draft/examples/GetTaskPayloadResultResponse/input-required-task-payload-response.json @@ -0,0 +1,28 @@ +{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "inputRequests": { + "echo_input": { + "method": "elicitation/create", + "params": { + "message": "Please provide the input string to echo back", + "requestedSchema": { + "type": "object", + "properties": { + "input": { + "type": "string" + } + }, + "required": ["input"] + } + } + } + }, + "_meta": { + "io.modelcontextprotocol/related-task": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} diff --git a/schema/draft/schema.json b/schema/draft/schema.json index c99dc8026..bc2785cea 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -1265,7 +1265,7 @@ "type": "object" }, "GetTaskPayloadRequest": { - "description": "A request to retrieve the result of a completed task.", + "description": "A request to retrieve the result of a completed task, or to discover\nwhat input is needed for a task in `input_required` status.", "properties": { "id": { "$ref": "#/$defs/RequestId" @@ -1301,7 +1301,7 @@ }, "GetTaskPayloadResult": { "additionalProperties": {}, - "description": "The result returned for a {@link GetTaskPayloadRequesttasks/result} request.\nThe structure matches the result type of the original request.\nFor example, a {@link CallToolRequesttools/call} task would return the {@link CallToolResult} structure.", + "description": "The result returned for a {@link GetTaskPayloadRequesttasks/result} request.\nThe structure matches the result type of the original request.\nFor example, a {@link CallToolRequesttools/call} task would return the {@link CallToolResult} structure.\n\nWhen the task is in `input_required` status, the server MUST return an\n{@link IncompleteResult} containing `inputRequests` that the client must\nfulfill via a {@link TaskInputResponseRequesttasks/input_response} request.", "properties": { "_meta": { "$ref": "#/$defs/MetaObject" @@ -1310,25 +1310,30 @@ "type": "object" }, "GetTaskPayloadResultResponse": { - "description": "A successful response for a {@link GetTaskPayloadRequesttasks/result} request.", - "properties": { - "id": { - "$ref": "#/$defs/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" + "anyOf": [ + { + "$ref": "#/$defs/JSONRPCIncompleteResultResponse" }, - "result": { - "$ref": "#/$defs/GetTaskPayloadResult" + { + "allOf": [ + { + "$ref": "#/$defs/JSONRPCResultResponse" + }, + { + "properties": { + "result": { + "$ref": "#/$defs/GetTaskPayloadResult" + } + }, + "required": [ + "result" + ], + "type": "object" + } + ] } - }, - "required": [ - "id", - "jsonrpc", - "result" ], - "type": "object" + "description": "A successful response for a {@link GetTaskPayloadRequesttasks/result} request.\nMay be either a complete result or an {@link IncompleteResult} indicating\nthat additional input is needed via {@link TaskInputResponseRequesttasks/input_response}." }, "GetTaskRequest": { "description": "A request to retrieve the state of a task.", @@ -3598,6 +3603,9 @@ { "$ref": "#/$defs/Result" }, + { + "$ref": "#/$defs/IncompleteResult" + }, { "$ref": "#/$defs/InitializeResult" }, @@ -3639,9 +3647,6 @@ { "$ref": "#/$defs/ListTasksResult" }, - { - "$ref": "#/$defs/IncompleteResult" - }, { "$ref": "#/$defs/CompleteResult" } diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 87cecd0ff..9cf0c6618 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -1909,7 +1909,11 @@ export interface GetTaskResultResponse extends JSONRPCResultResponse { } /** - * A request to retrieve the result of a completed task. + * A request to retrieve the result of a completed task, or to discover + * what input is needed for a task in `input_required` status. + * + * @example Get task payload request + * {@includeCode ./examples/GetTaskPayloadRequest/get-task-payload-request.json} * * @category `tasks/result` */ @@ -1928,6 +1932,16 @@ export interface GetTaskPayloadRequest extends JSONRPCRequest { * The structure matches the result type of the original request. * For example, a {@link CallToolRequest | tools/call} task would return the {@link CallToolResult} structure. * + * When the task is in `input_required` status, the server MUST return an + * {@link IncompleteResult} containing `inputRequests` that the client must + * fulfill via a {@link TaskInputResponseRequest | tasks/input_response} request. + * + * @example Completed task payload + * {@includeCode ./examples/GetTaskPayloadResult/completed-task-payload.json} + * + * @example Input required task payload + * {@includeCode ./examples/GetTaskPayloadResult/input-required-task-payload.json} + * * @category `tasks/result` */ export interface GetTaskPayloadResult extends Result { @@ -1936,12 +1950,20 @@ export interface GetTaskPayloadResult extends Result { /** * A successful response for a {@link GetTaskPayloadRequest | tasks/result} request. + * May be either a complete result or an {@link IncompleteResult} indicating + * that additional input is needed via {@link TaskInputResponseRequest | tasks/input_response}. + * + * @example Completed task payload response + * {@includeCode ./examples/GetTaskPayloadResultResponse/completed-task-payload-response.json} + * + * @example Input required task payload response + * {@includeCode ./examples/GetTaskPayloadResultResponse/input-required-task-payload-response.json} * * @category `tasks/result` */ -export interface GetTaskPayloadResultResponse extends JSONRPCResultResponse { - result: GetTaskPayloadResult; -} +export type GetTaskPayloadResultResponse = + | (JSONRPCResultResponse & { result: GetTaskPayloadResult }) + | JSONRPCIncompleteResultResponse; /** * A request to cancel a task. From 4f859d1c183434ce270fd9759c9e6bafac71d555 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 23 Feb 2026 15:00:42 -0800 Subject: [PATCH 11/27] update SEP.md to reflect schema changes. Decision: Remove results wrapper in the SEP because InputRequests maps keys directly to the requests so the InputResponses now follows that pattern and there is no benefit to the added wrapper. The only argument for the wrapper would be if we wanted to carry error's alongside results but we do not in these cases. If there is an error the client would retry until the necessary information was retrieved and then send back to the server. --- seps/XXXX-MRTR.md | 102 ++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 58 deletions(-) diff --git a/seps/XXXX-MRTR.md b/seps/XXXX-MRTR.md index f64e7bd54..fae9aa4ac 100644 --- a/seps/XXXX-MRTR.md +++ b/seps/XXXX-MRTR.md @@ -160,15 +160,14 @@ responses, the map values are the responses to those requests. Here's how that would look in the typescript MCP schema: ```typescript -export interface InputRequests { [key: string]: ServerRequest; } +export type InputRequest = ElicitationCreateRequest | SamplingCreateRequest; -export interface InputResponses { [key: string]: ClientResult; } -``` +export interface InputRequests { [key: string]: InputRequest; } + +export type InputResponse = ElicitResult | CreateMessageResult; -TODO: The above schema definitions are not quite right, because -ServerRequest and ServerResult include the JSON-RPC request id field, -which is not necessary here. Figure out what schema refactoring is -needed to get the types without that field. +export interface InputResponses { [key: string]: InputResponse; } +``` The keys are assigned by the server when issuing the requests. The client will send the response for each request using the corresponding key. @@ -226,26 +225,22 @@ The client would then send the responses in the following form: ```json5 "inputResponses": { - // Elicitation response. + // Elicitation response (ElicitResult). "github_login": { - "result": { - "action": "accept", - "content": { - "name": "octocat" - } + "action": "accept", + "content": { + "name": "octocat" } }, - // Sampling response. + // Sampling response (CreateMessageResult). "capital_of_france": { - "result": { - "role": "assistant", - "content": { - "type": "text", - "text": "The capital of France is Paris." - }, - "model": "claude-3-sonnet-20240307", - "stopReason": "endTurn" - } + "role": "assistant", + "content": { + "type": "text", + "text": "The capital of France is Paris." + }, + "model": "claude-3-sonnet-20240307", + "stopReason": "endTurn" } } ``` @@ -297,15 +292,18 @@ export interface IncompleteResult extends Result { requestState?: string; } -// New request parameter type that includes fields in a retried request. -// These parameters may be included in any client-initiated request. -export interface RetryAugmentedRequestParams extends RequestParams { - // New field to carry the responses for the server's requests from the - // JSONRPCIncompleteResultResponse message. For each key in the - // response's inputRequests field, the same key must appear here - // with the associated response. +// New fields added directly to RequestParams so that any client-initiated +// request can carry input responses and request state when retrying after +// an IncompleteResult. +export interface RequestParams { + _meta?: RequestMetaObject; + // Responses to server-initiated input requests from a previous + // IncompleteResult. For each key in the response's inputRequests + // field, the same key must appear here with the associated response. inputResponses?: InputResponses; // Request state passed back to the server from the client. + // The client must treat this as an opaque blob; it must not + // interpret it in any way. requestState?: string; } ``` @@ -383,11 +381,9 @@ Note: This is a contrived example, just to illustrate the flow. } "inputResponses": { "github_login": { - "result": { - "action": "accept", - "content": { - "name": "octocat" - } + "action": "accept", + "content": { + "name": "octocat" } } }, @@ -500,10 +496,8 @@ server-side storage. }, "inputResponses": { "resolution": { - "result": { - "action": "accept", - "content": { "resolution": "Duplicate" } - } + "action": "accept", + "content": { "resolution": "Duplicate" } } } } @@ -563,10 +557,8 @@ server-side storage. }, "inputResponses": { "duplicate_of": { - "result": { - "action": "accept", - "content": { "duplicateOfId": 4301 } - } + "action": "accept", + "content": { "duplicateOfId": 4301 } } }, "requestState": "eyJyZXNvbHV0aW9uIjoiRHVwbGljYXRlIn0..." @@ -862,11 +854,9 @@ The below example walks through the entire Task Message flow for a Echo Tool whi "params": { "inputResponses":{ "echo_input":{ - "result":{ - "action": "accept", - "content":{ - "input": "Hello World!" - } + "action": "accept", + "content":{ + "input": "Hello World!" } } }, @@ -1015,11 +1005,9 @@ In the ephemeral workflow, this would look like the following: } "inputResponses": { "not_requested_info": { - "result": { - "action": "accept", - "content": { - "not_requested_param_name": "Information the server did not request" - } + "action": "accept", + "content": { + "not_requested_param_name": "Information the server did not request" } } } @@ -1068,11 +1056,9 @@ Step 7 from above: Client Request The client mistakenly or maliciously se "params": { "inputResponses":{ "echo_input":{ - "result":{ - "action": "accept", - "content":{ - "not_requested_parameter": "Information the server did not request." - } + "action": "accept", + "content":{ + "not_requested_parameter": "Information the server did not request." } } }, From 053d9cba09a31e30078bc32a171b3420a938b9c7 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 23 Feb 2026 15:09:36 -0800 Subject: [PATCH 12/27] remove URLElicitationRequiredError --- .../authorization-required.json | 18 ------ schema/draft/schema.json | 56 +------------------ schema/draft/schema.ts | 25 --------- 3 files changed, 2 insertions(+), 97 deletions(-) delete mode 100644 schema/draft/examples/URLElicitationRequiredError/authorization-required.json diff --git a/schema/draft/examples/URLElicitationRequiredError/authorization-required.json b/schema/draft/examples/URLElicitationRequiredError/authorization-required.json deleted file mode 100644 index 83b73dabc..000000000 --- a/schema/draft/examples/URLElicitationRequiredError/authorization-required.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": 2, - "error": { - "code": -32042, - "message": "This request requires more information.", - "data": { - "elicitations": [ - { - "mode": "url", - "elicitationId": "550e8400-e29b-41d4-a716-446655440000", - "url": "https://mcp.example.com/connect?elicitationId=550e8400-e29b-41d4-a716-446655440000", - "message": "Authorization is required to access your Example Co files." - } - ] - } - } -} diff --git a/schema/draft/schema.json b/schema/draft/schema.json index bc2785cea..f6a102648 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -947,10 +947,10 @@ "ElicitRequestParams": { "anyOf": [ { - "$ref": "#/$defs/ElicitRequestURLParams" + "$ref": "#/$defs/ElicitRequestFormParams" }, { - "$ref": "#/$defs/ElicitRequestFormParams" + "$ref": "#/$defs/ElicitRequestURLParams" } ], "description": "The parameters for a request to elicit additional information from the user via the client." @@ -4441,58 +4441,6 @@ ], "type": "object" }, - "URLElicitationRequiredError": { - "description": "An error response that indicates that the server requires the client to provide additional information via an elicitation request.", - "properties": { - "error": { - "allOf": [ - { - "$ref": "#/$defs/Error" - }, - { - "properties": { - "code": { - "const": -32042, - "type": "integer" - }, - "data": { - "additionalProperties": {}, - "properties": { - "elicitations": { - "items": { - "$ref": "#/$defs/ElicitRequestURLParams" - }, - "type": "array" - } - }, - "required": [ - "elicitations" - ], - "type": "object" - } - }, - "required": [ - "code", - "data" - ], - "type": "object" - } - ] - }, - "id": { - "$ref": "#/$defs/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - } - }, - "required": [ - "error", - "jsonrpc" - ], - "type": "object" - }, "UnsubscribeRequest": { "description": "Sent from the client to request cancellation of {@link ResourceUpdatedNotificationresources/updated} notifications from the server. This should follow a previous {@link SubscribeRequestresources/subscribe} request.", "properties": { diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 9cf0c6618..c04f942e0 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -321,31 +321,6 @@ export interface InternalError extends Error { code: typeof INTERNAL_ERROR; } -// Implementation-specific JSON-RPC error codes [-32000, -32099] -/** @internal */ -export const URL_ELICITATION_REQUIRED = -32042; - -/** - * An error response that indicates that the server requires the client to provide additional information via an elicitation request. - * - * @example Authorization required - * {@includeCode ./examples/URLElicitationRequiredError/authorization-required.json} - * - * @internal - */ -export interface URLElicitationRequiredError extends Omit< - JSONRPCErrorResponse, - "error" -> { - error: Error & { - code: typeof URL_ELICITATION_REQUIRED; - data: { - elicitations: ElicitRequestURLParams[]; - [key: string]: unknown; - }; - }; -} - /* Empty result */ /** * A result that indicates success but carries no data. From 9fd8ddec89ddb5e5cc70e6e3236416c3e0ffe857 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 23 Feb 2026 15:17:57 -0800 Subject: [PATCH 13/27] updating SEP examples to adhere strictly to schema --- seps/XXXX-MRTR.md | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/seps/XXXX-MRTR.md b/seps/XXXX-MRTR.md index fae9aa4ac..fe6ea51db 100644 --- a/seps/XXXX-MRTR.md +++ b/seps/XXXX-MRTR.md @@ -378,7 +378,7 @@ Note: This is a contrived example, just to illustrate the flow. "name": "get_weather", "arguments": { "location": "New York" - } + }, "inputResponses": { "github_login": { "action": "accept", @@ -727,14 +727,14 @@ The workflow for `Tasks` is as follows: 1. Server sets Task Status to `input_required`. The server can pause processing the request at this point. 2. Client retrieves the Task Status by calling `tasks/get` and sees that more information is needed. -3. Client calls `task/result` +3. Client calls `tasks/result` 4. Server returns the `InputRequests` object. 5. Client calls `tasks/input_response` request that includes an `InputResponses` object along with `Task` metadata field. 6. Server resumes processing sets TaskStatus back to `Working`. Since `Tasks` are likely longer running, have state associated with them, and are likely more costly to compute, the request for more information does not end the originally requested operation (e.g., the tool call). Instead, the server can resume processing once the necessary information is provided. -To align with MRTR semantics, the server will respond to the `task/result` request with a `InputRequests` object. Both of these will have the same JsonRPC `id`. When the client responds with a `InputResponses` object this is a new client request wit a new JSONRPC `id` and therefore needs a new method name. We propose `tasks/input_response`. +To align with MRTR semantics, the server will respond to the `tasks/result` request with a `InputRequests` object. Both of these will have the same JsonRPC `id`. When the client responds with a `InputResponses` object this is a new client request wit a new JSONRPC `id` and therefore needs a new method name. We propose `tasks/input_response`. The above workflow and below example do not leverage any of the optional Task Status Notifications although this SEP does not preclude their use. @@ -764,9 +764,10 @@ The below example walks through the entire Task Message flow for a Echo Tool whi "result":{ "task":{ "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5", - "status": "Working", + "status": "working", "statusMessage": "Task has been created for echo tool invocation.", "createdAt": "2026-01-27T03:32:48.3148180Z", + "lastUpdatedAt": "2026-01-27T03:32:48.3148180Z", "ttl": 60000, "pollInterval": 100 } @@ -796,9 +797,10 @@ The below example walks through the entire Task Message flow for a Echo Tool whi "status": "input_required", "statusMessage": "Input Required to Proceed call tasks/result", "createdAt": "2026-01-27T03:38:07.7534643Z", + "lastUpdatedAt": "2026-01-27T03:38:07.7534643Z", "ttl": 60000, "pollInterval": 100 - }, + } } ``` @@ -896,19 +898,20 @@ Client Request } } ``` -10. Server Response with Task status `Completed` +10. Server Response with Task status `completed` ```json { "id": 5, "jsonrpc": "2.0", "result":{ "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5", - "status": "Completed", - "statusMessage": "Task has been completed successfully, call get/result", + "status": "completed", + "statusMessage": "Task has been completed successfully, call tasks/result", "createdAt": "2026-01-27T03:38:07.7534643Z", + "lastUpdatedAt": "2026-01-27T03:38:08.1234567Z", "ttl": 60000, "pollInterval": 100 - }, + } } ``` @@ -921,7 +924,7 @@ Client Message "method": "tasks/result", "params":{ "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" - }, + } } ``` 12. Server Response with the final result of the `Task` @@ -940,7 +943,7 @@ Client Message "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" } } - }, + } } ``` @@ -953,7 +956,7 @@ Client Message `tasks/result` response. 2. **Client Behavior:** - - When `tasks/status` shows state `input_required`, clients MUST call + - When `tasks/get` shows state `input_required`, clients MUST call `tasks/result` to get the input requests, construct the results of those requests, and then call `tasks/input_response` with the input responses, unless the client is going to cancel the task. @@ -1002,7 +1005,7 @@ In the ephemeral workflow, this would look like the following: "name": "get_weather", "arguments": { "location": "New York" - } + }, "inputResponses": { "not_requested_info": { "action": "accept", @@ -1071,7 +1074,7 @@ Step 7 from above: Client Request The client mistakenly or maliciously se } ``` -Step 8 from above. Server Response Server acknowledges the receipt of the response by sending a `JSONRPCResultResponse`. However, since the response is missing required information, the server does not proceed with processing the taks and leaves the Task status as `input_required`. The next time the client calls `task/result`, the server responds with a new `inputRequest` requesting the necessary information again. +Step 8 from above. Server Response Server acknowledges the receipt of the response by sending a `JSONRPCResultResponse`. However, since the response is missing required information, the server does not proceed with processing the taks and leaves the Task status as `input_required`. The next time the client calls `tasks/result`, the server responds with a new `inputRequest` requesting the necessary information again. ```json { "id": 4, From c741ca4eccd8a125d8891fe7f9741f398d067a7b Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Mon, 23 Feb 2026 16:28:39 -0800 Subject: [PATCH 14/27] WIP on updating Spec to be more readable, added new spec V2 to test out content ordering changes --- seps/XXXX-MRTR-V2.md | 958 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 958 insertions(+) create mode 100644 seps/XXXX-MRTR-V2.md diff --git a/seps/XXXX-MRTR-V2.md b/seps/XXXX-MRTR-V2.md new file mode 100644 index 000000000..1914dedfa --- /dev/null +++ b/seps/XXXX-MRTR-V2.md @@ -0,0 +1,958 @@ +# SEP-XXXX: Multi Round-Trip Requests (MRTR) + +- **Status**: Draft +- **Type**: Standards Track +- **Created**: 2026-02-03 +- **Author(s)**: Mark D. Roth (@markdroth), Caitie McCaffrey (@CaitieM20), + Gabriel Zimmerman (@gjz22) +- **Sponsor**: Caitie McCaffrey (@CaitieM20) +- **PR**: https://github.com/modelcontextprotocol/specification/pull/{NUMBER} + +## Abstract +1. We want the protocol to be stateless. This change is a step towards that goal. +2. SEP-2260 clarified that server requests must be associated with a client request. +3. This change provides a more robust mechanism for that association, without relying on protocol sessions or persistent connections to map server-initiated requests back to the original client request. +4. This change provides a mechanism for MCP Servers that cannot support persistent connections via an SSE stream (e.g., due to infrastructure limitations) to still use server-initiated requests like sampling and elicitation. + +The key intuition here is that server initiated requests are a special kind of response to a client request — one that doesn't provide a final result, but instead provides instructions for how the client should retry with additional information. Another mental model is to view these as a recoverable error. By modeling them this way, we can eliminate the need for any server-side state or session affinity to link the original request with subsequent interactions. + +To support this new paradigm this SEP introudces the following: + +1. **`IncompleteResult`**: A new result type returned via + `JSONRPCIncompleteResultResponse` that signals additional input is needed, + carrying `inputRequests` and/or opaque `requestState`. +2. **`RequestParams` augmentation**: `inputResponses` and `requestState` + fields added directly to `RequestParams`, allowing any client-initiated + request to carry retry context. +3. **Removal of standalone `CreateMessageRequest` / `ElicitRequest`**: These + server-to-client request types are replaced by `SamplingCreateRequest` and + `ElicitationCreateRequest` embedded within `InputRequests`. + +4. **New data structures**: `InputRequests` / `InputResponses` maps for + bundling server-initiated requests and client responses, using typed + `ElicitationCreateRequest | SamplingCreateRequest` and + `ElicitResult | CreateMessageResult` values respectively. +5. **Two workflows**: An *ephemeral* workflow (stateless retry loop) and a + *persistent* workflow (leveraging Tasks with `tasks/input_response`). +6. **Removal of `URLElicitationRequiredError`**: Replaced by the + `IncompleteResult` mechanism. + +7. **`GetTaskPayloadResultResponse` union**: Can now return either a completed + result or a `JSONRPCIncompleteResultResponse` for tasks needing input. + + +We start with the observation that there are two types of messaging patterns that MCP supports today: +1. **Ephemeral**: No state is accumulated on the server side. These requests are typically short running and inexpensive to execute. + - If server needs more info to process the request, it can start from + scratch when it gets that additional info. + - Examples: weather app, accessing email +2. **Persistent**: State is accumulated on the server side. These requests are typically long-running and/or expensive to execute. `Tasks` was introduced to handle this pattern. + - Server may generate a large amount of state before requesting more + info from the client, and it may need to pick up that state to + continue processing after it receives the info from the client. + - Server may need to continue processing in the background while + waiting for more info from the client, in which case server-side + state is needed to track that ongoing processing. + - Examples: accessing an agent, spinning up a VM and needing user + interaction to manipulate the VM + +Today many MCP requests are ephemeral, particularly Tools. We need to make it cheap and easy for MCP Servers to implement this pattern without requiring the additional complexity of session state, or server side state. + +This SEP introduces Multi Round-Trip Requests (MRTR), a mechanism for +handling server-initiated requests (e.g., elicitation, sampling) within +client-initiated requests (e.g., tool calls) without requiring shared +storage or stateful load balancing. The key changes are: + +## Motivation + +Server-initiated requests during client-initiated operations (e.g., an +elicitation during a tool call) currently require either a persistent +storage layer shared across server instances or stateful load balancing. +Both approaches are expensive, operationally complex, and fragile — yet +the vast majority of MCP tools are ephemeral and stateless. + +Additionally, the current approach depends on SSE streams for delivering +server-initiated requests, which causes problems in environments that +cannot support long-lived connections. + +MRTR eliminates these dependencies by ensuring each HTTP request is +self-contained: servers can process any individual request using only the +information present in that request, with no inter-request state required +on the server side. + +## Specification + +### Data Structures +This SEP introduces a new `JSONRPCResponse` type `JSONRPCIncompleteResultResponse`. When the server determines that more information is needed to process a client request, the server returns this `JSONRPCResponse`. + +This new response type includes two new fields: `inputRequests` and `requestState`. +- `inputRequests` is a map of client actions (elicitation, sampling) the server needs to complete the request, keyed by client-chosen identifiers. +- `requestState` is an opaque string that the server can use to encode any information it needs the client to return on the next request. + +Example `JSONRPCIncompleteResultResponse` with an elicitation input request and request state: + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "inputRequests": { + "github_login": { + "method": "elicitation/create", + "params": { + "message": "Please provide your GitHub username", + "requestedSchema": { + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "required": ["name"] + } + } + } + }, + "requestState": "eyJsb2dpbiI6bnVsbH0..." + } +} +``` + + + +The client provides the requested information on subsequent requests via `inputResponses` & `requestState` fields. + +```json +{ + "jsonrpc": "2.0", + "id": 2, + "inputResponses": { + "github_login": { + "action": "accept", + "content": { + "name": "octocat" + } + } + }, + "requestState": "eyJsb2dpbiI6bnVsbH0..." + // additional request info (e.g., method, params) would go here +} + +``` + +In the schema these are added directly to +`RequestParams`, making them available on any client-initiated request: + +```typescript +export interface RequestParams { + _meta?: RequestMetaObject; + inputResponses?: InputResponses; + requestState?: string; +} +``` + + +#### Use Cases for `requestState` +TODO: add some rationale for requestState because we got rid of JSONRPC id linking. + +- **Rolling upgrades**: When a new server version needs different input + than the old version, `requestState` preserves already-gathered answers + across rounds without server-side storage. +- **Load shedding**: A server can offload in-progress computation by + encoding accumulated state in `requestState` (with no `inputRequests`), + allowing any other instance to resume processing. + + +### Ephemeral Workflow + +This section describes the MRTR workflow for ephemeral interactions that do not require server-side state. The workflow proceeds in rounds: + +1. Client sends request (e.g., `tools/call`). +2. Server returns `JSONRPCIncompleteResultResponse` with `inputRequests` + and/or `requestState`. This terminates the original request. +3. Client fulfills the input requests, then sends a **new, independent** + request (with a new JSON-RPC `id`) including `inputResponses` and + echoing back `requestState` in the params. +4. Server returns a complete result, or another `IncompleteResult` for + additional rounds. + +**Server Behavior:** +- Servers MAY respond to any client-initiated request with a + `JSONRPCIncompleteResultResponse`, sent as a standalone response or as + the final message on an SSE stream. +- The response MAY include `inputRequests` and/or `requestState`. +- Servers SHOULD encrypt `requestState` (e.g., AES-GCM, signed JWT) to ensure confidentiality, integrity, and user-binding. +- If `requestState` is present on a request, servers MUST validate it on receipt (the client is an untrusted intermediary). + +**Client Behavior:** +- If `inputRequests` is present, clients MUST fulfill them before retrying. If absent, clients MAY retry immediately. +- If `requestState` is present, clients MUST echo it back exactly. + Clients MUST NOT inspect, parse, or modify `requestState`. + +For complete examples see +- [Ephemeral Workflow: Basic Flow](#ephemeral-workflow-basic-flow) +- [Ephemeral Workflow: Multi-Round with requestState](#ephemeral-workflow-multi-round-with-requeststate-azure-devops) + +## Persistent Workflow + +This section describes the MRTR workflows for interactions that require server-side state, using the Tasks API. The key difference from the ephemeral workflow is that instead of returning `JSONRPCIncompleteResultResponse` directly, the server sets the Task status to `input_required` and includes `inputRequests` in the `tasks/result` response. The client then fulfills the input requests via a new `tasks/input_response` method, allowing the server to resume processing and update the Task status accordingly. + +The `JSONRPCIncompleteResultResponse` returned by `tasks/result` in has the same structure as in the ephemeral workflow. However, since `tasks` already have server side state, are likely long running, and more expensive to compute a mechanism to provide the input responses without retrying the initial request is provided via the new method `tasks/input_response`. + +The workflow proceeds as follows, with steps 1-3 ahdering to how `tasks` is implemented today, and steps 4-6 illustrating the new MRTR mechanism for eliciting & providing additional input: + +1. Server sets Task status to `input_required` and MAY pause processing. +2. Client polls `tasks/get`, sees `input_required` status. +3. Client calls `tasks/result` to discover what input is needed. +4. Server returns `JSONRPCIncompleteResultResponse` with `inputRequests`. +5. Client calls `tasks/input_response` with `InputResponses` and task metadata. +6. Server sets Task status to `working`, and continues with request processing. + +The client delivers input responses via the `tasks/input_response` method: + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "tasks/input_response", + "params": { + "inputResponses": { + "echo_input": { + "action": "accept", + "content": { + "input": "Hello World!" + } + } + }, + "_meta": { + "io.modelcontextprotocol/related-task": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} +``` + +For a completeexample see [Persistent Workflow: Echo Tool with Elicitation](#persistent-workflow-echo-tool-with-elicitation) for a complete example. + +**Server Behavior:** +- If Servers set task status to `input_required` they MUST respond to subsequent `tasks/result` calls with `JSONRPCIncompleteResultResponse`. +- If Servers do not receive the requested information before the ttl expires, they SHOULD set the Task status to `failed` with an appropriate error message. + +**Client Behavior:** +- When `tasks/get` shows `input_required`, clients MUST call + `tasks/result` to get input requests. +- Clients MUST fulfill `input_requests` and then call `tasks/input_response` with the responses and task metadata to allow the server to resume processing without retrying the original request. +- If a client does not wish to fulfill the input requests or cannot it SHOULD call `tasks/cancel` to cancel the Task and free server resources. + +### Workflow Transitions + +A request may start with the ephemeral workflow and switch to the persistent +workflow by creating a task once it has the information needed to begin +long-running processing. The reverse is not possible — once a task is +created, all subsequent interaction must use the Tasks API. + +### Error Handling + +The server MUST validate that `inputResponses` is well-formed and +parseable. Protocol errors (malformed JSON, invalid schema) return a +`JSONRPCErrorResponse`. + +If the data is well-formed but unexpected or incomplete, the server MUST +treat values as optional: ignore unexpected keys, and if required +information is missing, respond with a new `IncompleteResult` requesting +the needed information again. This approach leverages the existing retry +mechanics of the ephemeral workflow and the Task state machine to ensure +clients can always recover. + +## Rationale + +- **Map vs. single object for input requests**: A map structurally + guarantees unique keys, avoiding the need for explicit conflict checks + in SDKs and applications. +- **Bidirectional streams rejected**: Would have required HTTP/2 or + HTTP/3, would not have solved long-lived connection problems, and would + not have addressed fault tolerance. +- **Separate `tasks/get` and `tasks/result`**: Keeps task status polling + at consistent latency, independent of the actual task state. The extra + round-trip can be optimized in the future if needed. +- **Direct values in `InputResponses` (no `{ "result": ... }` wrapper)**: + `InputRequests` maps keys directly to `InputRequest` objects without a + wrapper, so `InputResponses` mirrors that symmetry. The wrapper would + add nesting with no additional information. + +## Backward Compatibility + +Existing tools that use the inline async pattern with SSE streams: + +```python +def my_tool(): + do_mutation1() + await elicit_more_info() + do_mutation2() +``` + +Should be rewritten to use the MRTR pattern: + +```python +def my_tool(request): + github_login = request.inputResponses().get('github_login', None) + if github_login is None: + return IncompleteResponse({'github_login': elicitation_request}) + result = GetResult(github_login) + return Result(result) +``` + +SDKs should provide a backward compatibility layer to support existing +tool implementations during the transition. + +## Security Implications + +Because `requestState` passes through the client (an untrusted +intermediary), servers MUST: + +- Validate all state received from the client. +- Encrypt `requestState` (e.g., AES-GCM, signed JWT) if it contains + sensitive data, to ensure confidentiality and integrity. +- Cryptographically bind `requestState` to the authenticated user if it + contains user-specific data, to prevent replay/hijacking attacks. +- Treat plaintext state as untrusted input and validate it the same way + as any client-supplied data. + +## Reference Implementation + +- Schema types are defined in [`schema/draft/schema.ts`](../schema/draft/schema.ts) + under the "Multi Round-Trip" category. +- Example JSON files for each type are in + [`schema/draft/examples/`](../schema/draft/examples/) (e.g., + `InputRequests/`, `InputResponses/`, `IncompleteResult/`, + `TaskInputResponseRequest/`, `GetTaskPayloadResultResponse/`). + +--- + +## Additional Examples + +### Ephemeral Workflow: Basic Flow + +#### Step 1 — Client sends initial tool call + +```json +{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "get_weather", + "arguments": { + "location": "New York" + } + } +} +``` + +#### Step 2 — Server returns IncompleteResult with elicitation request + +```json +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "inputRequests": { + "github_login": { + "method": "elicitation/create", + "params": { + "message": "Please provide your GitHub username", + "requestedSchema": { + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "required": ["name"] + } + } + } + }, + "requestState": "foo" + } +} +``` + +#### Step 3 — Client retries with inputResponses and requestState + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "get_weather", + "arguments": { + "location": "New York" + }, + "inputResponses": { + "github_login": { + "action": "accept", + "content": { + "name": "octocat" + } + } + }, + "requestState": "foo" + } +} +``` + +#### Step 4 — Server returns final result + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "content": [ + { + "type": "text", + "text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy" + } + ], + "isError": false + } +} +``` + +### Ephemeral Workflow: Multi-Round with requestState (Azure DevOps) + +This example demonstrates iterative elicitation driven by Azure DevOps +custom rules. An `update_work_item` tool resolves Bug #4522. Rule 1 +requires a "Resolution" field; Rule 2 (triggered when Resolution = +"Duplicate") requires a "Duplicate Of" link. + +#### Round 1 — Client invokes tool + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "update_work_item", + "arguments": { + "workItemId": 4522, + "fields": { "System.State": "Resolved" } + } + } +} +``` + +#### Round 1 — Server elicits Resolution + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "inputRequests": { + "resolution": { + "method": "elicitation/create", + "params": { + "message": "Resolving Bug #4522 requires a resolution. How was this bug resolved?", + "requestedSchema": { + "type": "object", + "properties": { + "resolution": { + "type": "string", + "enum": ["Fixed", "Won't Fix", "Duplicate", "By Design"], + "description": "Resolution type for this bug" + } + }, + "required": ["resolution"] + } + } + } + } + } +} +``` + +#### Round 1 — Client retries with resolution + +```json +{ + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "update_work_item", + "arguments": { + "workItemId": 4522, + "fields": { "System.State": "Resolved" } + }, + "inputResponses": { + "resolution": { + "action": "accept", + "content": { "resolution": "Duplicate" } + } + } + } +} +``` + +#### Round 2 — Server elicits Duplicate Of, encodes resolution in requestState + +```json +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "inputRequests": { + "duplicate_of": { + "method": "elicitation/create", + "params": { + "message": "Since this is a duplicate, which work item is the original?", + "requestedSchema": { + "type": "object", + "properties": { + "duplicateOfId": { + "type": "number", + "description": "Work item ID of the original bug" + } + }, + "required": ["duplicateOfId"] + } + } + } + }, + "requestState": "eyJyZXNvbHV0aW9uIjoiRHVwbGljYXRlIn0..." + } +} +``` + +#### Round 2 — Client retries with duplicate ID and requestState + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "update_work_item", + "arguments": { + "workItemId": 4522, + "fields": { "System.State": "Resolved" } + }, + "inputResponses": { + "duplicate_of": { + "action": "accept", + "content": { "duplicateOfId": 4301 } + } + }, + "requestState": "eyJyZXNvbHV0aW9uIjoiRHVwbGljYXRlIn0..." + } +} +``` + +#### Final — Server completes update + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "content": [ + { + "type": "text", + "text": "Bug #4522 resolved as Duplicate of Bug #4301. State set to Resolved and duplicate link created." + } + ], + "isError": false + } +} +``` + +### Persistent Workflow: Echo Tool with Elicitation + +Full task lifecycle for an Echo tool that requests input via elicitation. + +#### Step 1 — Client invokes tool with task metadata + +```json +{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "echo", + "task": { + "ttl": 60000 + } + } +} +``` + +#### Step 2 — Server creates task + +```json +{ + "id": 1, + "jsonrpc": "2.0", + "result": { + "task": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5", + "status": "working", + "statusMessage": "Task has been created for echo tool invocation.", + "createdAt": "2026-01-27T03:32:48.3148180Z", + "lastUpdatedAt": "2026-01-27T03:32:48.3148180Z", + "ttl": 60000, + "pollInterval": 100 + } + } +} +``` + +#### Step 3 — Client polls task status + +```json +{ + "jsonrpc": "2.0", + "id": 2, + "method": "tasks/get", + "params": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } +} +``` + +#### Step 4 — Server responds with `input_required` + +```json +{ + "id": 2, + "jsonrpc": "2.0", + "result": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5", + "status": "input_required", + "statusMessage": "Input Required to Proceed call tasks/result", + "createdAt": "2026-01-27T03:38:07.7534643Z", + "lastUpdatedAt": "2026-01-27T03:38:07.7534643Z", + "ttl": 60000, + "pollInterval": 100 + } +} +``` + +#### Step 5 — Client calls `tasks/result` + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "tasks/result", + "params": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } +} +``` + +#### Step 6 — Server returns inputRequests + +```json +{ + "id": 3, + "jsonrpc": "2.0", + "result": { + "inputRequests": { + "echo_input": { + "method": "elicitation/create", + "params": { + "mode": "form", + "message": "Please provide the input string to echo back", + "requestedSchema": { + "type": "object", + "properties": { + "input": { "type": "string" } + }, + "required": ["input"] + } + } + } + }, + "_meta": { + "io.modelcontextprotocol/related-task": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} +``` + +#### Step 7 — Client sends `tasks/input_response` + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "tasks/input_response", + "params": { + "inputResponses": { + "echo_input": { + "action": "accept", + "content": { + "input": "Hello World!" + } + } + }, + "_meta": { + "io.modelcontextprotocol/related-task": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} +``` + +#### Step 8 — Server acknowledges + +```json +{ + "id": 4, + "jsonrpc": "2.0", + "result": { + "_meta": { + "io.modelcontextprotocol/related-task": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} +``` + +#### Step 9 — Client polls, task is completed + +```json +{ + "jsonrpc": "2.0", + "id": 5, + "method": "tasks/get", + "params": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } +} +``` + +```json +{ + "id": 5, + "jsonrpc": "2.0", + "result": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5", + "status": "completed", + "statusMessage": "Task has been completed successfully, call tasks/result", + "createdAt": "2026-01-27T03:38:07.7534643Z", + "lastUpdatedAt": "2026-01-27T03:38:08.1234567Z", + "ttl": 60000, + "pollInterval": 100 + } +} +``` + +#### Step 10 — Client retrieves final result + +```json +{ + "id": 6, + "jsonrpc": "2.0", + "method": "tasks/result", + "params": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } +} +``` + +```json +{ + "id": 6, + "jsonrpc": "2.0", + "result": { + "isError": false, + "content": [ + { + "type": "text", + "text": "Echo: Hello World!" + } + ], + "_meta": { + "io.modelcontextprotocol/related-task": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} +``` + +### Error Handling: Missing or Unexpected inputResponses + +#### Ephemeral — Client sends unexpected data + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "get_weather", + "arguments": { + "location": "New York" + }, + "inputResponses": { + "not_requested_info": { + "action": "accept", + "content": { + "not_requested_param_name": "Information the server did not request" + } + } + } + } +} +``` + +Server ignores unexpected data and re-issues the original input request: + +```json +{ + "jsonrpc": "2.0", + "id": 3, + "result": { + "inputRequests": { + "github_login": { + "method": "elicitation/create", + "params": { + "message": "Please provide your GitHub username", + "requestedSchema": { + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "required": ["name"] + } + } + } + } + } +} +``` + +#### Persistent — Client sends unexpected data + +```json +{ + "jsonrpc": "2.0", + "id": 4, + "method": "tasks/input_response", + "params": { + "inputResponses": { + "echo_input": { + "action": "accept", + "content": { + "not_requested_parameter": "Information the server did not request." + } + } + }, + "_meta": { + "io.modelcontextprotocol/related-task": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} +``` + +Server acknowledges but leaves Task status as `input_required`. The next +`tasks/result` call returns a new `inputRequest`: + +```json +{ + "id": 4, + "jsonrpc": "2.0", + "result": { + "_meta": { + "io.modelcontextprotocol/related-task": { + "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" + } + } + } +} +``` + +### InputRequests with Multiple Request Types + +A server can request both elicitation and sampling in a single response: + +```json5 +"inputRequests": { + // Elicitation request. + "github_login": { + "method": "elicitation/create", + "params": { + "message": "Please provide your GitHub username", + "requestedSchema": { + "type": "object", + "properties": { + "name": { "type": "string" } + }, + "required": ["name"] + } + } + }, + // Sampling request. + "capital_of_france": { + "method": "sampling/createMessage", + "params": { + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "What is the capital of France?" + } + } + ], + "modelPreferences": { + "hints": [{ "name": "claude-3-sonnet" }], + "intelligencePriority": 0.8, + "speedPriority": 0.5 + }, + "systemPrompt": "You are a helpful assistant.", + "maxTokens": 100 + } + } +} +``` + +The paired responses: + +```json5 +"inputResponses": { + // Elicitation response (ElicitResult). + "github_login": { + "action": "accept", + "content": { + "name": "octocat" + } + }, + // Sampling response (CreateMessageResult). + "capital_of_france": { + "role": "assistant", + "content": { + "type": "text", + "text": "The capital of France is Paris." + }, + "model": "claude-3-sonnet-20240307", + "stopReason": "endTurn" + } +} +``` + +--- + +### Acknowledgments + +Thanks to Luca Chang (@LucaButBoring) for his valuable input on how to +integrate input requests into Tasks. From 2d37d9de6d0828dbc11b922e6733480ff9f00952 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Thu, 26 Feb 2026 23:16:40 +0000 Subject: [PATCH 15/27] schema changes for ephemeral workflow --- schema/draft/schema.ts | 149 +++++++++++++++++++++++++---------------- 1 file changed, 90 insertions(+), 59 deletions(-) diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index f89665acc..dd4c6c384 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -69,7 +69,7 @@ export type Cursor = string; * * @internal */ -export interface TaskAugmentedRequestParams extends RequestParams { +export interface TaskAugmentedRequestParams extends RetryAugmentedRequestParams { /** * If specified, the caller is requesting task-augmented execution for this request. * The request will return a {@link CreateTaskResult} immediately, and the actual result can be @@ -329,6 +329,79 @@ export interface URLElicitationRequiredError extends Omit< */ export type EmptyResult = Result; +/** @internal */ +export type InputRequest = + | CreateMessageRequest + | ListRootsRequest + | ElicitRequest; + +/** @internal */ +export type InputResponse = + | CreateMessageResult + | ListRootsResult + | ElicitResult; + +/** + * A map of server-initiated requests that the client must fulfill. + * Keys are server-assigned identifiers; values are the request objects. + * + * @example Elicitation and sampling input requests + * {@includeCode ./examples/InputRequests/elicitation-and-sampling-input-requests.json} + * + * @category Multi Round-Trip + */ +export interface InputRequests { + [key: string]: InputRequest; +} + +/** + * A map of client responses to server-initiated requests. + * Keys correspond to the keys in the {@link InputRequests} map; + * values are the client's result for each request. + * + * @example Elicitation and sampling input responses + * {@includeCode ./examples/InputResponses/elicitation-and-sampling-input-responses.json} + * + * @category Multi Round-Trip + */ +export interface InputResponses { + [key: string]: InputResponse; +} + +/** + * Incomplete response. May be sent in response to any client-initiated + * request. + * At least one of the the inputRequests and requestState fields must be + * present, since the presence of these fields allow the client to + * determine that the result is incomplete. + */ +export interface IncompleteResult extends Result { + /* Requests issued by the server that must be complete before the + * client can retry the original request. + */ + inputRequests?: InputRequests; + /* Request state to be passed back to the server when the client + * retries the original request. + * Note: The client must treat this as an opaque blob; it must not + * interpret it in any way. + */ + requestState?: string; +} + +/* New request parameter type that includes fields in a retried request. + * These parameters may be included in any client-initiated request. + */ +export interface RetryAugmentedRequestParams extends RequestParams { + /* New field to carry the responses for the server's requests from the + * IncompleteResult message. For each key in the response's inputRequests + * field, the same key must appear here with the associated response. + */ + inputResponses?: InputResponses; + /* Request state passed back to the server from the client. + */ + requestState?: string; +} + /* Cancellation */ /** * Parameters for a `notifications/cancelled` notification. @@ -934,7 +1007,7 @@ export interface ListResourcesResult extends PaginatedResult { * @category `resources/list` */ export interface ListResourcesResultResponse extends JSONRPCResultResponse { - result: ListResourcesResult; + result: ListResourcesResult | IncompleteResult; } /** @@ -970,7 +1043,7 @@ export interface ListResourceTemplatesResult extends PaginatedResult { * @category `resources/templates/list` */ export interface ListResourceTemplatesResultResponse extends JSONRPCResultResponse { - result: ListResourceTemplatesResult; + result: ListResourceTemplatesResult | IncompleteResult; } /** @@ -978,7 +1051,7 @@ export interface ListResourceTemplatesResultResponse extends JSONRPCResultRespon * * @internal */ -export interface ResourceRequestParams extends RequestParams { +export interface ResourceRequestParams extends RetryAugmentedRequestParams { /** * The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. * @@ -1029,7 +1102,7 @@ export interface ReadResourceResult extends Result { * @category `resources/read` */ export interface ReadResourceResultResponse extends JSONRPCResultResponse { - result: ReadResourceResult; + result: ReadResourceResult | IncompleteResult; } /** @@ -1302,7 +1375,7 @@ export interface ListPromptsResult extends PaginatedResult { * @category `prompts/list` */ export interface ListPromptsResultResponse extends JSONRPCResultResponse { - result: ListPromptsResult; + result: ListPromptsResult | IncompleteResult; } /** @@ -1313,7 +1386,7 @@ export interface ListPromptsResultResponse extends JSONRPCResultResponse { * * @category `prompts/get` */ -export interface GetPromptRequestParams extends RequestParams { +export interface GetPromptRequestParams extends RetryAugmentedRequestParams { /** * The name of the prompt or prompt template. */ @@ -1362,7 +1435,7 @@ export interface GetPromptResult extends Result { * @category `prompts/get` */ export interface GetPromptResultResponse extends JSONRPCResultResponse { - result: GetPromptResult; + result: GetPromptResult | IncompleteResult; } /** @@ -1503,7 +1576,7 @@ export interface ListToolsResult extends PaginatedResult { * @category `tools/list` */ export interface ListToolsResultResponse extends JSONRPCResultResponse { - result: ListToolsResult; + result: ListToolsResult | IncompleteResult; } /** @@ -1557,7 +1630,7 @@ export interface CallToolResult extends Result { * @category `tools/call` */ export interface CallToolResultResponse extends JSONRPCResultResponse { - result: CallToolResult; + result: CallToolResult | IncompleteResult; } /** @@ -2173,7 +2246,7 @@ export interface ToolChoice { * * @category `sampling/createMessage` */ -export interface CreateMessageRequest extends JSONRPCRequest { +export interface CreateMessageRequest { method: "sampling/createMessage"; params: CreateMessageRequestParams; } @@ -2194,7 +2267,7 @@ export interface CreateMessageRequest extends JSONRPCRequest { * * @category `sampling/createMessage` */ -export interface CreateMessageResult extends Result, SamplingMessage { +export interface CreateMessageResult extends SamplingMessage { /** * The name of the model that generated the message. */ @@ -2214,18 +2287,6 @@ export interface CreateMessageResult extends Result, SamplingMessage { stopReason?: "endTurn" | "stopSequence" | "maxTokens" | "toolUse" | string; } -/** - * A successful response from the client for a {@link CreateMessageRequest | sampling/createMessage} request. - * - * @example Sampling result response - * {@includeCode ./examples/CreateMessageResultResponse/sampling-result-response.json} - * - * @category `sampling/createMessage` - */ -export interface CreateMessageResultResponse extends JSONRPCResultResponse { - result: CreateMessageResult; -} - /** * Describes a message issued to or received from an LLM API. * @@ -2568,7 +2629,7 @@ export interface ModelHint { * @example Prompt argument completion with context * {@includeCode ./examples/CompleteRequestParams/prompt-argument-completion-with-context.json} */ -export interface CompleteRequestParams extends RequestParams { +export interface CompleteRequestParams extends RetryAugmentedRequestParams { ref: PromptReference | ResourceTemplateReference; /** * The argument's information @@ -2687,7 +2748,7 @@ export interface PromptReference extends BaseMetadata { * * @category `roots/list` */ -export interface ListRootsRequest extends JSONRPCRequest { +export interface ListRootsRequest { method: "roots/list"; params?: RequestParams; } @@ -2705,22 +2766,10 @@ export interface ListRootsRequest extends JSONRPCRequest { * * @category `roots/list` */ -export interface ListRootsResult extends Result { +export interface ListRootsResult { roots: Root[]; } -/** - * A successful response from the client for a {@link ListRootsRequest | roots/list} request. - * - * @example List roots result response - * {@includeCode ./examples/ListRootsResultResponse/list-roots-result-response.json} - * - * @category `roots/list` - */ -export interface ListRootsResultResponse extends JSONRPCResultResponse { - result: ListRootsResult; -} - /** * Represents a root directory or file that the server can operate on. * @@ -2849,7 +2898,7 @@ export type ElicitRequestParams = * * @category `elicitation/create` */ -export interface ElicitRequest extends JSONRPCRequest { +export interface ElicitRequest { method: "elicitation/create"; params: ElicitRequestParams; } @@ -3125,7 +3174,7 @@ export type EnumSchema = * * @category `elicitation/create` */ -export interface ElicitResult extends Result { +export interface ElicitResult { /** * The user action in response to the elicitation. * - `"accept"`: User submitted the form/confirmed the action @@ -3142,18 +3191,6 @@ export interface ElicitResult extends Result { content?: { [key: string]: string | number | boolean | string[] }; } -/** - * A successful response from the client for a {@link ElicitRequest | elicitation/create} request. - * - * @example Elicitation result response - * {@includeCode ./examples/ElicitResultResponse/elicitation-result-response.json} - * - * @category `elicitation/create` - */ -export interface ElicitResultResponse extends JSONRPCResultResponse { - result: ElicitResult; -} - /** * An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request. * @@ -3204,9 +3241,6 @@ export type ClientNotification = /** @internal */ export type ClientResult = | EmptyResult - | CreateMessageResult - | ListRootsResult - | ElicitResult | GetTaskResult | GetTaskPayloadResult | ListTasksResult @@ -3216,9 +3250,6 @@ export type ClientResult = /** @internal */ export type ServerRequest = | PingRequest - | CreateMessageRequest - | ListRootsRequest - | ElicitRequest | GetTaskRequest | GetTaskPayloadRequest | ListTasksRequest From 08cffb17180c070c8d0efc01d2bba1e1fbf6f782 Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Thu, 26 Feb 2026 23:28:06 +0000 Subject: [PATCH 16/27] schema changes for persistent workflow --- schema/draft/schema.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index dd4c6c384..548195fe4 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -1991,6 +1991,46 @@ export interface GetTaskPayloadResultResponse extends JSONRPCResultResponse { result: GetTaskPayloadResult; } +/** + * Parameters for a `tasks/input_response` request. + * Used to deliver client responses to server-initiated input requests + * for a task in the persistent multi round-trip workflow. + * + * @example Task input response params + * {@includeCode ./examples/TaskInputResponseRequestParams/task-input-response-params.json} + * + * @category `tasks/input_response` + */ +export interface TaskInputResponseRequestParams extends TaskAugmentedRequestParams { + /** + * The client's responses to the server's input requests. + */ + inputResponses: InputResponses; +} + +/** + * A request from the client to deliver input responses for a task + * that is in `input_required` status. + * + * @example Task input response request + * {@includeCode ./examples/TaskInputResponseRequest/task-input-response-request.json} + * + * @category `tasks/input_response` + */ +export interface TaskInputResponseRequest extends JSONRPCRequest { + method: "tasks/input_response"; + params: TaskInputResponseRequestParams; +} + +/** + * A successful response for a {@link TaskInputResponseRequest | tasks/input_response} request. + * + * @category `tasks/input_response` + */ +export interface TaskInputResponseResultResponse extends JSONRPCResultResponse { + result: Result; +} + /** * A request to cancel a task. * @@ -3228,6 +3268,7 @@ export type ClientRequest = | GetTaskRequest | GetTaskPayloadRequest | ListTasksRequest + | TaskInputResponseRequest | CancelTaskRequest; /** @internal */ From 3e6c4f807c9cb4e6da413668ab8ab1c07a32a24f Mon Sep 17 00:00:00 2001 From: "Mark D. Roth" Date: Fri, 27 Feb 2026 19:47:06 +0000 Subject: [PATCH 17/27] remove URLElicitationRequiredError --- schema/draft/schema.ts | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 548195fe4..3ed038420 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -296,31 +296,6 @@ export interface InternalError extends Error { code: typeof INTERNAL_ERROR; } -// Implementation-specific JSON-RPC error codes [-32000, -32099] -/** @internal */ -export const URL_ELICITATION_REQUIRED = -32042; - -/** - * An error response that indicates that the server requires the client to provide additional information via an elicitation request. - * - * @example Authorization required - * {@includeCode ./examples/URLElicitationRequiredError/authorization-required.json} - * - * @internal - */ -export interface URLElicitationRequiredError extends Omit< - JSONRPCErrorResponse, - "error" -> { - error: Error & { - code: typeof URL_ELICITATION_REQUIRED; - data: { - elicitations: ElicitRequestURLParams[]; - [key: string]: unknown; - }; - }; -} - /* Empty result */ /** * A result that indicates success but carries no data. From 0a778244a0a69839edabcbb024b7257ea98fabaa Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Fri, 27 Feb 2026 19:06:38 -0800 Subject: [PATCH 18/27] fix examples form parameter accidentally removed --- .../draft/examples/InputRequest/elicitation-input-request.json | 1 + .../InputRequests/elicitation-and-sampling-input-requests.json | 1 + 2 files changed, 2 insertions(+) diff --git a/schema/draft/examples/InputRequest/elicitation-input-request.json b/schema/draft/examples/InputRequest/elicitation-input-request.json index acf32ed0c..62cbb8df6 100644 --- a/schema/draft/examples/InputRequest/elicitation-input-request.json +++ b/schema/draft/examples/InputRequest/elicitation-input-request.json @@ -1,6 +1,7 @@ { "method": "elicitation/create", "params": { + "mode": "form", "message": "Please provide your GitHub username", "requestedSchema": { "type": "object", diff --git a/schema/draft/examples/InputRequests/elicitation-and-sampling-input-requests.json b/schema/draft/examples/InputRequests/elicitation-and-sampling-input-requests.json index fe14634a5..5d1ce974a 100644 --- a/schema/draft/examples/InputRequests/elicitation-and-sampling-input-requests.json +++ b/schema/draft/examples/InputRequests/elicitation-and-sampling-input-requests.json @@ -2,6 +2,7 @@ "github_login": { "method": "elicitation/create", "params": { + "mode": "form", "message": "Please provide your GitHub username", "requestedSchema": { "type": "object", From 65772eddb2a972af9f440ce7d46984407713a4fb Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Fri, 27 Feb 2026 19:09:21 -0800 Subject: [PATCH 19/27] removing experimental edits --- seps/XXXX-MRTR-V2.md | 958 ------------------------------------------- 1 file changed, 958 deletions(-) delete mode 100644 seps/XXXX-MRTR-V2.md diff --git a/seps/XXXX-MRTR-V2.md b/seps/XXXX-MRTR-V2.md deleted file mode 100644 index 1914dedfa..000000000 --- a/seps/XXXX-MRTR-V2.md +++ /dev/null @@ -1,958 +0,0 @@ -# SEP-XXXX: Multi Round-Trip Requests (MRTR) - -- **Status**: Draft -- **Type**: Standards Track -- **Created**: 2026-02-03 -- **Author(s)**: Mark D. Roth (@markdroth), Caitie McCaffrey (@CaitieM20), - Gabriel Zimmerman (@gjz22) -- **Sponsor**: Caitie McCaffrey (@CaitieM20) -- **PR**: https://github.com/modelcontextprotocol/specification/pull/{NUMBER} - -## Abstract -1. We want the protocol to be stateless. This change is a step towards that goal. -2. SEP-2260 clarified that server requests must be associated with a client request. -3. This change provides a more robust mechanism for that association, without relying on protocol sessions or persistent connections to map server-initiated requests back to the original client request. -4. This change provides a mechanism for MCP Servers that cannot support persistent connections via an SSE stream (e.g., due to infrastructure limitations) to still use server-initiated requests like sampling and elicitation. - -The key intuition here is that server initiated requests are a special kind of response to a client request — one that doesn't provide a final result, but instead provides instructions for how the client should retry with additional information. Another mental model is to view these as a recoverable error. By modeling them this way, we can eliminate the need for any server-side state or session affinity to link the original request with subsequent interactions. - -To support this new paradigm this SEP introudces the following: - -1. **`IncompleteResult`**: A new result type returned via - `JSONRPCIncompleteResultResponse` that signals additional input is needed, - carrying `inputRequests` and/or opaque `requestState`. -2. **`RequestParams` augmentation**: `inputResponses` and `requestState` - fields added directly to `RequestParams`, allowing any client-initiated - request to carry retry context. -3. **Removal of standalone `CreateMessageRequest` / `ElicitRequest`**: These - server-to-client request types are replaced by `SamplingCreateRequest` and - `ElicitationCreateRequest` embedded within `InputRequests`. - -4. **New data structures**: `InputRequests` / `InputResponses` maps for - bundling server-initiated requests and client responses, using typed - `ElicitationCreateRequest | SamplingCreateRequest` and - `ElicitResult | CreateMessageResult` values respectively. -5. **Two workflows**: An *ephemeral* workflow (stateless retry loop) and a - *persistent* workflow (leveraging Tasks with `tasks/input_response`). -6. **Removal of `URLElicitationRequiredError`**: Replaced by the - `IncompleteResult` mechanism. - -7. **`GetTaskPayloadResultResponse` union**: Can now return either a completed - result or a `JSONRPCIncompleteResultResponse` for tasks needing input. - - -We start with the observation that there are two types of messaging patterns that MCP supports today: -1. **Ephemeral**: No state is accumulated on the server side. These requests are typically short running and inexpensive to execute. - - If server needs more info to process the request, it can start from - scratch when it gets that additional info. - - Examples: weather app, accessing email -2. **Persistent**: State is accumulated on the server side. These requests are typically long-running and/or expensive to execute. `Tasks` was introduced to handle this pattern. - - Server may generate a large amount of state before requesting more - info from the client, and it may need to pick up that state to - continue processing after it receives the info from the client. - - Server may need to continue processing in the background while - waiting for more info from the client, in which case server-side - state is needed to track that ongoing processing. - - Examples: accessing an agent, spinning up a VM and needing user - interaction to manipulate the VM - -Today many MCP requests are ephemeral, particularly Tools. We need to make it cheap and easy for MCP Servers to implement this pattern without requiring the additional complexity of session state, or server side state. - -This SEP introduces Multi Round-Trip Requests (MRTR), a mechanism for -handling server-initiated requests (e.g., elicitation, sampling) within -client-initiated requests (e.g., tool calls) without requiring shared -storage or stateful load balancing. The key changes are: - -## Motivation - -Server-initiated requests during client-initiated operations (e.g., an -elicitation during a tool call) currently require either a persistent -storage layer shared across server instances or stateful load balancing. -Both approaches are expensive, operationally complex, and fragile — yet -the vast majority of MCP tools are ephemeral and stateless. - -Additionally, the current approach depends on SSE streams for delivering -server-initiated requests, which causes problems in environments that -cannot support long-lived connections. - -MRTR eliminates these dependencies by ensuring each HTTP request is -self-contained: servers can process any individual request using only the -information present in that request, with no inter-request state required -on the server side. - -## Specification - -### Data Structures -This SEP introduces a new `JSONRPCResponse` type `JSONRPCIncompleteResultResponse`. When the server determines that more information is needed to process a client request, the server returns this `JSONRPCResponse`. - -This new response type includes two new fields: `inputRequests` and `requestState`. -- `inputRequests` is a map of client actions (elicitation, sampling) the server needs to complete the request, keyed by client-chosen identifiers. -- `requestState` is an opaque string that the server can use to encode any information it needs the client to return on the next request. - -Example `JSONRPCIncompleteResultResponse` with an elicitation input request and request state: - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "result": { - "inputRequests": { - "github_login": { - "method": "elicitation/create", - "params": { - "message": "Please provide your GitHub username", - "requestedSchema": { - "type": "object", - "properties": { - "name": { "type": "string" } - }, - "required": ["name"] - } - } - } - }, - "requestState": "eyJsb2dpbiI6bnVsbH0..." - } -} -``` - - - -The client provides the requested information on subsequent requests via `inputResponses` & `requestState` fields. - -```json -{ - "jsonrpc": "2.0", - "id": 2, - "inputResponses": { - "github_login": { - "action": "accept", - "content": { - "name": "octocat" - } - } - }, - "requestState": "eyJsb2dpbiI6bnVsbH0..." - // additional request info (e.g., method, params) would go here -} - -``` - -In the schema these are added directly to -`RequestParams`, making them available on any client-initiated request: - -```typescript -export interface RequestParams { - _meta?: RequestMetaObject; - inputResponses?: InputResponses; - requestState?: string; -} -``` - - -#### Use Cases for `requestState` -TODO: add some rationale for requestState because we got rid of JSONRPC id linking. - -- **Rolling upgrades**: When a new server version needs different input - than the old version, `requestState` preserves already-gathered answers - across rounds without server-side storage. -- **Load shedding**: A server can offload in-progress computation by - encoding accumulated state in `requestState` (with no `inputRequests`), - allowing any other instance to resume processing. - - -### Ephemeral Workflow - -This section describes the MRTR workflow for ephemeral interactions that do not require server-side state. The workflow proceeds in rounds: - -1. Client sends request (e.g., `tools/call`). -2. Server returns `JSONRPCIncompleteResultResponse` with `inputRequests` - and/or `requestState`. This terminates the original request. -3. Client fulfills the input requests, then sends a **new, independent** - request (with a new JSON-RPC `id`) including `inputResponses` and - echoing back `requestState` in the params. -4. Server returns a complete result, or another `IncompleteResult` for - additional rounds. - -**Server Behavior:** -- Servers MAY respond to any client-initiated request with a - `JSONRPCIncompleteResultResponse`, sent as a standalone response or as - the final message on an SSE stream. -- The response MAY include `inputRequests` and/or `requestState`. -- Servers SHOULD encrypt `requestState` (e.g., AES-GCM, signed JWT) to ensure confidentiality, integrity, and user-binding. -- If `requestState` is present on a request, servers MUST validate it on receipt (the client is an untrusted intermediary). - -**Client Behavior:** -- If `inputRequests` is present, clients MUST fulfill them before retrying. If absent, clients MAY retry immediately. -- If `requestState` is present, clients MUST echo it back exactly. - Clients MUST NOT inspect, parse, or modify `requestState`. - -For complete examples see -- [Ephemeral Workflow: Basic Flow](#ephemeral-workflow-basic-flow) -- [Ephemeral Workflow: Multi-Round with requestState](#ephemeral-workflow-multi-round-with-requeststate-azure-devops) - -## Persistent Workflow - -This section describes the MRTR workflows for interactions that require server-side state, using the Tasks API. The key difference from the ephemeral workflow is that instead of returning `JSONRPCIncompleteResultResponse` directly, the server sets the Task status to `input_required` and includes `inputRequests` in the `tasks/result` response. The client then fulfills the input requests via a new `tasks/input_response` method, allowing the server to resume processing and update the Task status accordingly. - -The `JSONRPCIncompleteResultResponse` returned by `tasks/result` in has the same structure as in the ephemeral workflow. However, since `tasks` already have server side state, are likely long running, and more expensive to compute a mechanism to provide the input responses without retrying the initial request is provided via the new method `tasks/input_response`. - -The workflow proceeds as follows, with steps 1-3 ahdering to how `tasks` is implemented today, and steps 4-6 illustrating the new MRTR mechanism for eliciting & providing additional input: - -1. Server sets Task status to `input_required` and MAY pause processing. -2. Client polls `tasks/get`, sees `input_required` status. -3. Client calls `tasks/result` to discover what input is needed. -4. Server returns `JSONRPCIncompleteResultResponse` with `inputRequests`. -5. Client calls `tasks/input_response` with `InputResponses` and task metadata. -6. Server sets Task status to `working`, and continues with request processing. - -The client delivers input responses via the `tasks/input_response` method: - -```json -{ - "jsonrpc": "2.0", - "id": 4, - "method": "tasks/input_response", - "params": { - "inputResponses": { - "echo_input": { - "action": "accept", - "content": { - "input": "Hello World!" - } - } - }, - "_meta": { - "io.modelcontextprotocol/related-task": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" - } - } - } -} -``` - -For a completeexample see [Persistent Workflow: Echo Tool with Elicitation](#persistent-workflow-echo-tool-with-elicitation) for a complete example. - -**Server Behavior:** -- If Servers set task status to `input_required` they MUST respond to subsequent `tasks/result` calls with `JSONRPCIncompleteResultResponse`. -- If Servers do not receive the requested information before the ttl expires, they SHOULD set the Task status to `failed` with an appropriate error message. - -**Client Behavior:** -- When `tasks/get` shows `input_required`, clients MUST call - `tasks/result` to get input requests. -- Clients MUST fulfill `input_requests` and then call `tasks/input_response` with the responses and task metadata to allow the server to resume processing without retrying the original request. -- If a client does not wish to fulfill the input requests or cannot it SHOULD call `tasks/cancel` to cancel the Task and free server resources. - -### Workflow Transitions - -A request may start with the ephemeral workflow and switch to the persistent -workflow by creating a task once it has the information needed to begin -long-running processing. The reverse is not possible — once a task is -created, all subsequent interaction must use the Tasks API. - -### Error Handling - -The server MUST validate that `inputResponses` is well-formed and -parseable. Protocol errors (malformed JSON, invalid schema) return a -`JSONRPCErrorResponse`. - -If the data is well-formed but unexpected or incomplete, the server MUST -treat values as optional: ignore unexpected keys, and if required -information is missing, respond with a new `IncompleteResult` requesting -the needed information again. This approach leverages the existing retry -mechanics of the ephemeral workflow and the Task state machine to ensure -clients can always recover. - -## Rationale - -- **Map vs. single object for input requests**: A map structurally - guarantees unique keys, avoiding the need for explicit conflict checks - in SDKs and applications. -- **Bidirectional streams rejected**: Would have required HTTP/2 or - HTTP/3, would not have solved long-lived connection problems, and would - not have addressed fault tolerance. -- **Separate `tasks/get` and `tasks/result`**: Keeps task status polling - at consistent latency, independent of the actual task state. The extra - round-trip can be optimized in the future if needed. -- **Direct values in `InputResponses` (no `{ "result": ... }` wrapper)**: - `InputRequests` maps keys directly to `InputRequest` objects without a - wrapper, so `InputResponses` mirrors that symmetry. The wrapper would - add nesting with no additional information. - -## Backward Compatibility - -Existing tools that use the inline async pattern with SSE streams: - -```python -def my_tool(): - do_mutation1() - await elicit_more_info() - do_mutation2() -``` - -Should be rewritten to use the MRTR pattern: - -```python -def my_tool(request): - github_login = request.inputResponses().get('github_login', None) - if github_login is None: - return IncompleteResponse({'github_login': elicitation_request}) - result = GetResult(github_login) - return Result(result) -``` - -SDKs should provide a backward compatibility layer to support existing -tool implementations during the transition. - -## Security Implications - -Because `requestState` passes through the client (an untrusted -intermediary), servers MUST: - -- Validate all state received from the client. -- Encrypt `requestState` (e.g., AES-GCM, signed JWT) if it contains - sensitive data, to ensure confidentiality and integrity. -- Cryptographically bind `requestState` to the authenticated user if it - contains user-specific data, to prevent replay/hijacking attacks. -- Treat plaintext state as untrusted input and validate it the same way - as any client-supplied data. - -## Reference Implementation - -- Schema types are defined in [`schema/draft/schema.ts`](../schema/draft/schema.ts) - under the "Multi Round-Trip" category. -- Example JSON files for each type are in - [`schema/draft/examples/`](../schema/draft/examples/) (e.g., - `InputRequests/`, `InputResponses/`, `IncompleteResult/`, - `TaskInputResponseRequest/`, `GetTaskPayloadResultResponse/`). - ---- - -## Additional Examples - -### Ephemeral Workflow: Basic Flow - -#### Step 1 — Client sends initial tool call - -```json -{ - "jsonrpc": "2.0", - "id": 2, - "method": "tools/call", - "params": { - "name": "get_weather", - "arguments": { - "location": "New York" - } - } -} -``` - -#### Step 2 — Server returns IncompleteResult with elicitation request - -```json -{ - "jsonrpc": "2.0", - "id": 2, - "result": { - "inputRequests": { - "github_login": { - "method": "elicitation/create", - "params": { - "message": "Please provide your GitHub username", - "requestedSchema": { - "type": "object", - "properties": { - "name": { "type": "string" } - }, - "required": ["name"] - } - } - } - }, - "requestState": "foo" - } -} -``` - -#### Step 3 — Client retries with inputResponses and requestState - -```json -{ - "jsonrpc": "2.0", - "id": 3, - "method": "tools/call", - "params": { - "name": "get_weather", - "arguments": { - "location": "New York" - }, - "inputResponses": { - "github_login": { - "action": "accept", - "content": { - "name": "octocat" - } - } - }, - "requestState": "foo" - } -} -``` - -#### Step 4 — Server returns final result - -```json -{ - "jsonrpc": "2.0", - "id": 3, - "result": { - "content": [ - { - "type": "text", - "text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy" - } - ], - "isError": false - } -} -``` - -### Ephemeral Workflow: Multi-Round with requestState (Azure DevOps) - -This example demonstrates iterative elicitation driven by Azure DevOps -custom rules. An `update_work_item` tool resolves Bug #4522. Rule 1 -requires a "Resolution" field; Rule 2 (triggered when Resolution = -"Duplicate") requires a "Duplicate Of" link. - -#### Round 1 — Client invokes tool - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "method": "tools/call", - "params": { - "name": "update_work_item", - "arguments": { - "workItemId": 4522, - "fields": { "System.State": "Resolved" } - } - } -} -``` - -#### Round 1 — Server elicits Resolution - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "result": { - "inputRequests": { - "resolution": { - "method": "elicitation/create", - "params": { - "message": "Resolving Bug #4522 requires a resolution. How was this bug resolved?", - "requestedSchema": { - "type": "object", - "properties": { - "resolution": { - "type": "string", - "enum": ["Fixed", "Won't Fix", "Duplicate", "By Design"], - "description": "Resolution type for this bug" - } - }, - "required": ["resolution"] - } - } - } - } - } -} -``` - -#### Round 1 — Client retries with resolution - -```json -{ - "jsonrpc": "2.0", - "id": 2, - "method": "tools/call", - "params": { - "name": "update_work_item", - "arguments": { - "workItemId": 4522, - "fields": { "System.State": "Resolved" } - }, - "inputResponses": { - "resolution": { - "action": "accept", - "content": { "resolution": "Duplicate" } - } - } - } -} -``` - -#### Round 2 — Server elicits Duplicate Of, encodes resolution in requestState - -```json -{ - "jsonrpc": "2.0", - "id": 2, - "result": { - "inputRequests": { - "duplicate_of": { - "method": "elicitation/create", - "params": { - "message": "Since this is a duplicate, which work item is the original?", - "requestedSchema": { - "type": "object", - "properties": { - "duplicateOfId": { - "type": "number", - "description": "Work item ID of the original bug" - } - }, - "required": ["duplicateOfId"] - } - } - } - }, - "requestState": "eyJyZXNvbHV0aW9uIjoiRHVwbGljYXRlIn0..." - } -} -``` - -#### Round 2 — Client retries with duplicate ID and requestState - -```json -{ - "jsonrpc": "2.0", - "id": 3, - "method": "tools/call", - "params": { - "name": "update_work_item", - "arguments": { - "workItemId": 4522, - "fields": { "System.State": "Resolved" } - }, - "inputResponses": { - "duplicate_of": { - "action": "accept", - "content": { "duplicateOfId": 4301 } - } - }, - "requestState": "eyJyZXNvbHV0aW9uIjoiRHVwbGljYXRlIn0..." - } -} -``` - -#### Final — Server completes update - -```json -{ - "jsonrpc": "2.0", - "id": 3, - "result": { - "content": [ - { - "type": "text", - "text": "Bug #4522 resolved as Duplicate of Bug #4301. State set to Resolved and duplicate link created." - } - ], - "isError": false - } -} -``` - -### Persistent Workflow: Echo Tool with Elicitation - -Full task lifecycle for an Echo tool that requests input via elicitation. - -#### Step 1 — Client invokes tool with task metadata - -```json -{ - "jsonrpc": "2.0", - "id": 1, - "method": "tools/call", - "params": { - "name": "echo", - "task": { - "ttl": 60000 - } - } -} -``` - -#### Step 2 — Server creates task - -```json -{ - "id": 1, - "jsonrpc": "2.0", - "result": { - "task": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5", - "status": "working", - "statusMessage": "Task has been created for echo tool invocation.", - "createdAt": "2026-01-27T03:32:48.3148180Z", - "lastUpdatedAt": "2026-01-27T03:32:48.3148180Z", - "ttl": 60000, - "pollInterval": 100 - } - } -} -``` - -#### Step 3 — Client polls task status - -```json -{ - "jsonrpc": "2.0", - "id": 2, - "method": "tasks/get", - "params": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" - } -} -``` - -#### Step 4 — Server responds with `input_required` - -```json -{ - "id": 2, - "jsonrpc": "2.0", - "result": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5", - "status": "input_required", - "statusMessage": "Input Required to Proceed call tasks/result", - "createdAt": "2026-01-27T03:38:07.7534643Z", - "lastUpdatedAt": "2026-01-27T03:38:07.7534643Z", - "ttl": 60000, - "pollInterval": 100 - } -} -``` - -#### Step 5 — Client calls `tasks/result` - -```json -{ - "jsonrpc": "2.0", - "id": 3, - "method": "tasks/result", - "params": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" - } -} -``` - -#### Step 6 — Server returns inputRequests - -```json -{ - "id": 3, - "jsonrpc": "2.0", - "result": { - "inputRequests": { - "echo_input": { - "method": "elicitation/create", - "params": { - "mode": "form", - "message": "Please provide the input string to echo back", - "requestedSchema": { - "type": "object", - "properties": { - "input": { "type": "string" } - }, - "required": ["input"] - } - } - } - }, - "_meta": { - "io.modelcontextprotocol/related-task": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" - } - } - } -} -``` - -#### Step 7 — Client sends `tasks/input_response` - -```json -{ - "jsonrpc": "2.0", - "id": 4, - "method": "tasks/input_response", - "params": { - "inputResponses": { - "echo_input": { - "action": "accept", - "content": { - "input": "Hello World!" - } - } - }, - "_meta": { - "io.modelcontextprotocol/related-task": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" - } - } - } -} -``` - -#### Step 8 — Server acknowledges - -```json -{ - "id": 4, - "jsonrpc": "2.0", - "result": { - "_meta": { - "io.modelcontextprotocol/related-task": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" - } - } - } -} -``` - -#### Step 9 — Client polls, task is completed - -```json -{ - "jsonrpc": "2.0", - "id": 5, - "method": "tasks/get", - "params": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" - } -} -``` - -```json -{ - "id": 5, - "jsonrpc": "2.0", - "result": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5", - "status": "completed", - "statusMessage": "Task has been completed successfully, call tasks/result", - "createdAt": "2026-01-27T03:38:07.7534643Z", - "lastUpdatedAt": "2026-01-27T03:38:08.1234567Z", - "ttl": 60000, - "pollInterval": 100 - } -} -``` - -#### Step 10 — Client retrieves final result - -```json -{ - "id": 6, - "jsonrpc": "2.0", - "method": "tasks/result", - "params": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" - } -} -``` - -```json -{ - "id": 6, - "jsonrpc": "2.0", - "result": { - "isError": false, - "content": [ - { - "type": "text", - "text": "Echo: Hello World!" - } - ], - "_meta": { - "io.modelcontextprotocol/related-task": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" - } - } - } -} -``` - -### Error Handling: Missing or Unexpected inputResponses - -#### Ephemeral — Client sends unexpected data - -```json -{ - "jsonrpc": "2.0", - "id": 3, - "method": "tools/call", - "params": { - "name": "get_weather", - "arguments": { - "location": "New York" - }, - "inputResponses": { - "not_requested_info": { - "action": "accept", - "content": { - "not_requested_param_name": "Information the server did not request" - } - } - } - } -} -``` - -Server ignores unexpected data and re-issues the original input request: - -```json -{ - "jsonrpc": "2.0", - "id": 3, - "result": { - "inputRequests": { - "github_login": { - "method": "elicitation/create", - "params": { - "message": "Please provide your GitHub username", - "requestedSchema": { - "type": "object", - "properties": { - "name": { "type": "string" } - }, - "required": ["name"] - } - } - } - } - } -} -``` - -#### Persistent — Client sends unexpected data - -```json -{ - "jsonrpc": "2.0", - "id": 4, - "method": "tasks/input_response", - "params": { - "inputResponses": { - "echo_input": { - "action": "accept", - "content": { - "not_requested_parameter": "Information the server did not request." - } - } - }, - "_meta": { - "io.modelcontextprotocol/related-task": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" - } - } - } -} -``` - -Server acknowledges but leaves Task status as `input_required`. The next -`tasks/result` call returns a new `inputRequest`: - -```json -{ - "id": 4, - "jsonrpc": "2.0", - "result": { - "_meta": { - "io.modelcontextprotocol/related-task": { - "taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5" - } - } - } -} -``` - -### InputRequests with Multiple Request Types - -A server can request both elicitation and sampling in a single response: - -```json5 -"inputRequests": { - // Elicitation request. - "github_login": { - "method": "elicitation/create", - "params": { - "message": "Please provide your GitHub username", - "requestedSchema": { - "type": "object", - "properties": { - "name": { "type": "string" } - }, - "required": ["name"] - } - } - }, - // Sampling request. - "capital_of_france": { - "method": "sampling/createMessage", - "params": { - "messages": [ - { - "role": "user", - "content": { - "type": "text", - "text": "What is the capital of France?" - } - } - ], - "modelPreferences": { - "hints": [{ "name": "claude-3-sonnet" }], - "intelligencePriority": 0.8, - "speedPriority": 0.5 - }, - "systemPrompt": "You are a helpful assistant.", - "maxTokens": 100 - } - } -} -``` - -The paired responses: - -```json5 -"inputResponses": { - // Elicitation response (ElicitResult). - "github_login": { - "action": "accept", - "content": { - "name": "octocat" - } - }, - // Sampling response (CreateMessageResult). - "capital_of_france": { - "role": "assistant", - "content": { - "type": "text", - "text": "The capital of France is Paris." - }, - "model": "claude-3-sonnet-20240307", - "stopReason": "endTurn" - } -} -``` - ---- - -### Acknowledgments - -Thanks to Luca Chang (@LucaButBoring) for his valuable input on how to -integrate input requests into Tasks. From 5cc289d57e6c72070932a2807b81ddb3422ca9c7 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Fri, 27 Feb 2026 19:34:12 -0800 Subject: [PATCH 20/27] merging in XXXX-MRTR.md changes in from transport-wg repo --- seps/XXXX-MRTR.md | 60 +++++++++++++++++++++++++++++------------------ 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/seps/XXXX-MRTR.md b/seps/XXXX-MRTR.md index fe6ea51db..231bdd3be 100644 --- a/seps/XXXX-MRTR.md +++ b/seps/XXXX-MRTR.md @@ -125,7 +125,11 @@ There are two main approaches that can be used to solve this problem today: Also, both of these approaches rely on the use of an SSE stream, which causes problems in environments that cannot support long-lived -connections. +connections. They also require an instance of the tool to stay in memory +in a particular server instance indefinitely. This is particularly +problematic for elicitation requests specifically, since the result may +not come from the user for an unbounded amount of time (e.g., it could +be days or months, or maybe even never). The goal of this SEP is to propose a simpler way to handle the pattern of server-initiated requests within the context of a client-initiated @@ -160,11 +164,17 @@ responses, the map values are the responses to those requests. Here's how that would look in the typescript MCP schema: ```typescript -export type InputRequest = ElicitationCreateRequest | SamplingCreateRequest; +export type InputRequest = + | CreateMessageRequest + | ElicitRequest + | ListRootsRequest; export interface InputRequests { [key: string]: InputRequest; } -export type InputResponse = ElicitResult | CreateMessageResult; +export type InputResponse = + | CreateMessageResponse + | | ElicitResult + | ListRootsResult; export interface InputResponses { [key: string]: InputResponse; } ``` @@ -249,8 +259,12 @@ These types will be used in two different workflows, one for ephemeral tools and another for persistent tools. ### Ephemeral Tool Workflow +For the ephemeral use case, in addition to input requests, we introduce +the concept of request state. In cases where the server needs more +information, the request state is sent to the client which echoes back +the state to the server, allowing the server to remain stateless. -For ephemeral tools, we will adopt the following workflow: +We will adopt the following workflow for ephemeral tools: 1. Client sends tool call request. 2. Server sends back a single response indicating that the request is @@ -292,18 +306,14 @@ export interface IncompleteResult extends Result { requestState?: string; } -// New fields added directly to RequestParams so that any client-initiated -// request can carry input responses and request state when retrying after -// an IncompleteResult. -export interface RequestParams { - _meta?: RequestMetaObject; - // Responses to server-initiated input requests from a previous - // IncompleteResult. For each key in the response's inputRequests +// New request parameter type that includes fields in a retried request. +// These parameters may be included in any client-initiated request. +export interface RetryAugmentedRequestParams extends RequestParams { + // New field to carry the responses for the server's requests from the + // IncompleteResult message. For each key in the response's inputRequests // field, the same key must appear here with the associated response. inputResponses?: InputResponses; // Request state passed back to the server from the client. - // The client must treat this as an opaque blob; it must not - // interpret it in any way. requestState?: string; } ``` @@ -675,14 +685,14 @@ The workflow here would look like this: 1. **Server Behavior:** - Servers MAY respond to any client-initiated request with a - `JSONRPCIncompleteResultResponse`. This message MAY be sent either + `IncompleteResult`. This message MAY be sent either as a standalone response or as the final message on an SSE stream, although implementations are encouraged to prefer the former. If using an SSE stream, servers MUST NOT send any message on the stream after the incomplete response message. - - The `JSONRPCIncompleteResultResponse` message MAY include an + - The `Incomplete` message MAY include an `inputRequests` field. - - The `JSONRPCIncompleteResultResponse` message MAY include a + - The `Incomplete` message MAY include a `requestState` field. If specified, this field is an opaque string that is meaningful only to the server. Servers are free to encode the state in any format (e.g., plain JSON, base64-encoded @@ -704,13 +714,13 @@ The workflow here would look like this: validate any client-supplied data. 2. **Client Behavior:** - - If a client receives a `JSONRPCIncompleteResultResponse` message, + - If a client receives a `IncompleteResult` message, if the message contains the `inputRequests` field, then the client MUST construct the requested input before retrying the original request. In contrast, if the message does *not* contain the `inputRequests` field, then the client MAY retry the original request immediately. - - If a client receives a `JSONRPCIncompleteResultResponse` message + - If a client receives a `IncompleteResult` message that contains the `requestState` field, it MUST echo back the exact value of that field when retrying the original request. Clients MUST NOT inspect, parse, modify, or make any assumptions @@ -985,14 +995,18 @@ duration of the task, and there is no way to transition back to the ephemeral model. All subsequent interactions must be performed via the Tasks API. -## Error Handling Note -In both the ephemeral & persistent workflows, the client may send back malformed or invalid responses to the server's requests for more information as part of the `inputResponses` object. +### Guidance for Error Handling +This section provides implementation guidance for error handling in scenarios where the client provides unexpected or malformed data in the `inputResponses` object. + +As with any received request, the server SHOULD validate the data provided by the client is a valid `inputResponses` object and that the information inside can be correctly parsed. Protocol errors, like malformed JSON, invalid schema, or internal server errors which prevent the processing of the request should return a `JSONRPCErrorResponse` with an appropriate error code and message. + +If additional parameters are provided in the `inputResponses` object The server SHOULD treat these as optional parameters. Therefore it SHOULD ignore any unexpected information in the `inputResponses` object that it does not recognize or need. -The server should validate the data provided by the client is a valid `inputResponses` object and that the information inside can be correctly parsed. Protocol errors, like malformed JSON, invalid schema, or internal server errors which prevent the processing of the request should return a `JSONRPCErrorResponse` with an appropriate error code and message. +The client may also fail to send all the information requested in previous `inputRequests`. If the missing information requested is necessary for the server to process the request, then it SHOULD respond with a new `IncompleteResult`. -If the client provides a well-formed `inputResponses` object but the data provided is unexpected or missing required information, the server MUST treat these as optional parameters. The server should ignore any unexpected information and proceed with processing the request using the information that is present. If required information is missing, the server should not proceed with processing the request and instead should respond with a new incomplete response. +We discussed having a specific application level error code returned, however the client may not have enough information to recover in all scenarios. Therefore, we decided to rely on the existing mechanics of requesting more input via `IncompleteResult` to ensure a client can always recover by having the server request the necessary information again. -We discussed having a specific application level error code returned, however the client may not have enough information to recover in all scenarios. For example, if a Server upgrade happens and the new version requires additional information, the client has no knowledge of this and must request the necessary information again. Therefore, we decided to rely on the existing mechanics of epheremal workflows and the existing state machine of `Tasks` to ensure a client can always recover and request the necessary information again. +Malicious clients could intentionally send incorrect information in the `inputResponses` object, and generate load on the server by causing it to repeatedly request the same information. However, this is not a new concern introduced by this workflow, since malicious clients could already generate load by sending malformed requests. Server implementors can use standard techniques like rate limiting and throttling to protect themselves from such attacks. In the ephemeral workflow, this would look like the following: 1. The client retries the original tool call, this time including the `inputResponses` object, but the response is missing required information that the server needs to process the request. From 901013777f7a7d9a84546159a8e48ba38fb94e80 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Fri, 27 Feb 2026 19:37:10 -0800 Subject: [PATCH 21/27] fix date typo --- seps/XXXX-MRTR.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/seps/XXXX-MRTR.md b/seps/XXXX-MRTR.md index 231bdd3be..040bd89cf 100644 --- a/seps/XXXX-MRTR.md +++ b/seps/XXXX-MRTR.md @@ -2,7 +2,7 @@ - **Status**: Draft - **Type**: Standards Track -- **Created**: 2026-20-03 +- **Created**: 2026-02-03 - **Author(s)**: Mark D. Roth (@markdroth), Caitie McCaffrey (@CaitieM20), Gabriel Zimmerman (@gjz22) - **Sponsor**: Caitie McCaffrey (@CaitieM20) @@ -126,7 +126,7 @@ There are two main approaches that can be used to solve this problem today: Also, both of these approaches rely on the use of an SSE stream, which causes problems in environments that cannot support long-lived connections. They also require an instance of the tool to stay in memory -in a particular server instance indefinitely. This is particularly +in a particular server instance indefinitely. This is particularly problematic for elicitation requests specifically, since the result may not come from the user for an unbounded amount of time (e.g., it could be days or months, or maybe even never). From f01df3c3141ba150df587842584c5688447faed9 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Fri, 27 Feb 2026 20:31:21 -0800 Subject: [PATCH 22/27] fixing some schema merge errors and deleting no longer used example --- .../list-roots-result-response.json | 12 - schema/draft/schema.json | 233 ++++++++++-------- schema/draft/schema.ts | 150 ++--------- 3 files changed, 149 insertions(+), 246 deletions(-) delete mode 100644 schema/draft/examples/ListRootsResultResponse/list-roots-result-response.json diff --git a/schema/draft/examples/ListRootsResultResponse/list-roots-result-response.json b/schema/draft/examples/ListRootsResultResponse/list-roots-result-response.json deleted file mode 100644 index 1e7e15393..000000000 --- a/schema/draft/examples/ListRootsResultResponse/list-roots-result-response.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": "list-roots-example", - "result": { - "roots": [ - { - "uri": "file:///home/user/projects/myproject", - "name": "My Project" - } - ] - } -} diff --git a/schema/draft/schema.json b/schema/draft/schema.json index f6a102648..0888f60af 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -217,7 +217,14 @@ "type": "string" }, "result": { - "$ref": "#/$defs/CallToolResult" + "anyOf": [ + { + "$ref": "#/$defs/IncompleteResult" + }, + { + "$ref": "#/$defs/CallToolResult" + } + ] } }, "required": [ @@ -421,7 +428,7 @@ "properties": { "create": { "additionalProperties": true, - "description": "Whether the client supports task-augmented {@link ElicitationCreateRequestelicitation/create} requests.", + "description": "Whether the client supports task-augmented {@link ElicitRequestelicitation/create} requests.", "properties": {}, "type": "object" } @@ -433,7 +440,7 @@ "properties": { "createMessage": { "additionalProperties": true, - "description": "Whether the client supports task-augmented {@link SamplingCreateRequestsampling/createMessage} requests.", + "description": "Whether the client supports task-augmented {@link CreateMessageRequestsampling/createMessage} requests.", "properties": {}, "type": "object" } @@ -510,13 +517,13 @@ "$ref": "#/$defs/GetTaskPayloadRequest" }, { - "$ref": "#/$defs/CancelTaskRequest" + "$ref": "#/$defs/TaskInputResponseRequest" }, { - "$ref": "#/$defs/ListTasksRequest" + "$ref": "#/$defs/CancelTaskRequest" }, { - "$ref": "#/$defs/TaskInputResponseRequest" + "$ref": "#/$defs/ListTasksRequest" }, { "$ref": "#/$defs/SetLevelRequest" @@ -531,6 +538,9 @@ { "$ref": "#/$defs/Result" }, + { + "$ref": "#/$defs/ListRootsResult" + }, { "$ref": "#/$defs/GetTaskResult", "description": "The result returned for a {@link GetTaskRequesttasks/get} request." @@ -544,9 +554,6 @@ }, { "$ref": "#/$defs/ListTasksResult" - }, - { - "$ref": "#/$defs/ListRootsResult" } ] }, @@ -713,6 +720,23 @@ } ] }, + "CreateMessageRequest": { + "description": "A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.", + "properties": { + "method": { + "const": "sampling/createMessage", + "type": "string" + }, + "params": { + "$ref": "#/$defs/CreateMessageRequestParams" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, "CreateMessageRequestParams": { "description": "Parameters for a `sampling/createMessage` request.", "properties": { @@ -792,7 +816,7 @@ "type": "object" }, "CreateMessageResult": { - "description": "The result returned by the client for a {@link SamplingCreateRequestsampling/createMessage} request.\nThe client should inform the user before returning the sampled message, to allow them\nto inspect the response (human in the loop) and decide whether to allow the server to see it.", + "description": "The result returned by the client for a {@link CreateMessageRequestsampling/createMessage} request.\nThe client should inform the user before returning the sampled message, to allow them\nto inspect the response (human in the loop) and decide whether to allow the server to see it.", "properties": { "_meta": { "$ref": "#/$defs/MetaObject" @@ -881,6 +905,23 @@ "description": "An opaque token used to represent a cursor for pagination.", "type": "string" }, + "ElicitRequest": { + "description": "A request from the server to elicit additional information from the user via the client.", + "properties": { + "method": { + "const": "elicitation/create", + "type": "string" + }, + "params": { + "$ref": "#/$defs/ElicitRequestParams" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, "ElicitRequestFormParams": { "description": "The parameters for a request to elicit non-sensitive information from the user via a form in the client.", "properties": { @@ -1001,11 +1042,8 @@ "type": "object" }, "ElicitResult": { - "description": "The result returned by the client for an {@link ElicitationCreateRequestelicitation/create} request.", + "description": "The result returned by the client for an {@link ElicitationCreateRequest elicitation/create} request.", "properties": { - "_meta": { - "$ref": "#/$defs/MetaObject" - }, "action": { "description": "The user action in response to the elicitation.\n- `\"accept\"`: User submitted the form/confirmed the action\n- `\"decline\"`: User explicitly declined the action\n- `\"cancel\"`: User dismissed without making an explicit choice", "enum": [ @@ -1073,23 +1111,6 @@ ], "type": "object" }, - "ElicitationCreateRequest": { - "description": "An elicitation input request that the server wants the client to fulfill\nas part of a multi round-trip interaction.", - "properties": { - "method": { - "const": "elicitation/create", - "type": "string" - }, - "params": { - "$ref": "#/$defs/ElicitRequestParams" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, "EmbeddedResource": { "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit\nof the LLM and/or the user.", "properties": { @@ -1254,7 +1275,14 @@ "type": "string" }, "result": { - "$ref": "#/$defs/GetPromptResult" + "anyOf": [ + { + "$ref": "#/$defs/IncompleteResult" + }, + { + "$ref": "#/$defs/GetPromptResult" + } + ] } }, "required": [ @@ -1524,11 +1552,9 @@ "$ref": "#/$defs/MetaObject" }, "inputRequests": { - "$ref": "#/$defs/InputRequests", - "description": "Requests issued by the server that the client must fulfill before retrying\nthe original request." + "$ref": "#/$defs/InputRequests" }, "requestState": { - "description": "Opaque state to be passed back to the server when the client retries the\noriginal request. Clients MUST treat this as an opaque blob and MUST NOT\ninspect, parse, or modify it.", "type": "string" } }, @@ -1665,13 +1691,15 @@ "InputRequest": { "anyOf": [ { - "$ref": "#/$defs/ElicitationCreateRequest" + "$ref": "#/$defs/CreateMessageRequest" + }, + { + "$ref": "#/$defs/ListRootsRequest" }, { - "$ref": "#/$defs/SamplingCreateRequest" + "$ref": "#/$defs/ElicitRequest" } - ], - "description": "A request that the server wants the client to fulfill as part of a multi round-trip\ninteraction. The object contains the method name and params of the server-initiated\nrequest (e.g., an elicitation or sampling request), but without the JSON-RPC `id` field." + ] }, "InputRequests": { "additionalProperties": { @@ -1683,13 +1711,15 @@ "InputResponse": { "anyOf": [ { - "$ref": "#/$defs/ElicitResult" + "$ref": "#/$defs/CreateMessageResult" }, { - "$ref": "#/$defs/CreateMessageResult" + "$ref": "#/$defs/ListRootsResult" + }, + { + "$ref": "#/$defs/ElicitResult" } - ], - "description": "The client's response to a single server-initiated input request." + ] }, "InputResponses": { "additionalProperties": { @@ -2000,7 +2030,14 @@ "type": "string" }, "result": { - "$ref": "#/$defs/ListPromptsResult" + "anyOf": [ + { + "$ref": "#/$defs/IncompleteResult" + }, + { + "$ref": "#/$defs/ListPromptsResult" + } + ] } }, "required": [ @@ -2068,7 +2105,14 @@ "type": "string" }, "result": { - "$ref": "#/$defs/ListResourceTemplatesResult" + "anyOf": [ + { + "$ref": "#/$defs/IncompleteResult" + }, + { + "$ref": "#/$defs/ListResourceTemplatesResult" + } + ] } }, "required": [ @@ -2136,7 +2180,14 @@ "type": "string" }, "result": { - "$ref": "#/$defs/ListResourcesResult" + "anyOf": [ + { + "$ref": "#/$defs/IncompleteResult" + }, + { + "$ref": "#/$defs/ListResourcesResult" + } + ] } }, "required": [ @@ -2149,13 +2200,6 @@ "ListRootsRequest": { "description": "Sent from the server to request a list of root URIs from the client. Roots allow\nservers to ask for specific directories or files to operate on. A common example\nfor roots is providing a set of repositories or directories a server should operate\non.\n\nThis request is typically used when the server needs to understand the file system\nstructure or access specific locations that the client has permission to read from.", "properties": { - "id": { - "$ref": "#/$defs/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - }, "method": { "const": "roots/list", "type": "string" @@ -2165,8 +2209,6 @@ } }, "required": [ - "id", - "jsonrpc", "method" ], "type": "object" @@ -2174,9 +2216,6 @@ "ListRootsResult": { "description": "The result returned by the client for a {@link ListRootsRequestroots/list} request.\nThis result contains an array of {@link Root} objects, each representing a root directory\nor file that the server can operate on.", "properties": { - "_meta": { - "$ref": "#/$defs/MetaObject" - }, "roots": { "items": { "$ref": "#/$defs/Root" @@ -2189,27 +2228,6 @@ ], "type": "object" }, - "ListRootsResultResponse": { - "description": "A successful response from the client for a {@link ListRootsRequestroots/list} request.", - "properties": { - "id": { - "$ref": "#/$defs/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "result": { - "$ref": "#/$defs/ListRootsResult" - } - }, - "required": [ - "id", - "jsonrpc", - "result" - ], - "type": "object" - }, "ListTasksRequest": { "description": "A request to retrieve a list of tasks.", "properties": { @@ -2336,7 +2354,14 @@ "type": "string" }, "result": { - "$ref": "#/$defs/ListToolsResult" + "anyOf": [ + { + "$ref": "#/$defs/IncompleteResult" + }, + { + "$ref": "#/$defs/ListToolsResult" + } + ] } }, "required": [ @@ -2957,7 +2982,14 @@ "type": "string" }, "result": { - "$ref": "#/$defs/ReadResourceResult" + "anyOf": [ + { + "$ref": "#/$defs/IncompleteResult" + }, + { + "$ref": "#/$defs/ReadResourceResult" + } + ] } }, "required": [ @@ -3312,6 +3344,22 @@ }, "type": "object" }, + "RetryAugmentedRequestParams": { + "properties": { + "_meta": { + "$ref": "#/$defs/RequestMetaObject" + }, + "inputResponses": { + "$ref": "#/$defs/InputResponses", + "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + }, + "requestState": { + "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", + "type": "string" + } + }, + "type": "object" + }, "Role": { "description": "The sender or recipient of messages and data in a conversation.", "enum": [ @@ -3362,23 +3410,6 @@ ], "type": "object" }, - "SamplingCreateRequest": { - "description": "A sampling input request that the server wants the client to fulfill\nas part of a multi round-trip interaction.", - "properties": { - "method": { - "const": "sampling/createMessage", - "type": "string" - }, - "params": { - "$ref": "#/$defs/CreateMessageRequestParams" - } - }, - "required": [ - "method", - "params" - ], - "type": "object" - }, "SamplingMessage": { "description": "Describes a message issued to or received from an LLM API.", "properties": { @@ -3578,6 +3609,9 @@ }, "ServerRequest": { "anyOf": [ + { + "$ref": "#/$defs/ListRootsRequest" + }, { "$ref": "#/$defs/PingRequest" }, @@ -3592,9 +3626,6 @@ }, { "$ref": "#/$defs/ListTasksRequest" - }, - { - "$ref": "#/$defs/ListRootsRequest" } ] }, diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index c4a61aacf..cb7b62ec8 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -369,11 +369,21 @@ export interface InputResponses { } /** - * Incomplete response. May be sent in response to any client-initiated - * request. - * At least one of the the inputRequests and requestState fields must be - * present, since the presence of these fields allow the client to - * determine that the result is incomplete. + * An IncompleteResult sent by the server to indicate that additional input is needed + * before the request can be completed. + * + * At least one of `inputRequests` or `requestState` MUST be present. + * + * @example Incomplete result with input requests + * {@includeCode ./examples/IncompleteResult/incomplete-result-with-input-requests.json} + * + * @example Incomplete result with elicitation and sampling input requests + * {@includeCode ./examples/IncompleteResult/incomplete-result-with-elicitation-and-sampling.json} + * + * @example Incomplete result with request state only (load shedding) + * {@includeCode ./examples/IncompleteResult/incomplete-result-with-request-state-only.json} + * + * @category Multi Round-Trip */ export interface IncompleteResult extends Result { /* Requests issued by the server that must be complete before the @@ -613,7 +623,7 @@ export interface ClientCapabilities { */ sampling?: { /** - * Whether the client supports task-augmented {@link SamplingCreateRequest | sampling/createMessage} requests. + * Whether the client supports task-augmented {@link CreateMessageRequest | sampling/createMessage} requests. */ createMessage?: object; }; @@ -622,7 +632,7 @@ export interface ClientCapabilities { */ elicitation?: { /** - * Whether the client supports task-augmented {@link ElicitationCreateRequest | elicitation/create} requests. + * Whether the client supports task-augmented {@link ElicitRequest | elicitation/create} requests. */ create?: object; }; @@ -2130,131 +2140,6 @@ export interface TaskStatusNotification extends JSONRPCNotification { /* Multi Round-Trip Requests */ -/** - * An elicitation input request that the server wants the client to fulfill - * as part of a multi round-trip interaction. - * - * @example Elicitation input request - * {@includeCode ./examples/InputRequest/elicitation-input-request.json} - * - * @category Multi Round-Trip - */ -export interface ElicitationCreateRequest { - method: "elicitation/create"; - params: ElicitRequestParams; -} - -/** - * A sampling input request that the server wants the client to fulfill - * as part of a multi round-trip interaction. - * - * @example Sampling input request - * {@includeCode ./examples/InputRequest/sampling-input-request.json} - * - * @category Multi Round-Trip - */ -export interface SamplingCreateRequest { - method: "sampling/createMessage"; - params: CreateMessageRequestParams; -} - -/** - * A request that the server wants the client to fulfill as part of a multi round-trip - * interaction. The object contains the method name and params of the server-initiated - * request (e.g., an elicitation or sampling request), but without the JSON-RPC `id` field. - * - * @category Multi Round-Trip - */ -export type InputRequest = ElicitationCreateRequest | SamplingCreateRequest; - -/** - * A map of server-initiated requests that the client must fulfill. - * Keys are server-assigned identifiers; values are the request objects. - * - * @example Elicitation and sampling input requests - * {@includeCode ./examples/InputRequests/elicitation-and-sampling-input-requests.json} - * - * @category Multi Round-Trip - */ -export interface InputRequests { - [key: string]: InputRequest; -} - -/** - * The client's response to a single server-initiated input request. - * - * @example Elicitation input response - * {@includeCode ./examples/InputResponse/elicitation-input-response.json} - * - * @example Sampling input response - * {@includeCode ./examples/InputResponse/sampling-input-response.json} - * - * @category Multi Round-Trip - */ -export type InputResponse = ElicitResult | CreateMessageResult; - -/** - * A map of client responses to server-initiated requests. - * Keys correspond to the keys in the {@link InputRequests} map; - * values are the client's result for each request. - * - * @example Elicitation and sampling input responses - * {@includeCode ./examples/InputResponses/elicitation-and-sampling-input-responses.json} - * - * @category Multi Round-Trip - */ -export interface InputResponses { - [key: string]: InputResponse; -} - -/** - * An IncompleteResult sent by the server to indicate that additional input is needed - * before the request can be completed. - * - * At least one of `inputRequests` or `requestState` MUST be present. - * - * @example Incomplete result with input requests - * {@includeCode ./examples/IncompleteResult/incomplete-result-with-input-requests.json} - * - * @example Incomplete result with elicitation and sampling input requests - * {@includeCode ./examples/IncompleteResult/incomplete-result-with-elicitation-and-sampling.json} - * - * @example Incomplete result with request state only (load shedding) - * {@includeCode ./examples/IncompleteResult/incomplete-result-with-request-state-only.json} - * - * @category Multi Round-Trip - */ -export interface IncompleteResult extends Result { - /** - * Requests issued by the server that the client must fulfill before retrying - * the original request. - */ - inputRequests?: InputRequests; - /** - * Opaque state to be passed back to the server when the client retries the - * original request. Clients MUST treat this as an opaque blob and MUST NOT - * inspect, parse, or modify it. - */ - requestState?: string; -} - -/** - * Parameters for a `tasks/input_response` request. - * Used to deliver client responses to server-initiated input requests - * for a task in the persistent multi round-trip workflow. - * - * @example Task input response params - * {@includeCode ./examples/TaskInputResponseRequestParams/task-input-response-params.json} - * - * @category `tasks/input_response` - */ -export interface TaskInputResponseRequestParams extends TaskAugmentedRequestParams { - /** - * The client's responses to the server's input requests. - */ - inputResponses: InputResponses; -} - /** * A request from the client to deliver input responses for a task * that is in `input_required` status. @@ -2465,7 +2350,6 @@ export interface CreateMessageRequest { /** * The result returned by the client for a {@link CreateMessageRequest | sampling/createMessage} request. - * The result returned by the client for a {@link SamplingCreateRequest | sampling/createMessage} request. * The client should inform the user before returning the sampled message, to allow them * to inspect the response (human in the loop) and decide whether to allow the server to see it. * From aaa44a3eff9e4e433fd115ec4b438bb27482ff99 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Fri, 27 Feb 2026 20:42:14 -0800 Subject: [PATCH 23/27] remove JSONRPCIncompleteResultType --- schema/draft/schema.ts | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index cb7b62ec8..daa110466 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -88,19 +88,6 @@ export interface TaskAugmentedRequestParams extends RetryAugmentedRequestParams */ export interface RequestParams { _meta?: RequestMetaObject; - /** - * Responses to server-initiated input requests from a previous - * {@link IncompleteResult}. Present only when retrying a request - * after receiving an incomplete result. - */ - inputResponses?: InputResponses; - /** - * Opaque request state echoed back from a previous {@link IncompleteResult}. - * Clients MUST return this value exactly as received. Present only when - * retrying a request after receiving an incomplete result that included - * a `requestState` field. - */ - requestState?: string; } /** @internal */ @@ -193,18 +180,6 @@ export interface JSONRPCResultResponse { result: Result; } -/** - * A response to a request that indicates the result is incomplete and - * additional input is needed. - * - * @category JSON-RPC - */ -export interface JSONRPCIncompleteResultResponse { - jsonrpc: typeof JSONRPC_VERSION; - id: RequestId; - result: IncompleteResult; -} - /** * A response to a request that indicates an error occurred. * @@ -221,7 +196,7 @@ export interface JSONRPCErrorResponse { * * @category JSON-RPC */ -export type JSONRPCResponse = JSONRPCResultResponse | JSONRPCErrorResponse | JSONRPCIncompleteResultResponse; +export type JSONRPCResponse = JSONRPCResultResponse | JSONRPCErrorResponse; // Standard JSON-RPC error codes export const PARSE_ERROR = -32700; From 3fc57a1b03e2cc1d9f036dfd41df40e137d16851 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Fri, 27 Feb 2026 20:54:08 -0800 Subject: [PATCH 24/27] add back examples fix GetTaskPayloadResultResponse --- .../sampling-request.json | 25 +++ .../ElicitRequest/elicitation-request.json | 17 +++ schema/draft/schema.json | 142 ++++-------------- schema/draft/schema.ts | 6 +- 4 files changed, 75 insertions(+), 115 deletions(-) create mode 100644 schema/draft/examples/CreateMessageRequest/sampling-request.json create mode 100644 schema/draft/examples/ElicitRequest/elicitation-request.json diff --git a/schema/draft/examples/CreateMessageRequest/sampling-request.json b/schema/draft/examples/CreateMessageRequest/sampling-request.json new file mode 100644 index 000000000..70a17485f --- /dev/null +++ b/schema/draft/examples/CreateMessageRequest/sampling-request.json @@ -0,0 +1,25 @@ +{ + "method": "sampling/createMessage", + "params": { + "messages": [ + { + "role": "user", + "content": { + "type": "text", + "text": "What is the capital of France?" + } + } + ], + "modelPreferences": { + "hints": [ + { + "name": "claude-3-sonnet" + } + ], + "intelligencePriority": 0.8, + "speedPriority": 0.5 + }, + "systemPrompt": "You are a helpful assistant.", + "maxTokens": 100 + } +} diff --git a/schema/draft/examples/ElicitRequest/elicitation-request.json b/schema/draft/examples/ElicitRequest/elicitation-request.json new file mode 100644 index 000000000..61e9196ef --- /dev/null +++ b/schema/draft/examples/ElicitRequest/elicitation-request.json @@ -0,0 +1,17 @@ +{ + "method": "elicitation/create", + "params": { + "message": "Please provide your GitHub username", + "requestedSchema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "title": "GitHub Username", + "description": "Your GitHub username" + } + }, + "required": ["name"] + } + } +} diff --git a/schema/draft/schema.json b/schema/draft/schema.json index 0888f60af..e003c557f 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -157,15 +157,13 @@ "type": "object" }, "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + "$ref": "#/$defs/InputResponses" }, "name": { "description": "The name of the tool.", "type": "string" }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" }, "task": { @@ -621,8 +619,7 @@ "type": "object" }, "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + "$ref": "#/$defs/InputResponses" }, "ref": { "anyOf": [ @@ -635,7 +632,6 @@ ] }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" } }, @@ -753,8 +749,7 @@ "type": "string" }, "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + "$ref": "#/$defs/InputResponses" }, "maxTokens": { "description": "The requested maximum number of tokens to sample (to prevent runaway completions).\n\nThe client MAY choose to sample fewer tokens than the requested maximum.", @@ -777,7 +772,6 @@ "description": "The server's preferences for which model to select. The client MAY ignore these preferences." }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" }, "stopSequences": { @@ -929,8 +923,7 @@ "$ref": "#/$defs/RequestMetaObject" }, "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + "$ref": "#/$defs/InputResponses" }, "message": { "description": "The message to present to the user describing what information is being requested.", @@ -942,7 +935,6 @@ "type": "string" }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" }, "requestedSchema": { @@ -1007,8 +999,7 @@ "type": "string" }, "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + "$ref": "#/$defs/InputResponses" }, "message": { "description": "The message to present to the user explaining why the interaction is needed.", @@ -1020,7 +1011,6 @@ "type": "string" }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" }, "task": { @@ -1225,15 +1215,13 @@ "type": "object" }, "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + "$ref": "#/$defs/InputResponses" }, "name": { "description": "The name of the prompt or prompt template.", "type": "string" }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" } }, @@ -1338,30 +1326,32 @@ "type": "object" }, "GetTaskPayloadResultResponse": { - "anyOf": [ - { - "$ref": "#/$defs/JSONRPCIncompleteResultResponse" + "description": "A successful response for a {@link GetTaskPayloadRequesttasks/result} request.\nMay be either a complete result or an {@link IncompleteResult} indicating\nthat additional input is needed via {@link TaskInputResponseRequesttasks/input_response}.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" }, - { - "allOf": [ + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "anyOf": [ { - "$ref": "#/$defs/JSONRPCResultResponse" + "$ref": "#/$defs/IncompleteResult" }, { - "properties": { - "result": { - "$ref": "#/$defs/GetTaskPayloadResult" - } - }, - "required": [ - "result" - ], - "type": "object" + "$ref": "#/$defs/GetTaskPayloadResult" } ] } + }, + "required": [ + "id", + "jsonrpc", + "result" ], - "description": "A successful response for a {@link GetTaskPayloadRequesttasks/result} request.\nMay be either a complete result or an {@link IncompleteResult} indicating\nthat additional input is needed via {@link TaskInputResponseRequesttasks/input_response}." + "type": "object" }, "GetTaskRequest": { "description": "A request to retrieve the state of a task.", @@ -1598,17 +1588,9 @@ "clientInfo": { "$ref": "#/$defs/Implementation" }, - "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." - }, "protocolVersion": { "description": "The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well.", "type": "string" - }, - "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", - "type": "string" } }, "required": [ @@ -1814,27 +1796,6 @@ ], "type": "object" }, - "JSONRPCIncompleteResultResponse": { - "description": "A response to a request that indicates the result is incomplete and\nadditional input is needed.", - "properties": { - "id": { - "$ref": "#/$defs/RequestId" - }, - "jsonrpc": { - "const": "2.0", - "type": "string" - }, - "result": { - "$ref": "#/$defs/IncompleteResult" - } - }, - "required": [ - "id", - "jsonrpc", - "result" - ], - "type": "object" - }, "JSONRPCMessage": { "anyOf": [ { @@ -1848,9 +1809,6 @@ }, { "$ref": "#/$defs/JSONRPCErrorResponse" - }, - { - "$ref": "#/$defs/JSONRPCIncompleteResultResponse" } ], "description": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent." @@ -1908,9 +1866,6 @@ }, { "$ref": "#/$defs/JSONRPCErrorResponse" - }, - { - "$ref": "#/$defs/JSONRPCIncompleteResultResponse" } ], "description": "A response to a request, containing either the result or error." @@ -2594,14 +2549,6 @@ "cursor": { "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", "type": "string" - }, - "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." - }, - "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", - "type": "string" } }, "type": "object" @@ -2928,11 +2875,9 @@ "$ref": "#/$defs/RequestMetaObject" }, "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + "$ref": "#/$defs/InputResponses" }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" }, "uri": { @@ -3049,14 +2994,6 @@ "properties": { "_meta": { "$ref": "#/$defs/RequestMetaObject" - }, - "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." - }, - "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", - "type": "string" } }, "type": "object" @@ -3213,11 +3150,9 @@ "$ref": "#/$defs/RequestMetaObject" }, "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + "$ref": "#/$defs/InputResponses" }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" }, "uri": { @@ -3350,11 +3285,9 @@ "$ref": "#/$defs/RequestMetaObject" }, "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + "$ref": "#/$defs/InputResponses" }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" } }, @@ -3715,17 +3648,9 @@ "_meta": { "$ref": "#/$defs/RequestMetaObject" }, - "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." - }, "level": { "$ref": "#/$defs/LoggingLevel", "description": "The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as {@link LoggingMessageNotificationnotifications/message}." - }, - "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", - "type": "string" } }, "required": [ @@ -3833,11 +3758,9 @@ "$ref": "#/$defs/RequestMetaObject" }, "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + "$ref": "#/$defs/InputResponses" }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" }, "uri": { @@ -3920,11 +3843,9 @@ "$ref": "#/$defs/RequestMetaObject" }, "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + "$ref": "#/$defs/InputResponses" }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" }, "task": { @@ -3971,7 +3892,6 @@ "description": "The client's responses to the server's input requests." }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" }, "task": { @@ -4505,11 +4425,9 @@ "$ref": "#/$defs/RequestMetaObject" }, "inputResponses": { - "$ref": "#/$defs/InputResponses", - "description": "Responses to server-initiated input requests from a previous\n{@link IncompleteResult}. Present only when retrying a request\nafter receiving an incomplete result." + "$ref": "#/$defs/InputResponses" }, "requestState": { - "description": "Opaque request state echoed back from a previous {@link IncompleteResult}.\nClients MUST return this value exactly as received. Present only when\nretrying a request after receiving an incomplete result that included\na `requestState` field.", "type": "string" }, "uri": { diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index daa110466..04bbe2f6c 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -1994,9 +1994,9 @@ export interface GetTaskPayloadResult extends Result { * * @category `tasks/result` */ -export type GetTaskPayloadResultResponse = - | (JSONRPCResultResponse & { result: GetTaskPayloadResult }) - | JSONRPCIncompleteResultResponse; +export interface GetTaskPayloadResultResponse extends JSONRPCResultResponse { + result: GetTaskPayloadResult | IncompleteResult; +} /** * Parameters for a `tasks/input_response` request. From bdc62ac719338bbc78595101e41dceec1de95d55 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Fri, 27 Feb 2026 21:00:01 -0800 Subject: [PATCH 25/27] removing duplicate types --- schema/draft/schema.ts | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 04bbe2f6c..4763aadfc 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -2113,31 +2113,6 @@ export interface TaskStatusNotification extends JSONRPCNotification { params: TaskStatusNotificationParams; } -/* Multi Round-Trip Requests */ - -/** - * A request from the client to deliver input responses for a task - * that is in `input_required` status. - * - * @example Task input response request - * {@includeCode ./examples/TaskInputResponseRequest/task-input-response-request.json} - * - * @category `tasks/input_response` - */ -export interface TaskInputResponseRequest extends JSONRPCRequest { - method: "tasks/input_response"; - params: TaskInputResponseRequestParams; -} - -/** - * A successful response for a {@link TaskInputResponseRequest | tasks/input_response} request. - * - * @category `tasks/input_response` - */ -export interface TaskInputResponseResultResponse extends JSONRPCResultResponse { - result: Result; -} - /* Logging */ /** From b54eafbd67b6dc4f15a6648e21cd0814e8ee3c32 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Fri, 27 Feb 2026 21:11:00 -0800 Subject: [PATCH 26/27] fixing TaskInputResponseRequest having duplicate InputResponse fields due to inheritance bug --- schema/draft/schema.ts | 19 +------------------ seps/XXXX-MRTR.md | 4 ++-- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 4763aadfc..23a4c1958 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -1998,23 +1998,6 @@ export interface GetTaskPayloadResultResponse extends JSONRPCResultResponse { result: GetTaskPayloadResult | IncompleteResult; } -/** - * Parameters for a `tasks/input_response` request. - * Used to deliver client responses to server-initiated input requests - * for a task in the persistent multi round-trip workflow. - * - * @example Task input response params - * {@includeCode ./examples/TaskInputResponseRequestParams/task-input-response-params.json} - * - * @category `tasks/input_response` - */ -export interface TaskInputResponseRequestParams extends TaskAugmentedRequestParams { - /** - * The client's responses to the server's input requests. - */ - inputResponses: InputResponses; -} - /** * A request from the client to deliver input responses for a task * that is in `input_required` status. @@ -2026,7 +2009,7 @@ export interface TaskInputResponseRequestParams extends TaskAugmentedRequestPara */ export interface TaskInputResponseRequest extends JSONRPCRequest { method: "tasks/input_response"; - params: TaskInputResponseRequestParams; + params: TaskAugmentedRequestParams; } /** diff --git a/seps/XXXX-MRTR.md b/seps/XXXX-MRTR.md index 040bd89cf..7690eb2e2 100644 --- a/seps/XXXX-MRTR.md +++ b/seps/XXXX-MRTR.md @@ -172,8 +172,8 @@ export type InputRequest = export interface InputRequests { [key: string]: InputRequest; } export type InputResponse = - | CreateMessageResponse - | | ElicitResult + | CreateMessageResult + | ElicitResult | ListRootsResult; export interface InputResponses { [key: string]: InputResponse; } From 3443ffacf49eb7897fc2c3317746b88f36e22452 Mon Sep 17 00:00:00 2001 From: Caitie McCaffrey Date: Fri, 27 Feb 2026 22:24:27 -0800 Subject: [PATCH 27/27] Removing inheritance from TaskAugmentedRequestParams these objects are no longer Requests in the MCP Request/Response sense --- schema/draft/schema.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 23a4c1958..012cff21e 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -2207,7 +2207,7 @@ export type LoggingLevel = * * @category `sampling/createMessage` */ -export interface CreateMessageRequestParams extends TaskAugmentedRequestParams { +export interface CreateMessageRequestParams { messages: SamplingMessage[]; /** * The server's preferences for which model to select. The client MAY ignore these preferences. @@ -2853,7 +2853,7 @@ export interface RootsListChangedNotification extends JSONRPCNotification { * * @category `elicitation/create` */ -export interface ElicitRequestFormParams extends TaskAugmentedRequestParams { +export interface ElicitRequestFormParams { /** * The elicitation mode. */ @@ -2886,7 +2886,7 @@ export interface ElicitRequestFormParams extends TaskAugmentedRequestParams { * * @category `elicitation/create` */ -export interface ElicitRequestURLParams extends TaskAugmentedRequestParams { +export interface ElicitRequestURLParams { /** * The elicitation mode. */