SEP-1686: Tasks (#1645)

This commit is contained in:
Max Isbey 2025-11-28 18:51:58 +00:00 • committed by GitHub
commit c92bb2f7ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,5 +1,6 @@
from collections.abc import Callable
from typing import Annotated, Any, Generic, Literal, TypeAlias, TypeVar
from datetime import datetime
from typing import Annotated, Any, Final, Generic, Literal, TypeAlias, TypeVar
from pydantic import BaseModel, ConfigDict, Field, FileUrl, RootModel
from pydantic.networks import AnyUrl, UrlConstraints
@ -39,6 +40,23 @@ Role = Literal["user", "assistant"]
RequestId = Annotated[int, Field(strict=True)] | str
AnyFunction: TypeAlias = Callable[..., Any]
TaskExecutionMode = Literal["forbidden", "optional", "required"]
TASK_FORBIDDEN: Final[Literal["forbidden"]] = "forbidden"
TASK_OPTIONAL: Final[Literal["optional"]] = "optional"
TASK_REQUIRED: Final[Literal["required"]] = "required"
class TaskMetadata(BaseModel):
"""
Metadata for augmenting a request with task execution.
Include this in the `task` field of the request parameters.
"""
model_config = ConfigDict(extra="allow")
ttl: Annotated[int, Field(strict=True)] | None = None
"""Requested duration in milliseconds to retain task from creation."""
class RequestParams(BaseModel):
class Meta(BaseModel):
@ -52,6 +70,16 @@ class RequestParams(BaseModel):
model_config = ConfigDict(extra="allow")
task: TaskMetadata | None = None
"""
If specified, the caller is requesting task-augmented execution for this request.
The request will return a CreateTaskResult immediately, and the actual result can be
retrieved later via tasks/result.
Task augmentation is subject to capability negotiation - receivers MUST declare support
for task augmentation of specific request types in their capabilities.
"""
meta: Meta | None = Field(alias="_meta", default=None)
@ -321,6 +349,71 @@ class SamplingCapability(BaseModel):
model_config = ConfigDict(extra="allow")
class TasksListCapability(BaseModel):
"""Capability for tasks listing operations."""
model_config = ConfigDict(extra="allow")
class TasksCancelCapability(BaseModel):
"""Capability for tasks cancel operations."""
model_config = ConfigDict(extra="allow")
class TasksCreateMessageCapability(BaseModel):
"""Capability for tasks create messages."""
model_config = ConfigDict(extra="allow")
class TasksSamplingCapability(BaseModel):
"""Capability for tasks sampling operations."""
model_config = ConfigDict(extra="allow")
createMessage: TasksCreateMessageCapability | None = None
class TasksCreateElicitationCapability(BaseModel):
"""Capability for tasks create elicitation operations."""
model_config = ConfigDict(extra="allow")
class TasksElicitationCapability(BaseModel):
"""Capability for tasks elicitation operations."""
model_config = ConfigDict(extra="allow")
create: TasksCreateElicitationCapability | None = None
class ClientTasksRequestsCapability(BaseModel):
"""Capability for tasks requests operations."""
model_config = ConfigDict(extra="allow")
sampling: TasksSamplingCapability | None = None
elicitation: TasksElicitationCapability | None = None
class ClientTasksCapability(BaseModel):
"""Capability for client tasks operations."""
model_config = ConfigDict(extra="allow")
list: TasksListCapability | None = None
"""Whether this client supports tasks/list."""
cancel: TasksCancelCapability | None = None
"""Whether this client supports tasks/cancel."""
requests: ClientTasksRequestsCapability | None = None
"""Specifies which request types can be augmented with tasks."""
class ClientCapabilities(BaseModel):
"""Capabilities a client may support."""
@ -335,6 +428,9 @@ class ClientCapabilities(BaseModel):
"""Present if the client supports elicitation from the user."""
roots: RootsCapability | None = None
"""Present if the client supports listing roots."""
tasks: ClientTasksCapability | None = None
"""Present if the client supports task-augmented requests."""
model_config = ConfigDict(extra="allow")
@ -376,6 +472,37 @@ class CompletionsCapability(BaseModel):
model_config = ConfigDict(extra="allow")
class TasksCallCapability(BaseModel):
"""Capability for tasks call operations."""
model_config = ConfigDict(extra="allow")
class TasksToolsCapability(BaseModel):
"""Capability for tasks tools operations."""
model_config = ConfigDict(extra="allow")
call: TasksCallCapability | None = None
class ServerTasksRequestsCapability(BaseModel):
"""Capability for tasks requests operations."""
model_config = ConfigDict(extra="allow")
tools: TasksToolsCapability | None = None
class ServerTasksCapability(BaseModel):
"""Capability for server tasks operations."""
model_config = ConfigDict(extra="allow")
list: TasksListCapability | None = None
cancel: TasksCancelCapability | None = None
requests: ServerTasksRequestsCapability | None = None
class ServerCapabilities(BaseModel):
"""Capabilities that a server may support."""
@ -391,9 +518,156 @@ class ServerCapabilities(BaseModel):
"""Present if the server offers any tools to call."""
completions: CompletionsCapability | None = None
"""Present if the server offers autocompletion suggestions for prompts and resources."""
tasks: ServerTasksCapability | None = None
"""Present if the server supports task-augmented requests."""
model_config = ConfigDict(extra="allow")
TaskStatus = Literal["working", "input_required", "completed", "failed", "cancelled"]
# Task status constants
TASK_STATUS_WORKING: Final[Literal["working"]] = "working"
TASK_STATUS_INPUT_REQUIRED: Final[Literal["input_required"]] = "input_required"
TASK_STATUS_COMPLETED: Final[Literal["completed"]] = "completed"
TASK_STATUS_FAILED: Final[Literal["failed"]] = "failed"
TASK_STATUS_CANCELLED: Final[Literal["cancelled"]] = "cancelled"
class RelatedTaskMetadata(BaseModel):
"""
Metadata for associating messages with a task.
Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`.
"""
model_config = ConfigDict(extra="allow")
taskId: str
"""The task identifier this message is associated with."""
class Task(BaseModel):
"""Data associated with a task."""
model_config = ConfigDict(extra="allow")
taskId: str
"""The task identifier."""
status: TaskStatus
"""Current task state."""
statusMessage: str | None = None
"""
Optional human-readable message describing the current task state.
This can provide context for any status, including:
- Reasons for "cancelled" status
- Summaries for "completed" status
- Diagnostic information for "failed" status (e.g., error details, what went wrong)
"""
createdAt: datetime # Pydantic will enforce ISO 8601 and re-serialize as a string later
"""ISO 8601 timestamp when the task was created."""
lastUpdatedAt: datetime
"""ISO 8601 timestamp when the task was last updated."""
ttl: Annotated[int, Field(strict=True)] | None
"""Actual retention duration from creation in milliseconds, null for unlimited."""
pollInterval: Annotated[int, Field(strict=True)] | None = None
"""Suggested polling interval in milliseconds."""
class CreateTaskResult(Result):
"""A response to a task-augmented request."""
task: Task
class GetTaskRequestParams(RequestParams):
model_config = ConfigDict(extra="allow")
taskId: str
"""The task identifier to query."""
class GetTaskRequest(Request[GetTaskRequestParams, Literal["tasks/get"]]):
"""A request to retrieve the state of a task."""
method: Literal["tasks/get"] = "tasks/get"
params: GetTaskRequestParams
class GetTaskResult(Result, Task):
"""The response to a tasks/get request."""
class GetTaskPayloadRequestParams(RequestParams):
model_config = ConfigDict(extra="allow")
taskId: str
"""The task identifier to retrieve results for."""
class GetTaskPayloadRequest(Request[GetTaskPayloadRequestParams, Literal["tasks/result"]]):
"""A request to retrieve the result of a completed task."""
method: Literal["tasks/result"] = "tasks/result"
params: GetTaskPayloadRequestParams
class GetTaskPayloadResult(Result):
"""
The response to a tasks/result request.
The structure matches the result type of the original request.
For example, a tools/call task would return the CallToolResult structure.
"""
class CancelTaskRequestParams(RequestParams):
model_config = ConfigDict(extra="allow")
taskId: str
"""The task identifier to cancel."""
class CancelTaskRequest(Request[CancelTaskRequestParams, Literal["tasks/cancel"]]):
"""A request to cancel a task."""
method: Literal["tasks/cancel"] = "tasks/cancel"
params: CancelTaskRequestParams
class CancelTaskResult(Result, Task):
"""The response to a tasks/cancel request."""
class ListTasksRequest(PaginatedRequest[Literal["tasks/list"]]):
"""A request to retrieve a list of tasks."""
method: Literal["tasks/list"] = "tasks/list"
class ListTasksResult(PaginatedResult):
"""The response to a tasks/list request."""
tasks: list[Task]
class TaskStatusNotificationParams(NotificationParams, Task):
"""Parameters for a `notifications/tasks/status` notification."""
class TaskStatusNotification(Notification[TaskStatusNotificationParams, Literal["notifications/tasks/status"]]):
"""
An optional notification from the receiver to the requestor, informing them that a task's status has changed.
Receivers are not required to send these notifications
"""
method: Literal["notifications/tasks/status"] = "notifications/tasks/status"
params: TaskStatusNotificationParams
class InitializeRequestParams(RequestParams):
"""Parameters for the initialize request."""
@ -1011,9 +1285,29 @@ class ToolAnnotations(BaseModel):
of a memory tool is not.
Default: true
"""
model_config = ConfigDict(extra="allow")
class ToolExecution(BaseModel):
"""Execution-related properties for a tool."""
model_config = ConfigDict(extra="allow")
taskSupport: TaskExecutionMode | None = None
"""
Indicates whether this tool supports task-augmented execution.
This allows clients to handle long-running operations through polling
the task system.
- "forbidden": Tool does not support task-augmented execution (default when absent)
- "optional": Tool may support task-augmented execution
- "required": Tool requires task-augmented execution
Default: "forbidden"
"""
class Tool(BaseMetadata):
"""Definition for a tool the client can call."""
@ -1035,6 +1329,9 @@ class Tool(BaseMetadata):
See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields)
for notes on _meta usage.
"""
execution: ToolExecution | None = None
model_config = ConfigDict(extra="allow")
@ -1419,10 +1716,17 @@ class RootsListChangedNotification(
class CancelledNotificationParams(NotificationParams):
"""Parameters for cancellation notifications."""
requestId: RequestId
"""The ID of the request to cancel."""
requestId: RequestId | None = None
"""
The ID of the request to cancel.
This MUST correspond to the ID of a request previously issued in the same direction.
This MUST be provided for cancelling non-task requests.
This MUST NOT be used for cancelling tasks (use the `tasks/cancel` request instead).
"""
reason: str | None = None
"""An optional string describing the reason for the cancellation."""
model_config = ConfigDict(extra="allow")
@ -1462,29 +1766,41 @@ class ElicitCompleteNotification(
params: ElicitCompleteNotificationParams
class ClientRequest(
RootModel[
PingRequest
| InitializeRequest
| CompleteRequest
| SetLevelRequest
| GetPromptRequest
| ListPromptsRequest
| ListResourcesRequest
| ListResourceTemplatesRequest
| ReadResourceRequest
| SubscribeRequest
| UnsubscribeRequest
| CallToolRequest
| ListToolsRequest
]
):
ClientRequestType: TypeAlias = (
PingRequest
| InitializeRequest
| CompleteRequest
| SetLevelRequest
| GetPromptRequest
| ListPromptsRequest
| ListResourcesRequest
| ListResourceTemplatesRequest
| ReadResourceRequest
| SubscribeRequest
| UnsubscribeRequest
| CallToolRequest
| ListToolsRequest
| GetTaskRequest
| GetTaskPayloadRequest
| ListTasksRequest
| CancelTaskRequest
)
class ClientRequest(RootModel[ClientRequestType]):
pass
class ClientNotification(
RootModel[CancelledNotification | ProgressNotification | InitializedNotification | RootsListChangedNotification]
):
ClientNotificationType: TypeAlias = (
CancelledNotification
| ProgressNotification
| InitializedNotification
| RootsListChangedNotification
| TaskStatusNotification
)
class ClientNotification(RootModel[ClientNotificationType]):
pass
@ -1585,41 +1901,74 @@ class ElicitationRequiredErrorData(BaseModel):
model_config = ConfigDict(extra="allow")
class ClientResult(RootModel[EmptyResult | CreateMessageResult | ListRootsResult | ElicitResult]):
ClientResultType: TypeAlias = (
EmptyResult
| CreateMessageResult
| ListRootsResult
| ElicitResult
| GetTaskResult
| GetTaskPayloadResult
| ListTasksResult
| CancelTaskResult
| CreateTaskResult
)
class ClientResult(RootModel[ClientResultType]):
pass
class ServerRequest(RootModel[PingRequest | CreateMessageRequest | ListRootsRequest | ElicitRequest]):
ServerRequestType: TypeAlias = (
PingRequest
| CreateMessageRequest
| ListRootsRequest
| ElicitRequest
| GetTaskRequest
| GetTaskPayloadRequest
| ListTasksRequest
| CancelTaskRequest
)
class ServerRequest(RootModel[ServerRequestType]):
pass
class ServerNotification(
RootModel[
CancelledNotification
| ProgressNotification
| LoggingMessageNotification
| ResourceUpdatedNotification
| ResourceListChangedNotification
| ToolListChangedNotification
| PromptListChangedNotification
| ElicitCompleteNotification
]
):
ServerNotificationType: TypeAlias = (
CancelledNotification
| ProgressNotification
| LoggingMessageNotification
| ResourceUpdatedNotification
| ResourceListChangedNotification
| ToolListChangedNotification
| PromptListChangedNotification
| ElicitCompleteNotification
| TaskStatusNotification
)
class ServerNotification(RootModel[ServerNotificationType]):
pass
class ServerResult(
RootModel[
EmptyResult
| InitializeResult
| CompleteResult
| GetPromptResult
| ListPromptsResult
| ListResourcesResult
| ListResourceTemplatesResult
| ReadResourceResult
| CallToolResult
| ListToolsResult
]
):
ServerResultType: TypeAlias = (
EmptyResult
| InitializeResult
| CompleteResult
| GetPromptResult
| ListPromptsResult
| ListResourcesResult
| ListResourceTemplatesResult
| ReadResourceResult
| CallToolResult
| ListToolsResult
| GetTaskResult
| GetTaskPayloadResult
| ListTasksResult
| CancelTaskResult
| CreateTaskResult
)
class ServerResult(RootModel[ServerResultType]):
pass