fix: align task response types with MCP spec (#658)

This commit is contained in:
Dale Seo 2026-02-13 17:56:44 -05:00 • committed by GitHub
commit 08a5b0551b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 349 additions and 134 deletions

View file

@ -47,7 +47,7 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
status: rmcp::model::TaskStatus::Working, status: rmcp::model::TaskStatus::Working,
status_message: None, status_message: None,
created_at: timestamp.clone(), created_at: timestamp.clone(),
last_updated_at: Some(timestamp), last_updated_at: timestamp,
ttl: None, ttl: None,
poll_interval: None, poll_interval: None,
} }
@ -111,7 +111,7 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
status: rmcp::model::TaskStatus::Working, status: rmcp::model::TaskStatus::Working,
status_message: Some("Task accepted".to_string()), status_message: Some("Task accepted".to_string()),
created_at: timestamp.clone(), created_at: timestamp.clone(),
last_updated_at: Some(timestamp), last_updated_at: timestamp,
ttl: None, ttl: None,
poll_interval: None, poll_interval: None,
}; };
@ -128,7 +128,7 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
&self, &self,
request: rmcp::model::GetTaskInfoParam, request: rmcp::model::GetTaskInfoParam,
_context: rmcp::service::RequestContext<rmcp::RoleServer>, _context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<rmcp::model::GetTaskInfoResult, McpError> { ) -> Result<rmcp::model::GetTaskResult, McpError> {
use rmcp::task_manager::current_timestamp; use rmcp::task_manager::current_timestamp;
let task_id = request.task_id.clone(); let task_id = request.task_id.clone();
let mut processor = (#processor).lock().await; let mut processor = (#processor).lock().await;
@ -156,11 +156,11 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
status, status,
status_message: None, status_message: None,
created_at: timestamp.clone(), created_at: timestamp.clone(),
last_updated_at: Some(timestamp), last_updated_at: timestamp,
ttl: completed_result.descriptor.ttl, ttl: completed_result.descriptor.ttl,
poll_interval: None, poll_interval: None,
}; };
return Ok(rmcp::model::GetTaskInfoResult { task: Some(task) }); return Ok(rmcp::model::GetTaskResult { meta: None, task });
} }
// If not completed, check running // If not completed, check running
@ -172,14 +172,14 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
status: rmcp::model::TaskStatus::Working, status: rmcp::model::TaskStatus::Working,
status_message: None, status_message: None,
created_at: timestamp.clone(), created_at: timestamp.clone(),
last_updated_at: Some(timestamp), last_updated_at: timestamp,
ttl: None, ttl: None,
poll_interval: None, poll_interval: None,
}; };
return Ok(rmcp::model::GetTaskInfoResult { task: Some(task) }); return Ok(rmcp::model::GetTaskResult { meta: None, task });
} }
Ok(rmcp::model::GetTaskInfoResult { task: None }) Err(McpError::resource_not_found(format!("task not found: {}", task_id), None))
} }
}; };
item_impl.items.push(syn::parse2::<ImplItem>(get_info_fn)?); item_impl.items.push(syn::parse2::<ImplItem>(get_info_fn)?);
@ -191,7 +191,7 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
&self, &self,
request: rmcp::model::GetTaskResultParam, request: rmcp::model::GetTaskResultParam,
_context: rmcp::service::RequestContext<rmcp::RoleServer>, _context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<rmcp::model::TaskResult, McpError> { ) -> Result<rmcp::model::GetTaskPayloadResult, McpError> {
use std::time::Duration; use std::time::Duration;
let task_id = request.task_id.clone(); let task_id = request.task_id.clone();
@ -207,11 +207,7 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
match &tool.result { match &tool.result {
Ok(call_tool) => { Ok(call_tool) => {
let value = ::serde_json::to_value(call_tool).unwrap_or(::serde_json::Value::Null); let value = ::serde_json::to_value(call_tool).unwrap_or(::serde_json::Value::Null);
return Ok(rmcp::model::TaskResult { return Ok(rmcp::model::GetTaskPayloadResult(value));
content_type: "application/json".to_string(),
value,
summary: None,
});
} }
Err(err) => return Err(McpError::internal_error( Err(err) => return Err(McpError::internal_error(
format!("task failed: {}", err), format!("task failed: {}", err),
@ -251,12 +247,23 @@ pub fn task_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
&self, &self,
request: rmcp::model::CancelTaskParam, request: rmcp::model::CancelTaskParam,
_context: rmcp::service::RequestContext<rmcp::RoleServer>, _context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<(), McpError> { ) -> Result<rmcp::model::CancelTaskResult, McpError> {
use rmcp::task_manager::current_timestamp;
let task_id = request.task_id; let task_id = request.task_id;
let mut processor = (#processor).lock().await; let mut processor = (#processor).lock().await;
if processor.cancel_task(&task_id) { if processor.cancel_task(&task_id) {
return Ok(()); let timestamp = current_timestamp();
let task = rmcp::model::Task {
task_id,
status: rmcp::model::TaskStatus::Cancelled,
status_message: None,
created_at: timestamp.clone(),
last_updated_at: timestamp,
ttl: None,
poll_interval: None,
};
return Ok(rmcp::model::CancelTaskResult { meta: None, task });
} }
// If already completed, signal it's not cancellable // If already completed, signal it's not cancellable

View file

@ -116,15 +116,15 @@ impl<H: ServerHandler> Service<RoleServer> for H {
ClientRequest::GetTaskInfoRequest(request) => self ClientRequest::GetTaskInfoRequest(request) => self
.get_task_info(request.params, context) .get_task_info(request.params, context)
.await .await
.map(ServerResult::GetTaskInfoResult), .map(ServerResult::GetTaskResult),
ClientRequest::GetTaskResultRequest(request) => self ClientRequest::GetTaskResultRequest(request) => self
.get_task_result(request.params, context) .get_task_result(request.params, context)
.await .await
.map(ServerResult::TaskResult), .map(ServerResult::GetTaskPayloadResult),
ClientRequest::CancelTaskRequest(request) => self ClientRequest::CancelTaskRequest(request) => self
.cancel_task(request.params, context) .cancel_task(request.params, context)
.await .await
.map(ServerResult::empty), .map(ServerResult::CancelTaskResult),
} }
} }
@ -339,7 +339,8 @@ pub trait ServerHandler: Sized + Send + Sync + 'static {
&self, &self,
request: GetTaskInfoParams, request: GetTaskInfoParams,
context: RequestContext<RoleServer>, context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<GetTaskInfoResult, McpError>> + Send + '_ { ) -> impl Future<Output = Result<GetTaskResult, McpError>> + Send + '_ {
let _ = (request, context);
std::future::ready(Err(McpError::method_not_found::<GetTaskInfoMethod>())) std::future::ready(Err(McpError::method_not_found::<GetTaskInfoMethod>()))
} }
@ -347,7 +348,7 @@ pub trait ServerHandler: Sized + Send + Sync + 'static {
&self, &self,
request: GetTaskResultParams, request: GetTaskResultParams,
context: RequestContext<RoleServer>, context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<TaskResult, McpError>> + Send + '_ { ) -> impl Future<Output = Result<GetTaskPayloadResult, McpError>> + Send + '_ {
let _ = (request, context); let _ = (request, context);
std::future::ready(Err(McpError::method_not_found::<GetTaskResultMethod>())) std::future::ready(Err(McpError::method_not_found::<GetTaskResultMethod>()))
} }
@ -356,7 +357,7 @@ pub trait ServerHandler: Sized + Send + Sync + 'static {
&self, &self,
request: CancelTaskParams, request: CancelTaskParams,
context: RequestContext<RoleServer>, context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<(), McpError>> + Send + '_ { ) -> impl Future<Output = Result<CancelTaskResult, McpError>> + Send + '_ {
let _ = (request, context); let _ = (request, context);
std::future::ready(Err(McpError::method_not_found::<CancelTaskMethod>())) std::future::ready(Err(McpError::method_not_found::<CancelTaskMethod>()))
} }
@ -543,7 +544,7 @@ macro_rules! impl_server_handler_for_wrapper {
&self, &self,
request: GetTaskInfoParams, request: GetTaskInfoParams,
context: RequestContext<RoleServer>, context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<GetTaskInfoResult, McpError>> + Send + '_ { ) -> impl Future<Output = Result<GetTaskResult, McpError>> + Send + '_ {
(**self).get_task_info(request, context) (**self).get_task_info(request, context)
} }
@ -551,7 +552,7 @@ macro_rules! impl_server_handler_for_wrapper {
&self, &self,
request: GetTaskResultParams, request: GetTaskResultParams,
context: RequestContext<RoleServer>, context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<TaskResult, McpError>> + Send + '_ { ) -> impl Future<Output = Result<GetTaskPayloadResult, McpError>> + Send + '_ {
(**self).get_task_result(request, context) (**self).get_task_result(request, context)
} }
@ -559,7 +560,7 @@ macro_rules! impl_server_handler_for_wrapper {
&self, &self,
request: CancelTaskParams, request: CancelTaskParams,
context: RequestContext<RoleServer>, context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<(), McpError>> + Send + '_ { ) -> impl Future<Output = Result<CancelTaskResult, McpError>> + Send + '_ {
(**self).cancel_task(request, context) (**self).cancel_task(request, context)
} }
} }

View file

@ -2537,14 +2537,9 @@ impl RequestParamsMeta for CancelTaskParams {
/// Deprecated: Use [`CancelTaskParams`] instead (SEP-1319 compliance). /// Deprecated: Use [`CancelTaskParams`] instead (SEP-1319 compliance).
#[deprecated(since = "0.13.0", note = "Use CancelTaskParams instead")] #[deprecated(since = "0.13.0", note = "Use CancelTaskParams instead")]
pub type CancelTaskParam = CancelTaskParams; pub type CancelTaskParam = CancelTaskParams;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] /// Deprecated: Use [`GetTaskResult`] instead (spec alignment).
#[serde(rename_all = "camelCase")] #[deprecated(since = "0.15.0", note = "Use GetTaskResult instead")]
#[serde(deny_unknown_fields)] pub type GetTaskInfoResult = GetTaskResult;
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GetTaskInfoResult {
#[serde(skip_serializing_if = "Option::is_none")]
pub task: Option<crate::model::Task>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -2720,9 +2715,10 @@ ts_union!(
| EmptyResult | EmptyResult
| CreateTaskResult | CreateTaskResult
| ListTasksResult | ListTasksResult
| GetTaskInfoResult | GetTaskResult
| TaskResult | CancelTaskResult
| CustomResult | CustomResult
| GetTaskPayloadResult
; ;
); );

View file

@ -1,6 +1,8 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use super::Meta;
/// Canonical task lifecycle status as defined by SEP-1686. /// Canonical task lifecycle status as defined by SEP-1686.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -19,21 +21,10 @@ pub enum TaskStatus {
Cancelled, Cancelled,
} }
/// Final result for a succeeded task (returned from `tasks/result`).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct TaskResult {
/// MIME type or custom content-type identifier.
pub content_type: String,
/// The actual result payload, matching the underlying request's schema.
pub value: Value,
/// Optional short summary for UI surfaces.
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
}
/// Primary Task object that surfaces metadata during the task lifecycle. /// Primary Task object that surfaces metadata during the task lifecycle.
///
/// Per spec, `lastUpdatedAt` and `ttl` are required fields.
/// `ttl` is nullable (`null` means unlimited retention).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
@ -48,10 +39,9 @@ pub struct Task {
/// ISO-8601 creation timestamp. /// ISO-8601 creation timestamp.
pub created_at: String, pub created_at: String,
/// ISO-8601 timestamp for the most recent status change. /// ISO-8601 timestamp for the most recent status change.
#[serde(skip_serializing_if = "Option::is_none")] pub last_updated_at: String,
pub last_updated_at: Option<String>,
/// Retention window in milliseconds that the receiver agreed to honor. /// Retention window in milliseconds that the receiver agreed to honor.
#[serde(skip_serializing_if = "Option::is_none")] /// `None` (serialized as `null`) means unlimited retention.
pub ttl: Option<u64>, pub ttl: Option<u64>,
/// Suggested polling interval (milliseconds). /// Suggested polling interval (milliseconds).
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@ -66,6 +56,43 @@ pub struct CreateTaskResult {
pub task: Task, pub task: Task,
} }
/// Response to a `tasks/get` request.
///
/// Per spec, `GetTaskResult = allOf[Result, Task]` — the Task fields are
/// flattened at the top level, not nested under a `task` key.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GetTaskResult {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
#[serde(flatten)]
pub task: Task,
}
/// Response to a `tasks/result` request.
///
/// Per spec, the result structure matches the original request type
/// (e.g., `CallToolResult` for `tools/call`). This is represented as
/// an open object. The payload is the original request's result
/// serialized as a JSON value.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct GetTaskPayloadResult(pub Value);
/// Response to a `tasks/cancel` request.
///
/// Per spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct CancelTaskResult {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
#[serde(flatten)]
pub task: Task,
}
/// Paginated list of tasks /// Paginated list of tasks
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]

