diff --git a/docs/specification/draft/basic/lifecycle.mdx b/docs/specification/draft/basic/lifecycle.mdx index b6a84a3f0..f5f513a99 100644 --- a/docs/specification/draft/basic/lifecycle.mdx +++ b/docs/specification/draft/basic/lifecycle.mdx @@ -150,18 +150,19 @@ available during the session. Key capabilities include: -| Category | Capability | Description | -| -------- | -------------- | ------------------------------------------------------------------------------------ | -| Client | `roots` | Ability to provide filesystem [roots](/specification/draft/client/roots) | -| Client | `sampling` | Support for LLM [sampling](/specification/draft/client/sampling) requests | -| Client | `elicitation` | Support for server [elicitation](/specification/draft/client/elicitation) requests | -| Client | `experimental` | Describes support for non-standard experimental features | -| Server | `prompts` | Offers [prompt templates](/specification/draft/server/prompts) | -| Server | `resources` | Provides readable [resources](/specification/draft/server/resources) | -| Server | `tools` | Exposes callable [tools](/specification/draft/server/tools) | -| Server | `logging` | Emits structured [log messages](/specification/draft/server/utilities/logging) | -| Server | `completions` | Supports argument [autocompletion](/specification/draft/server/utilities/completion) | -| Server | `experimental` | Describes support for non-standard experimental features | +| Category | Capability | Description | +| -------- | ------------------- | ------------------------------------------------------------------------------------ | +| Client | `roots` | Ability to provide filesystem [roots](/specification/draft/client/roots) | +| Client | `sampling` | Support for LLM [sampling](/specification/draft/client/sampling) requests | +| Client | `elicitation` | Support for server [elicitation](/specification/draft/client/elicitation) requests | +| Client | `resumableRequests` | Support for [resumable requests](#resuming-requests) | +| Client | `experimental` | Describes support for non-standard experimental features | +| Server | `prompts` | Offers [prompt templates](/specification/draft/server/prompts) | +| Server | `resources` | Provides readable [resources](/specification/draft/server/resources) | +| Server | `tools` | Exposes callable [tools](/specification/draft/server/tools) | +| Server | `logging` | Emits structured [log messages](/specification/draft/server/utilities/logging) | +| Server | `completions` | Supports argument [autocompletion](/specification/draft/server/utilities/completion) | +| Server | `experimental` | Describes support for non-standard experimental features | Capability objects can describe sub-capabilities like: @@ -179,6 +180,57 @@ Both parties **SHOULD**: - Respect the negotiated protocol version - Only use capabilities that were successfully negotiated +#### Resuming Requests + +MCP provides a transport-agnostic mechanism for resuming requests across disconnections. + +If a client has advertised the [`resumableRequests`][] capability, a server **MAY** send a [`notifications/requests/resumePolicy`][] notification when responding to a request. The notification will specify the resume policy for the request in the event of disconnection, and will include a token that the client can use to resume the request. + +After the resume policy is sent, both the client and the server **MAY** disconnect at will. This allows servers to handle long-running requests without maintaining a constant connection. + +After a disconnection, clients can resume the request by sending a [`requests/resume`][] request with the **same message ID** as the original request, plus the server-issued token as a parameter. If the ID and token are valid per the resume policy, the server **SHOULD** reset policy-related timers, send any pending messages (e.g., progress notifications), and then continue as if it were handling the original request. + + + +When using the [Streamable HTTP transport](/specification/draft/basic/transports#streamable-http) to stream messages, a server cannot confirm whether a message has been delivered. Clients use the `Last-Event-ID` HTTP header upon reconnect to indicate the last delivered message. + +In order to provide message delivery guarantees, servers SHOULD maintain a buffer of unconfirmed messages and use the `Last-Event-ID` header to determine which messages in the buffer should be sent to the client. For example, when using incremental event IDs, a server might retain all messages and, upon reconnection, discard messages with an event ID <= `Last-Event-ID` before sending the remainder to the client. + + + +Alternatively, after a disconnection, a client can send a [`requests/getStatus`][] request to get the status of the original request without fetching pending messages. If the parameters of the `requests/getStatus` request are valid per the request policy, the server **SHOULD** reset policy-related timers and then return the status of the original request. + +```mermaid +sequenceDiagram + participant Client + participant Server + + Client->>+Server: Request (e.g., tools/call)
{ id: 123, params: { ... } } + + Server-->>Client: notifications/requests/resumePolicy
{ params: { requestId: 123, resumeToken: "abc" } } + loop + Server-->>Client: Messages (e.g., notifications/progress) + end + Server--x-Client: Disconnection occurs + + Note over Client: Client checks request status (optional) + Client->>+Server: requests/getStatus
{ params: { requestId: 123, resumeToken: "abc" } } + Server-->>-Client: GetRequestStatusResult + + Note over Client: Client decides to resume + Client->>+Server: requests/resume
{ id: 123, params: { resumeToken: "abc" } }
[Same `id` as original request] + Server-->>Client: Undelivered messages + loop + Server-->>Client: Messages (e.g., notifications/progress) + end + Server-->>-Client: CallToolResult
{ id: 123, result: { ... } } +``` + +[`notifications/requests/resumePolicy`]: /specification/draft/schema#notifications%2Frequests%2Fresumepolicy +[`resumableRequests`]: /specification/draft/schema#clientcapabilities-resumablerequests +[`requests/getStatus`]: /specification/draft/schema#requests%2Fgetstatus +[`requests/resume`]: /specification/draft/schema#requests%2Fresume + ### Shutdown During the shutdown phase, one side (usually the client) cleanly terminates the protocol diff --git a/docs/specification/draft/basic/transports.mdx b/docs/specification/draft/basic/transports.mdx index 582976b06..4cb8b47eb 100644 --- a/docs/specification/draft/basic/transports.mdx +++ b/docs/specification/draft/basic/transports.mdx @@ -107,8 +107,8 @@ MCP endpoint. JSON-RPC _response_. These messages **SHOULD** relate to the originating client _request_. - The server **SHOULD NOT** close the SSE stream before sending the JSON-RPC _response_ - for the received JSON-RPC _request_, unless the [session](#session-management) - expires. + or [_resume policy notification_](/specification/draft/basic/lifecycle#resuming-requests) + for the received JSON-RPC _request_, unless the [session](#session-management) expires. - After the JSON-RPC _response_ has been sent, the server **SHOULD** close the SSE stream. - Disconnection **MAY** occur at any time (e.g., due to network conditions). diff --git a/docs/specification/draft/schema.mdx b/docs/specification/draft/schema.mdx index 458a2d482..bbca01a1f 100644 --- a/docs/specification/draft/schema.mdx +++ b/docs/specification/draft/schema.mdx @@ -27,7 +27,7 @@ the data is entirely optional.

interface ClientCapabilities {
  elicitation?: object;
  experimental?: { [key: string]: object };
  roots?: { listChanged?: boolean };
  sampling?: object;
}

Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.

elicitation?: object

Present if the client supports elicitation from the server.

experimental?: { [key: string]: object }

Experimental, non-standard capabilities that the client supports.

roots?: { listChanged?: boolean }

Present if the client supports listing roots.

Type declaration
sampling?: object

Present if the client supports sampling from an LLM.

+
interface ClientCapabilities {
  elicitation?: object;
  experimental?: { [key: string]: object };
  resumableRequests?: object;
  roots?: { listChanged?: boolean };
  sampling?: object;
}

Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.

elicitation?: object

Present if the client supports elicitation from the server.

experimental?: { [key: string]: object }

Experimental, non-standard capabilities that the client supports.

resumableRequests?: object

Present if the client supports resumable requests.

roots?: { listChanged?: boolean }

Present if the client supports listing roots.

Type declaration
sampling?: object

Present if the client supports sampling from an LLM.

### `ContentBlock` @@ -300,6 +300,21 @@ Contains values matching the requested schema.

interface PromptListChangedNotification {
  method: "notifications/prompts/list_changed";
  params?: { _meta?: { [key: string]: unknown }; [key: string]: unknown };
}

An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.

params?: { _meta?: { [key: string]: unknown }; [key: string]: unknown }
Type declaration
+## `notifications/requests/resumePolicy` + +### `ResumePolicyNotification` + +
interface ResumePolicyNotification {
  method: "notifications/requests/resumePolicy";
  params: {
    maxWait?: number;
    minWait?: number;
    requestId: RequestId;
    resumeToken: string;
  };
}

A notification to the client that a request is resumable, subject to the +specified policy.

The server SHOULD send this notification as soon as possible after +determining a request is resumable.

params: {
  maxWait?: number;
  minWait?: number;
  requestId: RequestId;
  resumeToken: string;
}
Type declaration
+ ## `notifications/resources/list_changed` ### `ResourceListChangedNotification` @@ -354,6 +369,32 @@ If provided, the server should return results starting after this cursor.

interface ListPromptsResult {
  _meta?: { [key: string]: unknown };
  nextCursor?: string;
  prompts: Prompt[];
  [key: string]: unknown;
}

The server's response to a prompts/list request from the client.

_meta?: { [key: string]: unknown }

See [specification/draft/basic/index#general-fields] for notes on _meta usage.

nextCursor?: string

An opaque token representing the pagination position after the last returned result. If present, there may be more results available.

+## `requests/getStatus` + +### `GetRequestStatusRequest` + +
interface GetRequestStatusRequest {
  method: "requests/getStatus";
  params: { requestId: RequestId; resumeToken: string };
}

A request from the client to the server to get the status of a resumable +request.

When handling this request, the server SHOULD also reset policy-related +timers in the same way as for requests/resume.

params: { requestId: RequestId; resumeToken: string }
Type declaration
+ +### `GetRequestStatusResult` + +
interface GetRequestStatusResult {
  _meta?: { [key: string]: unknown };
  hasInputRequest: boolean;
  hasPendingMessage: boolean;
  requestId: RequestId;
  status: "processing" | "completed" | "failed";
  [key: string]: unknown;
}

A response to a requests/getStatus request.

_meta?: { [key: string]: unknown }

See [specification/draft/basic/index#general-fields] for notes on _meta usage.

hasInputRequest: boolean

Whether the server has requested additional input from the client, such as +when making a sampling request.

hasPendingMessage: boolean

Whether there are pending messages for the client that are related to the +resumable request, such as progress notifications or sampling requests.

requestId: RequestId

The ID of the resumable request.

status: "processing" | "completed" | "failed"

The current status of the resumable request.

+ +## `requests/resume` + +### `ResumeRequest` + +
interface ResumeRequest {
  method: "requests/resume";
  params: { resumeToken: string };
}

A request from the client to the server to resume a prior resumable request. +The ID of this request MUST be the same as the ID of the resumable request.

If the resumable request was cancelled (due to the resume policy or due to an +explicit cancellation request from the client), the server SHOULD respond +with an error.

params: { resumeToken: string }
Type declaration
+ ## `resources/list` ### `ListResourcesRequest` diff --git a/schema/draft/schema.json b/schema/draft/schema.json index 8fea15032..4ffb07440 100644 --- a/schema/draft/schema.json +++ b/schema/draft/schema.json @@ -230,6 +230,12 @@ "description": "Experimental, non-standard capabilities that the client supports.", "type": "object" }, + "resumableRequests": { + "additionalProperties": true, + "description": "Present if the client supports resumable requests.", + "properties": {}, + "type": "object" + }, "roots": { "description": "Present if the client supports listing roots.", "properties": { @@ -305,6 +311,12 @@ }, { "$ref": "#/definitions/CompleteRequest" + }, + { + "$ref": "#/definitions/ResumeRequest" + }, + { + "$ref": "#/definitions/GetRequestStatusRequest" } ] }, @@ -764,6 +776,75 @@ ], "type": "object" }, + "GetRequestStatusRequest": { + "description": "A request from the client to the server to get the status of a resumable\nrequest.\n\nWhen handling this request, the server SHOULD also reset policy-related\ntimers in the same way as for `requests/resume`.", + "properties": { + "method": { + "const": "requests/getStatus", + "type": "string" + }, + "params": { + "properties": { + "requestId": { + "$ref": "#/definitions/RequestId", + "description": "The ID of the resumable request." + }, + "resumeToken": { + "description": "The resume token for the resumable request issued by the server via a\n`notifications/requests/resumePolicy` notification.\n\nIf this value does not match the token issued by the server, the server\nMUST respond with an error.", + "type": "string" + } + }, + "required": [ + "requestId", + "resumeToken" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "GetRequestStatusResult": { + "description": "A response to a `requests/getStatus` request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [specification/draft/basic/index#general-fields] for notes on _meta usage.", + "type": "object" + }, + "hasInputRequest": { + "description": "Whether the server has requested additional input from the client, such as\nwhen making a sampling request.", + "type": "boolean" + }, + "hasPendingMessage": { + "description": "Whether there are pending messages for the client that are related to the\nresumable request, such as progress notifications or sampling requests.", + "type": "boolean" + }, + "requestId": { + "$ref": "#/definitions/RequestId", + "description": "The ID of the resumable request." + }, + "status": { + "description": "The current status of the resumable request.\n\n- `\"processing\"` indicates that the server is computing a response.\n- `\"completed\"` indicates that the server has computed a final response.\n- `\"failed\"` indicates that the server has a final response, but the\n response is an error.", + "enum": [ + "completed", + "failed", + "processing" + ], + "type": "string" + } + }, + "required": [ + "hasInputRequest", + "hasPendingMessage", + "requestId", + "status" + ], + "type": "object" + }, "ImageContent": { "description": "An image provided to or from an LLM.", "properties": { @@ -1993,6 +2074,71 @@ }, "type": "object" }, + "ResumePolicyNotification": { + "description": "A notification to the client that a request is resumable, subject to the\nspecified policy.\n\nThe server SHOULD send this notification as soon as possible after\ndetermining a request is resumable.", + "properties": { + "method": { + "const": "notifications/requests/resumePolicy", + "type": "string" + }, + "params": { + "properties": { + "maxWait": { + "description": "The maximum number of seconds a client may wait after a disconnection\nbefore resuming the request or checking its status. After this time has\nelapsed, the server MAY cancel the request and free all associated\nresources.\n\nIf this number is omitted, the server provides no guarantee, and MAY\ncancel the request at its own discretion.", + "type": "integer" + }, + "minWait": { + "description": "The minimum number of seconds a client SHOULD wait after a disconnection\nbefore resuming the request or checking its status.", + "type": "integer" + }, + "requestId": { + "$ref": "#/definitions/RequestId", + "description": "The ID of the request." + }, + "resumeToken": { + "description": "An opaque token that the client MUST send back to the server when\nresuming the request or checking its status.\n\nThis token should be treated as sensitive information because it can be\nused to access messages related to the request.", + "type": "string" + } + }, + "required": [ + "requestId", + "resumeToken" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "ResumeRequest": { + "description": "A request from the client to the server to resume a prior resumable request.\nThe ID of this request MUST be the same as the ID of the resumable request.\n\nIf the resumable request was cancelled (due to the resume policy or due to an\nexplicit cancellation request from the client), the server SHOULD respond\nwith an error.", + "properties": { + "method": { + "const": "requests/resume", + "type": "string" + }, + "params": { + "properties": { + "resumeToken": { + "description": "The resume token for the original request issued by the server via a\n`notifications/requests/resumePolicy` notification.\n\nIf this value does not match the token issued by the server, the server\nMUST respond with an error.", + "type": "string" + } + }, + "required": [ + "resumeToken" + ], + "type": "object" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, "Role": { "description": "The sender or recipient of messages and data in a conversation.", "enum": [ @@ -2157,6 +2303,9 @@ }, { "$ref": "#/definitions/LoggingMessageNotification" + }, + { + "$ref": "#/definitions/ResumePolicyNotification" } ] }, @@ -2207,6 +2356,9 @@ }, { "$ref": "#/definitions/CompleteResult" + }, + { + "$ref": "#/definitions/GetRequestStatusResult" } ] }, diff --git a/schema/draft/schema.ts b/schema/draft/schema.ts index 01c25000a..55cce4aae 100644 --- a/schema/draft/schema.ts +++ b/schema/draft/schema.ts @@ -235,6 +235,10 @@ export interface ClientCapabilities { * Present if the client supports elicitation from the server. */ elicitation?: object; + /** + * Present if the client supports resumable requests. + */ + resumableRequests?: object; } /** @@ -1471,6 +1475,137 @@ export interface ElicitResult extends Result { content?: { [key: string]: string | number | boolean }; } +/** + * A notification to the client that a request is resumable, subject to the + * specified policy. + * + * The server SHOULD send this notification as soon as possible after + * determining a request is resumable. + * + * @category notifications/requests/resumePolicy + */ +export interface ResumePolicyNotification extends Notification { + method: "notifications/requests/resumePolicy"; + params: { + /** + * The ID of the request. + */ + requestId: RequestId; + + /** + * An opaque token that the client MUST send back to the server when + * resuming the request or checking its status. + * + * This token should be treated as sensitive information because it can be + * used to access messages related to the request. + */ + resumeToken: string; + + /** + * The minimum number of seconds a client SHOULD wait after a disconnection + * before resuming the request or checking its status. + */ + minWait?: number; + + /** + * The maximum number of seconds a client may wait after a disconnection + * before resuming the request or checking its status. After this time has + * elapsed, the server MAY cancel the request and free all associated + * resources. + * + * If this number is omitted, the server provides no guarantee, and MAY + * cancel the request at its own discretion. + */ + maxWait?: number; + } +} + +/** + * A request from the client to the server to resume a prior resumable request. + * The ID of this request MUST be the same as the ID of the resumable request. + * + * If the resumable request was cancelled (due to the resume policy or due to an + * explicit cancellation request from the client), the server SHOULD respond + * with an error. + * + * @category requests/resume + */ +export interface ResumeRequest extends Request { + method: "requests/resume"; + params: { + /** + * The resume token for the original request issued by the server via a + * `notifications/requests/resumePolicy` notification. + * + * If this value does not match the token issued by the server, the server + * MUST respond with an error. + */ + resumeToken: string; + }; +} + +/** + * A request from the client to the server to get the status of a resumable + * request. + * + * When handling this request, the server SHOULD also reset policy-related + * timers in the same way as for `requests/resume`. + * + * @category requests/getStatus + */ +export interface GetRequestStatusRequest extends Request { + method: "requests/getStatus"; + params: { + /** + * The ID of the resumable request. + */ + requestId: RequestId; + + /** + * The resume token for the resumable request issued by the server via a + * `notifications/requests/resumePolicy` notification. + * + * If this value does not match the token issued by the server, the server + * MUST respond with an error. + */ + resumeToken: string; + }; +} + +/** + * A response to a `requests/getStatus` request. + * + * @category requests/getStatus + */ +export interface GetRequestStatusResult extends Result { + /** + * The ID of the resumable request. + */ + requestId: RequestId; + + /** + * The current status of the resumable request. + * + * - `"processing"` indicates that the server is computing a response. + * - `"completed"` indicates that the server has computed a final response. + * - `"failed"` indicates that the server has a final response, but the + * response is an error. + */ + status: "processing" | "completed" | "failed"; + + /** + * Whether there are pending messages for the client that are related to the + * resumable request, such as progress notifications or sampling requests. + */ + hasPendingMessage: boolean; + + /** + * Whether the server has requested additional input from the client, such as + * when making a sampling request. + */ + hasInputRequest: boolean; +} + /* Client messages */ /** @internal */ export type ClientRequest = @@ -1486,7 +1621,9 @@ export type ClientRequest = | SubscribeRequest | UnsubscribeRequest | CallToolRequest - | ListToolsRequest; + | ListToolsRequest + | ResumeRequest + | GetRequestStatusRequest; /** @internal */ export type ClientNotification = @@ -1518,7 +1655,8 @@ export type ServerNotification = | ResourceUpdatedNotification | ResourceListChangedNotification | ToolListChangedNotification - | PromptListChangedNotification; + | PromptListChangedNotification + | ResumePolicyNotification; /** @internal */ export type ServerResult = @@ -1531,4 +1669,5 @@ export type ServerResult = | ListResourcesResult | ReadResourceResult | CallToolResult - | ListToolsResult; + | ListToolsResult + | GetRequestStatusResult;