feat(task): add task support (SEP-1686) (#536)

Signed-off-by: jokemanfire <hu.dingyang@zte.com.cn>
This commit is contained in:
jokemanfire 2025-12-22 22:01:00 +08:00 • committed by GitHub
commit 621c9f619e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1922 additions and 57 deletions

View file

@ -202,12 +202,35 @@
"name": {
"description": "The name of the tool to call",
"type": "string"
},
"task": {
"type": [
"object",
"null"
],
"additionalProperties": true
}
},
"required": [
"name"
]
},
"CancelTaskMethod": {
"type": "string",
"format": "const",
"const": "tasks/cancel"
},
"CancelTaskParam": {
"type": "object",
"properties": {
"taskId": {
"type": "string"
}
},
"required": [
"taskId"
]
},
"CancelledNotificationMethod": {
"type": "string",
"format": "const",
@ -272,6 +295,16 @@
"null"
],
"additionalProperties": true
},
"tasks": {
"anyOf": [
{
"$ref": "#/definitions/TasksCapability"
},
{
"type": "null"
}
]
}
}
},
@ -520,6 +553,38 @@
"name"
]
},
"GetTaskInfoMethod": {
"type": "string",
"format": "const",
"const": "tasks/get"
},
"GetTaskInfoParam": {
"type": "object",
"properties": {
"taskId": {
"type": "string"
}
},
"required": [
"taskId"
]
},
"GetTaskResultMethod": {
"type": "string",
"format": "const",
"const": "tasks/result"
},
"GetTaskResultParam": {
"type": "object",
"properties": {
"taskId": {
"type": "string"
}
},
"required": [
"taskId"
]
},
"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)",
"type": "object",
@ -730,6 +795,18 @@
},
{
"$ref": "#/definitions/CustomRequest"
},
{
"$ref": "#/definitions/Request9"
},
{
"$ref": "#/definitions/RequestOptionalParam5"
},
{
"$ref": "#/definitions/Request10"
},
{
"$ref": "#/definitions/Request11"
}
],
"required": [
@ -790,6 +867,11 @@
"roots"
]
},
"ListTasksMethod": {
"type": "string",
"format": "const",
"const": "tasks/list"
},
"ListToolsRequestMethod": {
"type": "string",
"format": "const",
@ -1168,6 +1250,38 @@
"params"
]
},
"Request10": {
"description": "Represents a JSON-RPC request with method, parameters, and extensions.\n\nThis is the core structure for all MCP requests, containing:\n- `method`: The name of the method being called\n- `params`: The parameters for the method\n- `extensions`: Additional context data (similar to HTTP headers)",
"type": "object",
"properties": {
"method": {
"$ref": "#/definitions/GetTaskResultMethod"
},
"params": {
"$ref": "#/definitions/GetTaskResultParam"
}
},
"required": [
"method",
"params"
]
},
"Request11": {
"description": "Represents a JSON-RPC request with method, parameters, and extensions.\n\nThis is the core structure for all MCP requests, containing:\n- `method`: The name of the method being called\n- `params`: The parameters for the method\n- `extensions`: Additional context data (similar to HTTP headers)",
"type": "object",
"properties": {
"method": {
"$ref": "#/definitions/CancelTaskMethod"
},
"params": {
"$ref": "#/definitions/CancelTaskParam"
}
},
"required": [
"method",
"params"
]
},
"Request2": {
"description": "Represents a JSON-RPC request with method, parameters, and extensions.\n\nThis is the core structure for all MCP requests, containing:\n- `method`: The name of the method being called\n- `params`: The parameters for the method\n- `extensions`: Additional context data (similar to HTTP headers)",
"type": "object",
@ -1280,6 +1394,22 @@
"params"
]
},
"Request9": {
"description": "Represents a JSON-RPC request with method, parameters, and extensions.\n\nThis is the core structure for all MCP requests, containing:\n- `method`: The name of the method being called\n- `params`: The parameters for the method\n- `extensions`: Additional context data (similar to HTTP headers)",
"type": "object",
"properties": {
"method": {
"$ref": "#/definitions/GetTaskInfoMethod"
},
"params": {
"$ref": "#/definitions/GetTaskInfoParam"
}
},
"required": [
"method",
"params"
]
},
"RequestNoParam": {
"type": "object",
"properties": {
@ -1375,6 +1505,27 @@
"method"
]
},
"RequestOptionalParam5": {
"type": "object",
"properties": {
"method": {
"$ref": "#/definitions/ListTasksMethod"
},
"params": {
"anyOf": [
{
"$ref": "#/definitions/PaginatedRequestParam"
},
{
"type": "null"
}
]
}
},
"required": [
"method"
]
},
"ResourceContents": {
"anyOf": [
{
@ -1534,6 +1685,36 @@
"uri"
]
},
"TasksCapability": {
"description": "Task capability negotiation for SEP-1686.",
"type": "object",
"properties": {
"cancel": {
"description": "Whether the receiver supports `tasks/cancel`.",
"type": [
"boolean",
"null"
]
},
"list": {
"description": "Whether the receiver supports `tasks/list`.",
"type": [
"boolean",
"null"
]
},
"requests": {
"description": "Map of request category (e.g. \"tools.call\") to a boolean indicating support.",
"type": [
"object",
"null"
],
"additionalProperties": {
"type": "boolean"
}
}
}
},
"UnsubscribeRequestMethod": {
"type": "string",
"format": "const",

View file

@ -202,12 +202,35 @@
"name": {
"description": "The name of the tool to call",
"type": "string"
},
"task": {
"type": [
"object",
"null"
],
"additionalProperties": true
}
},
"required": [
"name"
]
},
"CancelTaskMethod": {
"type": "string",
"format": "const",
"const": "tasks/cancel"
},
"CancelTaskParam": {
"type": "object",
"properties": {
"taskId": {
"type": "string"
}
},
"required": [
"taskId"
]
},
"CancelledNotificationMethod": {
"type": "string",
"format": "const",
@ -272,6 +295,16 @@
"null"
],
"additionalProperties": true
},
"tasks": {
"anyOf": [
{
"$ref": "#/definitions/TasksCapability"
},
{
"type": "null"
}
]
}
}
},
@ -520,6 +553,38 @@
"name"
]
},
"GetTaskInfoMethod": {
"type": "string",
"format": "const",
"const": "tasks/get"
},
"GetTaskInfoParam": {
"type": "object",
"properties": {
"taskId": {
"type": "string"
}
},
"required": [
"taskId"
]
},
"GetTaskResultMethod": {
"type": "string",
"format": "const",
"const": "tasks/result"
},
"GetTaskResultParam": {
"type": "object",
"properties": {
"taskId": {
"type": "string"
}
},
"required": [
"taskId"
]
},
"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)",
"type": "object",
@ -730,6 +795,18 @@
},
{
"$ref": "#/definitions/CustomRequest"
},
{
"$ref": "#/definitions/Request9"
},
{
"$ref": "#/definitions/RequestOptionalParam5"
},
{
"$ref": "#/definitions/Request10"
},
{
"$ref": "#/definitions/Request11"
}
],
"required": [
@ -790,6 +867,11 @@
"roots"
]
},
"ListTasksMethod": {
"type": "string",
"format": "const",
"const": "tasks/list"
},
"ListToolsRequestMethod": {
"type": "string",
"format": "const",
@ -1168,6 +1250,38 @@
"params"
]
},
"Request10": {
"description": "Represents a JSON-RPC request with method, parameters, and extensions.\n\nThis is the core structure for all MCP requests, containing:\n- `method`: The name of the method being called\n- `params`: The parameters for the method\n- `extensions`: Additional context data (similar to HTTP headers)",
"type": "object",
"properties": {
"method": {
"$ref": "#/definitions/GetTaskResultMethod"
},
"params": {
"$ref": "#/definitions/GetTaskResultParam"
}
},
"required": [
"method",
"params"
]
},
"Request11": {
"description": "Represents a JSON-RPC request with method, parameters, and extensions.\n\nThis is the core structure for all MCP requests, containing:\n- `method`: The name of the method being called\n- `params`: The parameters for the method\n- `extensions`: Additional context data (similar to HTTP headers)",
"type": "object",
"properties": {
"method": {
"$ref": "#/definitions/CancelTaskMethod"
},
"params": {
"$ref": "#/definitions/CancelTaskParam"
}
},
"required": [
"method",
"params"
]
},
"Request2": {
"description": "Represents a JSON-RPC request with method, parameters, and extensions.\n\nThis is the core structure for all MCP requests, containing:\n- `method`: The name of the method being called\n- `params`: The parameters for the method\n- `extensions`: Additional context data (similar to HTTP headers)",
"type": "object",
@ -1280,6 +1394,22 @@
"params"
]
},
"Request9": {
"description": "Represents a JSON-RPC request with method, parameters, and extensions.\n\nThis is the core structure for all MCP requests, containing:\n- `method`: The name of the method being called\n- `params`: The parameters for the method\n- `extensions`: Additional context data (similar to HTTP headers)",
"type": "object",
"properties": {
"method": {
"$ref": "#/definitions/GetTaskInfoMethod"
},
"params": {
"$ref": "#/definitions/GetTaskInfoParam"
}
},
"required": [
"method",
"params"
]
},
"RequestNoParam": {
"type": "object",
"properties": {
@ -1375,6 +1505,27 @@
"method"
]
},
"RequestOptionalParam5": {
"type": "object",
"properties": {
"method": {
"$ref": "#/definitions/ListTasksMethod"
},
"params": {
"anyOf": [
{
"$ref": "#/definitions/PaginatedRequestParam"
},
{
"type": "null"
}
]
}
},
"required": [
"method"
]
},
"ResourceContents": {
"anyOf": [
{
@ -1534,6 +1685,36 @@
"uri"
]
},
"TasksCapability": {
"description": "Task capability negotiation for SEP-1686.",
"type": "object",
"properties": {
"cancel": {
"description": "Whether the receiver supports `tasks/cancel`.",
"type": [
"boolean",
"null"
]
},
"list": {
"description": "Whether the receiver supports `tasks/list`.",
"type": [
"boolean",
"null"
]
},
"requests": {
"description": "Map of request category (e.g. \"tools.call\") to a boolean indicating support.",
"type": [
"object",
"null"
],
"additionalProperties": {
"type": "boolean"
}
}
}
},
"UnsubscribeRequestMethod": {
"type": "string",
"format": "const",

View file

@ -603,6 +603,18 @@
"maxTokens"
]
},
"CreateTaskResult": {
"description": "Wrapper returned by task-augmented requests (CreateTaskResult in SEP-1686).",
"type": "object",
"properties": {
"task": {
"$ref": "#/definitions/Task"
}
},
"required": [
"task"
]
},
"CustomNotification": {
"description": "A catch-all notification either side can use to send custom messages to its peer.\n\nThis preserves the raw `method` name and `params` payload so handlers can\ndeserialize them into domain-specific types.",
"type": "object",
@ -810,6 +822,21 @@
"messages"
]
},
"GetTaskInfoResult": {
"type": "object",
"properties": {
"task": {
"anyOf": [
{
"$ref": "#/definitions/Task"
},
{
"type": "null"
}
]
}
}
},
"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)",
"type": "object",
@ -1168,6 +1195,34 @@
"format": "const",
"const": "roots/list"
},
"ListTasksResult": {
"type": "object",
"properties": {
"nextCursor": {
"type": [
"string",
"null"
]
},
"tasks": {
"type": "array",
"items": {
"$ref": "#/definitions/Task"
}
},
"total": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
}
},
"required": [
"tasks"
]
},
"ListToolsResult": {
"type": "object",
"properties": {
@ -2255,6 +2310,16 @@
}
]
},
"tasks": {
"anyOf": [
{
"$ref": "#/definitions/TasksCapability"
},
{
"type": "null"
}
]
},
"tools": {
"anyOf": [
{
@ -2304,6 +2369,18 @@
},
{
"$ref": "#/definitions/CustomResult"
},
{
"$ref": "#/definitions/CreateTaskResult"
},
{
"$ref": "#/definitions/ListTasksResult"
},
{
"$ref": "#/definitions/GetTaskInfoResult"
},
{
"$ref": "#/definitions/TaskResult"
}
]
},
@ -2397,6 +2474,149 @@
"format": "const",
"const": "string"
},
"Task": {
"description": "Primary Task object that surfaces metadata during the task lifecycle.",
"type": "object",
"properties": {
"createdAt": {
"description": "ISO-8601 creation timestamp.",
"type": "string"
},
"lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.",
"type": [
"string",
"null"
]
},
"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.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
}
},
"required": [
"taskId",
"status",
"createdAt"
]
},
"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": {
"description": "Canonical task lifecycle status as defined by SEP-1686.",
"oneOf": [
{
"description": "The receiver accepted the request and is currently working on it.",
"type": "string",
"const": "working"
},
{
"description": "The receiver requires additional input before work can continue.",
"type": "string",
"const": "input_required"
},
{
"description": "The underlying operation completed successfully and the result is ready.",
"type": "string",
"const": "completed"
},
{
"description": "The underlying operation failed and will not continue.",
"type": "string",
"const": "failed"
},
{
"description": "The task was cancelled and will not continue processing.",
"type": "string",
"const": "cancelled"
}
]
},
"TasksCapability": {
"description": "Task capability negotiation for SEP-1686.",
"type": "object",
"properties": {
"cancel": {
"description": "Whether the receiver supports `tasks/cancel`.",
"type": [
"boolean",
"null"
]
},
"list": {
"description": "Whether the receiver supports `tasks/list`.",
"type": [
"boolean",
"null"
]
},
"requests": {
"description": "Map of request category (e.g. \"tools.call\") to a boolean indicating support.",
"type": [
"object",
"null"
],
"additionalProperties": {
"type": "boolean"
}
}
}
},
"Tool": {
"description": "A tool that can be used by a model.",
"type": "object",

View file

@ -603,6 +603,18 @@
"maxTokens"
]
},
"CreateTaskResult": {
"description": "Wrapper returned by task-augmented requests (CreateTaskResult in SEP-1686).",
"type": "object",
"properties": {
"task": {
"$ref": "#/definitions/Task"
}
},
"required": [
"task"
]
},
"CustomNotification": {
"description": "A catch-all notification either side can use to send custom messages to its peer.\n\nThis preserves the raw `method` name and `params` payload so handlers can\ndeserialize them into domain-specific types.",
"type": "object",
@ -810,6 +822,21 @@
"messages"
]
},
"GetTaskInfoResult": {
"type": "object",
"properties": {
"task": {
"anyOf": [
{
"$ref": "#/definitions/Task"
},
{
"type": "null"
}
]
}
}
},
"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)",
"type": "object",
@ -1168,6 +1195,34 @@
"format": "const",
"const": "roots/list"
},
"ListTasksResult": {
"type": "object",
"properties": {
"nextCursor": {
"type": [
"string",
"null"
]
},
"tasks": {
"type": "array",
"items": {
"$ref": "#/definitions/Task"
}
},
"total": {
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
}
},
"required": [
"tasks"
]
},
"ListToolsResult": {
"type": "object",
"properties": {
@ -2255,6 +2310,16 @@
}
]
},
"tasks": {
"anyOf": [
{
"$ref": "#/definitions/TasksCapability"
},
{
"type": "null"
}
]
},
"tools": {
"anyOf": [
{
@ -2304,6 +2369,18 @@
},
{
"$ref": "#/definitions/CustomResult"
},
{
"$ref": "#/definitions/CreateTaskResult"
},
{
"$ref": "#/definitions/ListTasksResult"
},
{
"$ref": "#/definitions/GetTaskInfoResult"
},
{
"$ref": "#/definitions/TaskResult"
}
]
},
@ -2397,6 +2474,149 @@
"format": "const",
"const": "string"
},
"Task": {
"description": "Primary Task object that surfaces metadata during the task lifecycle.",
"type": "object",
"properties": {
"createdAt": {
"description": "ISO-8601 creation timestamp.",
"type": "string"
},
"lastUpdatedAt": {
"description": "ISO-8601 timestamp for the most recent status change.",
"type": [
"string",
"null"
]
},
"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.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
}
},
"required": [
"taskId",
"status",
"createdAt"
]
},
"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": {
"description": "Canonical task lifecycle status as defined by SEP-1686.",
"oneOf": [
{
"description": "The receiver accepted the request and is currently working on it.",
"type": "string",
"const": "working"
},
{
"description": "The receiver requires additional input before work can continue.",
"type": "string",
"const": "input_required"
},
{
"description": "The underlying operation completed successfully and the result is ready.",
"type": "string",
"const": "completed"
},
{
"description": "The underlying operation failed and will not continue.",
"type": "string",
"const": "failed"
},
{
"description": "The task was cancelled and will not continue processing.",
"type": "string",
"const": "cancelled"
}
]
},
"TasksCapability": {
"description": "Task capability negotiation for SEP-1686.",
"type": "object",
"properties": {
"cancel": {
"description": "Whether the receiver supports `tasks/cancel`.",
"type": [
"boolean",
"null"
]
},
"list": {
"description": "Whether the receiver supports `tasks/list`.",
"type": [
"boolean",
"null"
]
},
"requests": {
"description": "Map of request category (e.g. \"tools.call\") to a boolean indicating support.",
"type": [
"object",
"null"
],
"additionalProperties": {
"type": "boolean"
}
}
}
},
"Tool": {
"description": "A tool that can be used by a model.",
"type": "object",

View file

@ -110,6 +110,7 @@ async fn test_progress_subscriber() -> anyhow::Result<()> {
ClientRequest::CallToolRequest(Request::new(CallToolRequestParam {
name: "some_progress".into(),
arguments: None,
task: None,
})),
PeerRequestOptions::no_options(),
)

View file

@ -0,0 +1,77 @@
use std::{any::Any, time::Duration};
use rmcp::task_manager::{
OperationDescriptor, OperationMessage, OperationProcessor, OperationResultTransport,
};
struct DummyTransport {
id: String,
value: u32,
}
impl OperationResultTransport for DummyTransport {
fn operation_id(&self) -> &String {
&self.id
}
fn as_any(&self) -> &dyn Any {
self
}
}
#[tokio::test]
async fn executes_enqueued_future() {
let mut processor = OperationProcessor::new();
let descriptor = OperationDescriptor::new("op1", "dummy");
let future = Box::pin(async {
tokio::time::sleep(Duration::from_millis(10)).await;
Ok(Box::new(DummyTransport {
id: "op1".to_string(),
value: 42,
}) as Box<dyn OperationResultTransport>)
});
processor
.submit_operation(OperationMessage::new(descriptor, future))
.expect("submit operation");
tokio::time::sleep(Duration::from_millis(30)).await;
let results = processor.collect_completed_results();
assert_eq!(results.len(), 1);
let payload = results[0]
.result
.as_ref()
.unwrap()
.as_any()
.downcast_ref::<DummyTransport>()
.unwrap();
assert_eq!(payload.value, 42);
}
#[tokio::test]
async fn rejects_duplicate_operation_ids() {
let mut processor = OperationProcessor::new();
let descriptor = OperationDescriptor::new("dup", "dummy");
let future = Box::pin(async {
Ok(Box::new(DummyTransport {
id: "dup".to_string(),
value: 1,
}) as Box<dyn OperationResultTransport>)
});
processor
.submit_operation(OperationMessage::new(descriptor, future))
.expect("first submit");
let descriptor_dup = OperationDescriptor::new("dup", "dummy");
let future_dup = Box::pin(async {
Ok(Box::new(DummyTransport {
id: "dup".to_string(),
value: 2,
}) as Box<dyn OperationResultTransport>)
});
let err = processor
.submit_operation(OperationMessage::new(descriptor_dup, future_dup))
.expect_err("duplicate should fail");
assert!(format!("{err}").contains("already running"));
}

View file

@ -320,6 +320,7 @@ async fn test_optional_i64_field_with_null_input() -> anyhow::Result<()> {
.unwrap()
.clone(),
),
task: None,
})
.await?;
@ -348,6 +349,7 @@ async fn test_optional_i64_field_with_null_input() -> anyhow::Result<()> {
.unwrap()
.clone(),
),
task: None,
})
.await?;