View file

@ -407,6 +407,70 @@
"content" "content"
] ]
}, },
"CancelTaskResult": {
"description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.",
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
"type": "string"
},
"lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.",
"type": "string"
},
"pollInterval": {
"description": "Suggested polling interval (milliseconds).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"status": {
"description": "Current lifecycle status (see [`TaskStatus`]).",
"allOf": [
{
"$ref": "#/definitions/TaskStatus"
}
]
},
"statusMessage": {
"description": "Optional human-readable status message for UI surfaces.",
"type": [
"string",
"null"
]
},
"taskId": {
"description": "Unique task identifier generated by the receiver.",
"type": "string"
},
"ttl": {
"description": "Retention window in milliseconds that the receiver agreed to honor.\n`None` (serialized as `null`) means unlimited retention.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
}
},
"required": [
"taskId",
"status",
"createdAt",
"lastUpdatedAt"
]
},
"CancelledNotificationMethod": { "CancelledNotificationMethod": {
"type": "string", "type": "string",
"format": "const", "format": "const",
@ -937,21 +1001,72 @@
"messages" "messages"
] ]
}, },
"GetTaskInfoResult": { "GetTaskPayloadResult": {
"description": "Response to a `tasks/result` request.\n\nPer spec, the result structure matches the original request type\n(e.g., `CallToolResult` for `tools/call`). This is represented as\nan open object. The payload is the original request's result\nserialized as a JSON value."
},
"GetTaskResult": {
"description": "Response to a `tasks/get` request.\n\nPer spec, `GetTaskResult = allOf[Result, Task]` — the Task fields are\nflattened at the top level, not nested under a `task` key.",
"type": "object", "type": "object",
"properties": { "properties": {
"task": { "_meta": {
"anyOf": [ "type": [
"object",
"null"
],
"additionalProperties": true
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
"type": "string"
},
"lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.",
"type": "string"
},
"pollInterval": {
"description": "Suggested polling interval (milliseconds).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"status": {
"description": "Current lifecycle status (see [`TaskStatus`]).",
"allOf": [
{ {
"$ref": "#/definitions/Task" "$ref": "#/definitions/TaskStatus"
},
{
"type": "null"
} }
] ]
},
"statusMessage": {
"description": "Optional human-readable status message for UI surfaces.",
"type": [
"string",
"null"
]
},
"taskId": {
"description": "Unique task identifier generated by the receiver.",
"type": "string"
},
"ttl": {
"description": "Retention window in milliseconds that the receiver agreed to honor.\n`None` (serialized as `null`) means unlimited retention.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
} }
}, },
"additionalProperties": false "required": [
"taskId",
"status",
"createdAt",
"lastUpdatedAt"
]
}, },
"Icon": { "Icon": {
"description": "A URL pointing to an icon resource or a base64-encoded data URI.\n\nClients that support rendering icons MUST support at least the following MIME types:\n- image/png - PNG images (safe, universal compatibility)\n- image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- image/svg+xml - SVG images (scalable but requires security precautions)\n- image/webp - WebP images (modern, efficient format)", "description": "A URL pointing to an icon resource or a base64-encoded data URI.\n\nClients that support rendering icons MUST support at least the following MIME types:\n- image/png - PNG images (safe, universal compatibility)\n- image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- image/svg+xml - SVG images (scalable but requires security precautions)\n- image/webp - WebP images (modern, efficient format)",
@ -2701,13 +2816,16 @@
"$ref": "#/definitions/ListTasksResult" "$ref": "#/definitions/ListTasksResult"
}, },
{ {
"$ref": "#/definitions/GetTaskInfoResult" "$ref": "#/definitions/GetTaskResult"
}, },
{ {
"$ref": "#/definitions/TaskResult" "$ref": "#/definitions/CancelTaskResult"
}, },
{ {
"$ref": "#/definitions/CustomResult" "$ref": "#/definitions/CustomResult"
},
{
"$ref": "#/definitions/GetTaskPayloadResult"
} }
] ]
}, },
@ -2813,7 +2931,7 @@
"const": "string" "const": "string"
}, },
"Task": { "Task": {
"description": "Primary Task object that surfaces metadata during the task lifecycle.", "description": "Primary Task object that surfaces metadata during the task lifecycle.\n\nPer spec, `lastUpdatedAt` and `ttl` are required fields.\n`ttl` is nullable (`null` means unlimited retention).",
"type": "object", "type": "object",
"properties": { "properties": {
"createdAt": { "createdAt": {
@ -2822,10 +2940,7 @@
}, },
"lastUpdatedAt": { "lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.", "description": "ISO-8601 timestamp for the most recent status change.",
"type": [ "type": "string"
"string",
"null"
]
}, },
"pollInterval": { "pollInterval": {
"description": "Suggested polling interval (milliseconds).", "description": "Suggested polling interval (milliseconds).",
@ -2856,7 +2971,7 @@
"type": "string" "type": "string"
}, },
"ttl": { "ttl": {
"description": "Retention window in milliseconds that the receiver agreed to honor.", "description": "Retention window in milliseconds that the receiver agreed to honor.\n`None` (serialized as `null`) means unlimited retention.",
"type": [ "type": [
"integer", "integer",
"null" "null"
@ -2868,7 +2983,8 @@
"required": [ "required": [
"taskId", "taskId",
"status", "status",
"createdAt" "createdAt",
"lastUpdatedAt"
] ]
}, },
"TaskRequestsCapability": { "TaskRequestsCapability": {
@ -2907,30 +3023,6 @@
} }
} }
}, },
"TaskResult": {
"description": "Final result for a succeeded task (returned from `tasks/result`).",
"type": "object",
"properties": {
"contentType": {
"description": "MIME type or custom content-type identifier.",
"type": "string"
},
"summary": {
"description": "Optional short summary for UI surfaces.",
"type": [
"string",
"null"
]
},
"value": {
"description": "The actual result payload, matching the underlying request's schema."
}
},
"required": [
"contentType",
"value"
]
},
"TaskStatus": { "TaskStatus": {
"description": "Canonical task lifecycle status as defined by SEP-1686.", "description": "Canonical task lifecycle status as defined by SEP-1686.",
"oneOf": [ "oneOf": [

View file

@ -407,6 +407,70 @@
"content" "content"
] ]
}, },
"CancelTaskResult": {
"description": "Response to a `tasks/cancel` request.\n\nPer spec, `CancelTaskResult = allOf[Result, Task]` — same shape as `GetTaskResult`.",
"type": "object",
"properties": {
"_meta": {
"type": [
"object",
"null"
],
"additionalProperties": true
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
"type": "string"
},
"lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.",
"type": "string"
},
"pollInterval": {
"description": "Suggested polling interval (milliseconds).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"status": {
"description": "Current lifecycle status (see [`TaskStatus`]).",
"allOf": [
{
"$ref": "#/definitions/TaskStatus"
}
]
},
"statusMessage": {
"description": "Optional human-readable status message for UI surfaces.",
"type": [
"string",
"null"
]
},
"taskId": {
"description": "Unique task identifier generated by the receiver.",
"type": "string"
},
"ttl": {
"description": "Retention window in milliseconds that the receiver agreed to honor.\n`None` (serialized as `null`) means unlimited retention.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
}
},
"required": [
"taskId",
"status",
"createdAt",
"lastUpdatedAt"
]
},
"CancelledNotificationMethod": { "CancelledNotificationMethod": {
"type": "string", "type": "string",
"format": "const", "format": "const",
@ -937,21 +1001,72 @@
"messages" "messages"
] ]
}, },
"GetTaskInfoResult": { "GetTaskPayloadResult": {
"description": "Response to a `tasks/result` request.\n\nPer spec, the result structure matches the original request type\n(e.g., `CallToolResult` for `tools/call`). This is represented as\nan open object. The payload is the original request's result\nserialized as a JSON value."
},
"GetTaskResult": {
"description": "Response to a `tasks/get` request.\n\nPer spec, `GetTaskResult = allOf[Result, Task]` — the Task fields are\nflattened at the top level, not nested under a `task` key.",
"type": "object", "type": "object",
"properties": { "properties": {
"task": { "_meta": {
"anyOf": [ "type": [
"object",
"null"
],
"additionalProperties": true
},
"createdAt": {
"description": "ISO-8601 creation timestamp.",
"type": "string"
},
"lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.",
"type": "string"
},
"pollInterval": {
"description": "Suggested polling interval (milliseconds).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"status": {
"description": "Current lifecycle status (see [`TaskStatus`]).",
"allOf": [
{ {
"$ref": "#/definitions/Task" "$ref": "#/definitions/TaskStatus"
},
{
"type": "null"
} }
] ]
},
"statusMessage": {
"description": "Optional human-readable status message for UI surfaces.",
"type": [
"string",
"null"
]
},
"taskId": {
"description": "Unique task identifier generated by the receiver.",
"type": "string"
},
"ttl": {
"description": "Retention window in milliseconds that the receiver agreed to honor.\n`None` (serialized as `null`) means unlimited retention.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
} }
}, },
"additionalProperties": false "required": [
"taskId",
"status",
"createdAt",
"lastUpdatedAt"
]
}, },
"Icon": { "Icon": {
"description": "A URL pointing to an icon resource or a base64-encoded data URI.\n\nClients that support rendering icons MUST support at least the following MIME types:\n- image/png - PNG images (safe, universal compatibility)\n- image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- image/svg+xml - SVG images (scalable but requires security precautions)\n- image/webp - WebP images (modern, efficient format)", "description": "A URL pointing to an icon resource or a base64-encoded data URI.\n\nClients that support rendering icons MUST support at least the following MIME types:\n- image/png - PNG images (safe, universal compatibility)\n- image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- image/svg+xml - SVG images (scalable but requires security precautions)\n- image/webp - WebP images (modern, efficient format)",
@ -2701,13 +2816,16 @@
"$ref": "#/definitions/ListTasksResult" "$ref": "#/definitions/ListTasksResult"
}, },
{ {
"$ref": "#/definitions/GetTaskInfoResult" "$ref": "#/definitions/GetTaskResult"
}, },
{ {
"$ref": "#/definitions/TaskResult" "$ref": "#/definitions/CancelTaskResult"
}, },
{ {
"$ref": "#/definitions/CustomResult" "$ref": "#/definitions/CustomResult"
},
{
"$ref": "#/definitions/GetTaskPayloadResult"
} }
] ]
}, },
@ -2813,7 +2931,7 @@
"const": "string" "const": "string"
}, },
"Task": { "Task": {
"description": "Primary Task object that surfaces metadata during the task lifecycle.", "description": "Primary Task object that surfaces metadata during the task lifecycle.\n\nPer spec, `lastUpdatedAt` and `ttl` are required fields.\n`ttl` is nullable (`null` means unlimited retention).",
"type": "object", "type": "object",
"properties": { "properties": {
"createdAt": { "createdAt": {
@ -2822,10 +2940,7 @@
}, },
"lastUpdatedAt": { "lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.", "description": "ISO-8601 timestamp for the most recent status change.",
"type": [ "type": "string"
"string",
"null"
]
}, },
"pollInterval": { "pollInterval": {
"description": "Suggested polling interval (milliseconds).", "description": "Suggested polling interval (milliseconds).",
@ -2856,7 +2971,7 @@
"type": "string" "type": "string"
}, },
"ttl": { "ttl": {
"description": "Retention window in milliseconds that the receiver agreed to honor.", "description": "Retention window in milliseconds that the receiver agreed to honor.\n`None` (serialized as `null`) means unlimited retention.",
"type": [ "type": [
"integer", "integer",
"null" "null"
@ -2868,7 +2983,8 @@
"required": [ "required": [
"taskId", "taskId",
"status", "status",
"createdAt" "createdAt",
"lastUpdatedAt"
] ]
}, },
"TaskRequestsCapability": { "TaskRequestsCapability": {
@ -2907,30 +3023,6 @@
} }
} }
}, },
"TaskResult": {
"description": "Final result for a succeeded task (returned from `tasks/result`).",
"type": "object",
"properties": {
"contentType": {
"description": "MIME type or custom content-type identifier.",
"type": "string"
},
"summary": {
"description": "Optional short summary for UI surfaces.",
"type": [
"string",
"null"
]
},
"value": {
"description": "The actual result payload, matching the underlying request's schema."
}
},
"required": [
"contentType",
"value"
]
},
"TaskStatus": { "TaskStatus": {
"description": "Canonical task lifecycle status as defined by SEP-1686.", "description": "Canonical task lifecycle status as defined by SEP-1686.",
"oneOf": [ "oneOf": [