Merge pull request #1 from CaitieM20/initialCommit
Initial commit populating repo with 11-25-2025 content
This commit is contained in:
commit
d9494a5b83
15 changed files with 4103 additions and 0 deletions
19
.github/workflows/ci.yml
vendored
Normal file
19
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
check-schema:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "24"
|
||||
- run: npm ci
|
||||
- run: npm run check:schema:ts
|
||||
- run: npm run check:schema:json
|
||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
node_modules/
|
||||
package-lock.json
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
.claude/
|
||||
.jj/
|
||||
*.tsbuildinfo
|
||||
dist/
|
||||
schema/**/generated/
|
||||
5
CODE_OF_CONDUCT.md
Normal file
5
CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Code of Conduct
|
||||
|
||||
This repository follows the same Code of Conduct as the main Model Context Protocol project.
|
||||
|
||||
Please see the [MCP Code of Conduct](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/CODE_OF_CONDUCT.md) for full details.
|
||||
16
CONTRIBUTING.md
Normal file
16
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Contributing
|
||||
|
||||
This repository follows the same contributing guidelines as the main Model Context Protocol project.
|
||||
|
||||
Please see the [MCP Contributing Guidelines](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/CONTRIBUTING.md) for full details.
|
||||
|
||||
## Extension-Specific Notes
|
||||
|
||||
- The Tasks extension specification lives in `docs/specification/`
|
||||
- Schema types live in `schema/draft/`
|
||||
- Changes should be discussed in the **Agents Working Group** prior to submission
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions will be licensed under the Apache License 2.0.
|
||||
See the [LICENSE](LICENSE) file for details.
|
||||
191
LICENSE
Normal file
191
LICENSE
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to the Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by the Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding any notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2025 The Model Context Protocol Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
21
MAINTAINERS.md
Normal file
21
MAINTAINERS.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Tasks Extension Maintainers
|
||||
|
||||
This document lists the maintainers of the MCP Tasks experimental extension.
|
||||
|
||||
This extension is maintained by the **Agents Working Group**.
|
||||
|
||||
## Extension Maintainers
|
||||
|
||||
- [Peter Alexander](https://github.com/pja-ant)
|
||||
- [Luca Chang](https://github.com/LucaButBoring)
|
||||
- [Caitie McCaffrey](https://github.com/CaitieM20)
|
||||
|
||||
## Sponsor
|
||||
|
||||
- [Caitie McCaffrey](https://github.com/CaitieM20)
|
||||
|
||||
## About This Document
|
||||
|
||||
This document is updated by the extension maintainers and reflects the current
|
||||
maintainer structure. For more information about MCP governance, see the
|
||||
[governance documentation](https://modelcontextprotocol.io/community/governance).
|
||||
BIN
README.md
BIN
README.md
Binary file not shown.
14
SECURITY.md
Normal file
14
SECURITY.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Security Policy
|
||||
|
||||
This repository follows the same security policy as the main Model Context Protocol project.
|
||||
|
||||
Please see the [MCP Security Policy](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/SECURITY.md) for full details on reporting security issues, the trust model, and what is considered in scope.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
If you discover a security vulnerability in this repository, please report it through
|
||||
the [GitHub Security Advisory process](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability)
|
||||
for this repository.
|
||||
|
||||
Please **do not** report security vulnerabilities through public GitHub issues, discussions,
|
||||
or pull requests.
|
||||
905
docs/specification/draft/tasks.mdx
Normal file
905
docs/specification/draft/tasks.mdx
Normal file
|
|
@ -0,0 +1,905 @@
|
|||
---
|
||||
title: Tasks
|
||||
---
|
||||
|
||||
<div id="enable-section-numbers" />
|
||||
|
||||
<Note>
|
||||
|
||||
**This document is part of the experimental Tasks extension (`io.modelcontextprotocol/tasks`).**
|
||||
|
||||
Tasks were introduced in version 2025-11-25 of the [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25)
|
||||
and are currently considered **experimental**. The design and behavior of tasks may evolve in future versions.
|
||||
|
||||
This content is extracted from the core specification to support the extension's incubation.
|
||||
For the core protocol specification, see [modelcontextprotocol.io](https://modelcontextprotocol.io/specification/2025-11-25).
|
||||
|
||||
</Note>
|
||||
|
||||
The Model Context Protocol (MCP) allows requestors — which can be either clients or servers, depending on the direction of communication — to augment their requests with **tasks**. Tasks are durable state machines that carry information about the underlying execution state of the request they wrap, and are intended for requestor polling and deferred result retrieval. Each task is uniquely identifiable by a receiver-generated **task ID**.
|
||||
|
||||
Tasks are useful for representing expensive computations and batch processing requests, and integrate seamlessly with external job APIs.
|
||||
|
||||
## Definitions
|
||||
|
||||
Tasks represent parties as either "requestors" or "receivers," defined as follows:
|
||||
|
||||
- **Requestor:** The sender of a task-augmented request. This can be the client or the server — either can create tasks.
|
||||
- **Receiver:** The receiver of a task-augmented request, and the entity executing the task. This can be the client or the server — either can receive and execute tasks.
|
||||
|
||||
## User Interaction Model
|
||||
|
||||
Tasks are designed to be **requestor-driven** - requestors are responsible for augmenting requests with tasks and for polling for the results of those tasks; meanwhile, receivers tightly control which requests (if any) support task-based execution and manages the lifecycles of those tasks.
|
||||
|
||||
This requestor-driven approach ensures deterministic response handling and enables sophisticated patterns such as dispatching concurrent requests, which only the requestor has sufficient context to orchestrate.
|
||||
|
||||
Implementations are free to expose tasks through any interface pattern that suits their needs — the protocol itself does not mandate any specific user interaction model.
|
||||
|
||||
## Capabilities
|
||||
|
||||
Servers and clients that support task-augmented requests **MUST** declare a `tasks` capability during initialization. The `tasks` capability is structured by request category, with boolean properties indicating which specific request types support task augmentation.
|
||||
|
||||
### Server Capabilities
|
||||
|
||||
Servers declare if they support tasks, and if so, which server-side requests can be augmented with tasks.
|
||||
|
||||
| Capability | Description |
|
||||
| --------------------------- | ---------------------------------------------------- |
|
||||
| `tasks.list` | Server supports the `tasks/list` operation |
|
||||
| `tasks.cancel` | Server supports the `tasks/cancel` operation |
|
||||
| `tasks.requests.tools.call` | Server supports task-augmented `tools/call` requests |
|
||||
|
||||
```json
|
||||
{
|
||||
"capabilities": {
|
||||
"tasks": {
|
||||
"list": {},
|
||||
"cancel": {},
|
||||
"requests": {
|
||||
"tools": {
|
||||
"call": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Client Capabilities
|
||||
|
||||
Clients declare if they support tasks, and if so, which client-side requests can be augmented with tasks.
|
||||
|
||||
| Capability | Description |
|
||||
| --------------------------------------- | ---------------------------------------------------------------- |
|
||||
| `tasks.list` | Client supports the `tasks/list` operation |
|
||||
| `tasks.cancel` | Client supports the `tasks/cancel` operation |
|
||||
| `tasks.requests.sampling.createMessage` | Client supports task-augmented `sampling/createMessage` requests |
|
||||
| `tasks.requests.elicitation.create` | Client supports task-augmented `elicitation/create` requests |
|
||||
|
||||
```json
|
||||
{
|
||||
"capabilities": {
|
||||
"tasks": {
|
||||
"list": {},
|
||||
"cancel": {},
|
||||
"requests": {
|
||||
"sampling": {
|
||||
"createMessage": {}
|
||||
},
|
||||
"elicitation": {
|
||||
"create": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Capability Negotiation
|
||||
|
||||
During the initialization phase, both parties exchange their `tasks` capabilities to establish which operations support task-based execution. Requestors **SHOULD** only augment requests with a task if the corresponding capability has been declared by the receiver.
|
||||
|
||||
For example, if a server's capabilities include `tasks.requests.tools.call: {}`, then clients may augment `tools/call` requests with a task. If a client's capabilities include `tasks.requests.sampling.createMessage: {}`, then servers may augment `sampling/createMessage` requests with a task.
|
||||
|
||||
If `capabilities.tasks` is not defined, the peer **SHOULD NOT** attempt to create tasks during requests.
|
||||
|
||||
The set of capabilities in `capabilities.tasks.requests` is exhaustive. If a request type is not present, it does not support task-augmentation.
|
||||
|
||||
`capabilities.tasks.list` controls if the `tasks/list` operation is supported by the party.
|
||||
|
||||
`capabilities.tasks.cancel` controls if the `tasks/cancel` operation is supported by the party.
|
||||
|
||||
### Tool-Level Negotiation
|
||||
|
||||
Tool calls are given special consideration for the purpose of task augmentation. In the result of `tools/list`, tools declare support for tasks via `execution.taskSupport`, which if present can have a value of `"required"`, `"optional"`, or `"forbidden"`.
|
||||
|
||||
This is to be interpreted as a fine-grained layer in addition to capabilities, following these rules:
|
||||
|
||||
1. If a server's capabilities do not include `tasks.requests.tools.call`, then clients **MUST NOT** attempt to use task augmentation on that server's tools, regardless of the `execution.taskSupport` value.
|
||||
1. If a server's capabilities include `tasks.requests.tools.call`, then clients consider the value of `execution.taskSupport`, and handle it accordingly:
|
||||
1. If `execution.taskSupport` is not present or `"forbidden"`, clients **MUST NOT** attempt to invoke the tool as a task. Servers **SHOULD** return a `-32601` (Method not found) error if a client attempts to do so. This is the default behavior.
|
||||
1. If `execution.taskSupport` is `"optional"`, clients **MAY** invoke the tool as a task or as a normal request.
|
||||
1. If `execution.taskSupport` is `"required"`, clients **MUST** invoke the tool as a task. Servers **MUST** return a `-32601` (Method not found) error if a client does not attempt to do so.
|
||||
|
||||
## Protocol Messages
|
||||
|
||||
### Creating Tasks
|
||||
|
||||
Task-augmented requests follow a two-phase response pattern that differs from normal requests:
|
||||
|
||||
- **Normal requests**: The server processes the request and returns the actual operation result directly.
|
||||
- **Task-augmented requests**: The server accepts the request and immediately returns a `CreateTaskResult` containing task data. The actual operation result becomes available later through `tasks/result` after the task completes.
|
||||
|
||||
To create a task, requestors send a request with the `task` field included in the request params. Requestors **MAY** include a `ttl` value indicating the desired task lifetime duration (in milliseconds) since its creation.
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "get_weather",
|
||||
"arguments": {
|
||||
"city": "New York"
|
||||
},
|
||||
"task": {
|
||||
"ttl": 60000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"task": {
|
||||
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
|
||||
"status": "working",
|
||||
"statusMessage": "The operation is now in progress.",
|
||||
"createdAt": "2025-11-25T10:30:00Z",
|
||||
"lastUpdatedAt": "2025-11-25T10:40:00Z",
|
||||
"ttl": 60000,
|
||||
"pollInterval": 5000
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When a receiver accepts a task-augmented request, it returns a [`CreateTaskResult`](https://modelcontextprotocol.io/specification/2025-11-25/schema#createtaskresult) containing task data. The response does not include the actual operation result. The actual result (e.g., tool result for `tools/call`) becomes available only through `tasks/result` after the task completes.
|
||||
|
||||
<Note>
|
||||
|
||||
When a task is created in response to a `tools/call` request, host applications may wish to return control to the model while the task is executing. This allows the model to continue processing other requests or perform additional work while waiting for the task to complete.
|
||||
|
||||
To support this pattern, servers can provide an optional `io.modelcontextprotocol/model-immediate-response` key in the `_meta` field of the `CreateTaskResult`. The value of this key should be a string intended to be passed as an immediate tool result to the model.
|
||||
If a server does not provide this field, the host application can fall back to its own predefined message.
|
||||
|
||||
This guidance is non-binding and is provisional logic intended to account for the specific use case. This behavior may be formalized or modified as part of `CreateTaskResult` in future protocol versions.
|
||||
|
||||
</Note>
|
||||
|
||||
### Getting Tasks
|
||||
|
||||
<Note>
|
||||
|
||||
In the Streamable HTTP (SSE) transport, clients **MAY** disconnect from an SSE stream opened by the server in response to a `tasks/get` request at any time.
|
||||
|
||||
While this note is not prescriptive regarding the specific usage of SSE streams, all implementations **MUST** continue to comply with the existing [Streamable HTTP transport specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server).
|
||||
|
||||
</Note>
|
||||
|
||||
Requestors poll for task completion by sending [`tasks/get`](https://modelcontextprotocol.io/specification/2025-11-25/schema#tasks%2Fget)requests.
|
||||
Requestors **SHOULD** respect the `pollInterval` provided in responses when determining polling frequency.
|
||||
|
||||
Requestors **SHOULD** continue polling until the task reaches a terminal status (`completed`, `failed`, or `cancelled`), or until encountering the [`input_required`](#input-required-status) status. Note that invoking `tasks/result` does not imply that the requestor needs to stop polling - requestors **SHOULD** continue polling the task status via `tasks/get` if they are not actively waiting for `tasks/result` to complete.
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"method": "tasks/get",
|
||||
"params": {
|
||||
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"result": {
|
||||
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
|
||||
"status": "working",
|
||||
"statusMessage": "The operation is now in progress.",
|
||||
"createdAt": "2025-11-25T10:30:00Z",
|
||||
"lastUpdatedAt": "2025-11-25T10:40:00Z",
|
||||
"ttl": 30000,
|
||||
"pollInterval": 5000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieving Task Results
|
||||
|
||||
<Note>
|
||||
|
||||
In the Streamable HTTP (SSE) transport, clients **MAY** disconnect from an SSE stream opened by the server in response to a `tasks/result` request at any time.
|
||||
|
||||
While this note is not prescriptive regarding the specific usage of SSE streams, all implementations **MUST** continue to comply with the existing [Streamable HTTP transport specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server).
|
||||
|
||||
</Note>
|
||||
|
||||
After a task completes the operation result is retrieved via [`tasks/result`](https://modelcontextprotocol.io/specification/2025-11-25/schema#tasks%2Fresult). This is distinct from the initial `CreateTaskResult` response, which contains only task data. The result structure matches the original request type (e.g., `CallToolResult` for `tools/call`).
|
||||
|
||||
To retrieve the result of a completed task, requestors can send a `tasks/result` request:
|
||||
|
||||
While `tasks/result` blocks until the task reaches a terminal status, requestors can continue polling via `tasks/get` in parallel if they are not actively blocked waiting for the result, such as if their previous `tasks/result` request failed or was cancelled. This allows requestors to monitor status changes or display progress updates while the task executes, even after invoking `tasks/result`.
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"method": "tasks/result",
|
||||
"params": {
|
||||
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
|
||||
}
|
||||
],
|
||||
"isError": false,
|
||||
"_meta": {
|
||||
"io.modelcontextprotocol/related-task": {
|
||||
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Task Status Notification
|
||||
|
||||
When a task status changes, receivers **MAY** send a [`notifications/tasks/status`](https://modelcontextprotocol.io/specification/2025-11-25/schema#notifications%2Ftasks%2Fstatus) notification to inform the requestor of the change. This notification includes the full task state.
|
||||
|
||||
**Notification:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/tasks/status",
|
||||
"params": {
|
||||
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
|
||||
"status": "completed",
|
||||
"createdAt": "2025-11-25T10:30:00Z",
|
||||
"lastUpdatedAt": "2025-11-25T10:50:00Z",
|
||||
"ttl": 60000,
|
||||
"pollInterval": 5000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The notification includes the full [`Task`](https://modelcontextprotocol.io/specification/2025-11-25/schema#task) object, including the updated `status` and `statusMessage` (if present). This allows requestors to access the complete task state without making an additional `tasks/get` request.
|
||||
|
||||
Requestors **MUST NOT** rely on receiving this notifications, as it is optional. Receivers are not required to send status notifications and may choose to only send them for certain status transitions. Requestors **SHOULD** continue to poll via `tasks/get` to ensure they receive status updates.
|
||||
|
||||
### Listing Tasks
|
||||
|
||||
To retrieve a list of tasks, requestors can send a [`tasks/list`](https://modelcontextprotocol.io/specification/2025-11-25/schema#tasks%2Flist) request. This operation supports pagination.
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 5,
|
||||
"method": "tasks/list",
|
||||
"params": {
|
||||
"cursor": "optional-cursor-value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 5,
|
||||
"result": {
|
||||
"tasks": [
|
||||
{
|
||||
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
|
||||
"status": "working",
|
||||
"createdAt": "2025-11-25T10:30:00Z",
|
||||
"lastUpdatedAt": "2025-11-25T10:40:00Z",
|
||||
"ttl": 30000,
|
||||
"pollInterval": 5000
|
||||
},
|
||||
{
|
||||
"taskId": "abc123-def456-ghi789",
|
||||
"status": "completed",
|
||||
"createdAt": "2025-11-25T09:15:00Z",
|
||||
"lastUpdatedAt": "2025-11-25T10:40:00Z",
|
||||
"ttl": 60000
|
||||
}
|
||||
],
|
||||
"nextCursor": "next-page-cursor"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cancelling Tasks
|
||||
|
||||
To explicitly cancel a task, requestors can send a [`tasks/cancel`](https://modelcontextprotocol.io/specification/2025-11-25/schema#tasks%2Fcancel) request.
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 6,
|
||||
"method": "tasks/cancel",
|
||||
"params": {
|
||||
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 6,
|
||||
"result": {
|
||||
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
|
||||
"status": "cancelled",
|
||||
"statusMessage": "The task was cancelled by request.",
|
||||
"createdAt": "2025-11-25T10:30:00Z",
|
||||
"lastUpdatedAt": "2025-11-25T10:40:00Z",
|
||||
"ttl": 30000,
|
||||
"pollInterval": 5000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Behavior Requirements
|
||||
|
||||
These requirements apply to all parties that support receiving task-augmented requests.
|
||||
|
||||
### Task Support and Handling
|
||||
|
||||
1. Receivers that do not declare the task capability for a request type **MUST** process requests of that type normally, ignoring any task-augmentation metadata if present.
|
||||
1. Receivers that declare the task capability for a request type **MAY** return an error for non-task-augmented requests, requiring requestors to use task augmentation.
|
||||
|
||||
### Task ID Requirements
|
||||
|
||||
1. Task IDs **MUST** be a string value.
|
||||
1. Task IDs **MUST** be generated by the receiver when creating a task.
|
||||
1. Task IDs **MUST** be unique among all tasks controlled by the receiver.
|
||||
|
||||
### Task Status Lifecycle
|
||||
|
||||
1. Tasks **MUST** begin in the `working` status when created.
|
||||
1. Receivers **MUST** only transition tasks through the following valid paths:
|
||||
1. From `working`: may move to `input_required`, `completed`, `failed`, or `cancelled`
|
||||
1. From `input_required`: may move to `working`, `completed`, `failed`, or `cancelled`
|
||||
1. Tasks with a `completed`, `failed`, or `cancelled` status are in a terminal state and **MUST NOT** transition to any other status
|
||||
|
||||
**Task Status State Diagram:**
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> working
|
||||
|
||||
working --> input_required
|
||||
working --> terminal
|
||||
|
||||
input_required --> working
|
||||
input_required --> terminal
|
||||
|
||||
terminal --> [*]
|
||||
|
||||
note right of terminal
|
||||
Terminal states:
|
||||
• completed
|
||||
• failed
|
||||
• cancelled
|
||||
end note
|
||||
```
|
||||
|
||||
### Input Required Status
|
||||
|
||||
<Note>
|
||||
|
||||
With the Streamable HTTP (SSE) transport, servers often close SSE streams after delivering a response message, which can lead to ambiguity regarding the stream used for subsequent task messages.
|
||||
|
||||
Servers can handle this by enqueueing messages to the client to side-channel task-related messages alongside other responses.
|
||||
|
||||
Servers have flexibility in how they manage SSE streams during task polling and result retrieval, and clients **SHOULD** expect messages to be delivered on any SSE stream, including the HTTP GET stream.
|
||||
One possible approach is maintaining an SSE stream on `tasks/result` (see notes on the `input_required` status).
|
||||
Where possible, servers **SHOULD NOT** upgrade to an SSE stream in response to a `tasks/get` request, as the client has indicated it wishes to poll for a result.
|
||||
|
||||
While this note is not prescriptive regarding the specific usage of SSE streams, all implementations **MUST** continue to comply with the existing [Streamable HTTP transport specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#sending-messages-to-the-server).
|
||||
|
||||
</Note>
|
||||
|
||||
1. When the task receiver has messagesfor the requestor that are necessary to complete the task, the receiver **SHOULD** move the task to the `input_required` status.
|
||||
1. The receiver **MUST** include the `io.modelcontextprotocol/related-task` metadata in the request to associate it with the task.
|
||||
1. When the requestor encounters the `input_required` status, it **SHOULD** preemptively call `tasks/result`.
|
||||
1. When the receiver receives all required input, the task **SHOULD** transition out of `input_required` status (typically back to `working`).
|
||||
|
||||
### TTL and Resource Management
|
||||
|
||||
1. Receivers **MUST** include a `createdAt` [ISO 8601](https://datatracker.ietf.org/doc/html/rfc3339#section-5)-formatted timestamp in all task responses to indicate when the task was created.
|
||||
1. Receivers **MUST** include a `lastUpdatedAt` [ISO 8601](https://datatracker.ietf.org/doc/html/rfc3339#section-5)-formatted timestamp in all task responses to indicate when the task was last updated.
|
||||
1. Receivers **MAY** override the requested `ttl` duration.
|
||||
1. Receivers **MUST** include the actual `ttl` duration (or `null` for unlimited) in `tasks/get` responses.
|
||||
1. After a task's `ttl` lifetime has elapsed, receivers **MAY** delete the task and its results, regardless of the task status.
|
||||
1. Receivers **MAY** include a `pollInterval` value (in milliseconds) in `tasks/get` responses to suggest polling intervals. Requestors **SHOULD** respect this value when provided.
|
||||
|
||||
### Result Retrieval
|
||||
|
||||
1. Receivers that accept a task-augmented request **MUST** return a `CreateTaskResult` as the response. This result **SHOULD** be returned as soon as possible after accepting the task.
|
||||
1. When a receiver receives a `tasks/result` request for a task in a terminal status (`completed`, `failed`, or `cancelled`), it **MUST** return the final result of the underlying request, whether that is a successful result or a JSON-RPC error.
|
||||
1. When a receiver receives a `tasks/result` request for a task in any other non-terminal status (`working` or `input_required`), it **MUST** block the response until the task reaches a terminal status.
|
||||
1. For tasks in a terminal status, receivers **MUST** return from `tasks/result` exactly what the underlying request would have returned, whether that is a successful result or a JSON-RPC error.
|
||||
|
||||
### Associating Task-Related Messages
|
||||
|
||||
1. All requests, notifications, and responses related to a task **MUST** include the `io.modelcontextprotocol/related-task` key in their `_meta` field, with the value set to an object with a `taskId` matching the associated task ID.
|
||||
1. For example, an elicitation that a task-augmented tool call depends on **MUST** share the same related task ID with that tool call's task.
|
||||
1. For the `tasks/get`, `tasks/result`, and `tasks/cancel` operations, the `taskId` parameter in the request **MUST** be used as the source of truth for identifying the target task. Requestors **SHOULD NOT** include `io.modelcontextprotocol/related-task` metadata in these requests, and receivers **MUST** ignore such metadata if present in favor of the RPC method parameter.
|
||||
Similarly, for the `tasks/get`, `tasks/list`, and `tasks/cancel` operations, receivers **SHOULD NOT** include `io.modelcontextprotocol/related-task` metadata in the result messages, as the `taskId` is already present in the response structure.
|
||||
|
||||
### Task Notifications
|
||||
|
||||
1. Receivers **MAY** send `notifications/tasks/status` notifications when a task's status changes.
|
||||
1. Requestors **MUST NOT** rely on receiving the `notifications/tasks/status` notification, as it is optional.
|
||||
1. When sent, the `notifications/tasks/status` notification **SHOULD NOT** include the `io.modelcontextprotocol/related-task` metadata, as the task ID is already present in the notification parameters.
|
||||
|
||||
### Task Progress Notifications
|
||||
|
||||
Task-augmented requests support progress notifications as defined in the [progress](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/progress) specification. The `progressToken` provided in the initial request remains valid throughout the task lifetime.
|
||||
|
||||
### Task Listing
|
||||
|
||||
1. Receivers **SHOULD** use cursor-based pagination to limit the number of tasks returned in a single response.
|
||||
1. Receivers **MUST** include a `nextCursor` in the response if more tasks are available.
|
||||
1. Requestors **MUST** treat cursors as opaque tokens and not attempt to parse or modify them.
|
||||
1. If a task is retrievable via `tasks/get` for a requestor, it **MUST** be retrievable via `tasks/list` for that requestor.
|
||||
|
||||
### Task Cancellation
|
||||
|
||||
1. Receivers **MUST** reject cancellation requests for tasks already in a terminal status (`completed`, `failed`, or `cancelled`) with error code `-32602` (Invalid params).
|
||||
1. Upon receiving a valid cancellation request, receivers **SHOULD** attempt to stop the task execution and **MUST** transition the task to `cancelled` status before sending the response.
|
||||
1. Once a task is cancelled, it **MUST** remain in `cancelled` status even if execution continues to completion or fails.
|
||||
1. The `tasks/cancel` operation does not define deletion behavior. However, receivers **MAY** delete cancelled tasks at their discretion at any time, including immediately after cancellation or after the task `ttl` expires.
|
||||
1. Requestors **SHOULD NOT** rely on cancelled tasks being retained for any specific duration and should retrieve any needed information before cancelling.
|
||||
|
||||
## Message Flow
|
||||
|
||||
### Basic Task Lifecycle
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Client (Requestor)
|
||||
participant S as Server (Receiver)
|
||||
Note over C,S: 1. Task Creation
|
||||
C->>S: Request with task field (ttl)
|
||||
activate S
|
||||
S->>C: CreateTaskResult (taskId, status: working, ttl, pollInterval)
|
||||
deactivate S
|
||||
Note over C,S: 2. Task Polling
|
||||
C->>S: tasks/get (taskId)
|
||||
activate S
|
||||
S->>C: working
|
||||
deactivate S
|
||||
Note over S: Task processing continues...
|
||||
C->>S: tasks/get (taskId)
|
||||
activate S
|
||||
S->>C: working
|
||||
deactivate S
|
||||
Note over S: Task completes
|
||||
C->>S: tasks/get (taskId)
|
||||
activate S
|
||||
S->>C: completed
|
||||
deactivate S
|
||||
Note over C,S: 3. Result Retrieval
|
||||
C->>S: tasks/result (taskId)
|
||||
activate S
|
||||
S->>C: Result content
|
||||
deactivate S
|
||||
Note over C,S: 4. Cleanup
|
||||
Note over S: After ttl period from creation, task is cleaned up
|
||||
```
|
||||
|
||||
### Task-Augmented Tool Call With Elicitation
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as User
|
||||
participant LLM
|
||||
participant C as Client (Requestor)
|
||||
participant S as Server (Receiver)
|
||||
|
||||
Note over LLM,C: LLM initiates request
|
||||
LLM->>C: Request operation
|
||||
|
||||
Note over C,S: Client augments with task
|
||||
C->>S: tools/call (ttl: 3600000)
|
||||
activate S
|
||||
S->>C: CreateTaskResult (task-123, status: working)
|
||||
deactivate S
|
||||
|
||||
Note over LLM,C: Client continues processing other requests<br/>while task executes in background
|
||||
LLM->>C: Request other operation
|
||||
C->>LLM: Other operation result
|
||||
|
||||
Note over C,S: Client polls for status
|
||||
C->>S: tasks/get (task-123)
|
||||
activate S
|
||||
S->>C: working
|
||||
deactivate S
|
||||
|
||||
Note over S: Server needs information from client<br/>Task moves to input_required
|
||||
|
||||
Note over C,S: Client polls and discovers input_required
|
||||
C->>S: tasks/get (task-123)
|
||||
activate S
|
||||
S->>C: input_required
|
||||
deactivate S
|
||||
|
||||
Note over C,S: Client opens result stream
|
||||
C->>S: tasks/result (task-123)
|
||||
activate S
|
||||
S->>C: elicitation/create (related-task: task-123)
|
||||
activate C
|
||||
C->>U: Prompt user for input
|
||||
U->>C: Provide information
|
||||
C->>S: elicitation response (related-task: task-123)
|
||||
deactivate C
|
||||
deactivate S
|
||||
|
||||
Note over C,S: Client closes result stream and resumes polling
|
||||
|
||||
Note over S: Task continues processing...<br/>Task moves back to working
|
||||
|
||||
C->>S: tasks/get (task-123)
|
||||
activate S
|
||||
S->>C: working
|
||||
deactivate S
|
||||
|
||||
Note over S: Task completes
|
||||
|
||||
Note over C,S: Client polls and discovers completion
|
||||
C->>S: tasks/get (task-123)
|
||||
activate S
|
||||
S->>C: completed
|
||||
deactivate S
|
||||
|
||||
Note over C,S: Client retrieves final results
|
||||
C->>S: tasks/result (task-123)
|
||||
activate S
|
||||
S->>C: Result content
|
||||
deactivate S
|
||||
C->>LLM: Process result
|
||||
|
||||
Note over S: Results retained for ttl period from creation
|
||||
```
|
||||
|
||||
### Task-Augmented Sampling Request
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as User
|
||||
participant LLM
|
||||
participant C as Client (Receiver)
|
||||
participant S as Server (Requestor)
|
||||
|
||||
Note over S: Server decides to initiate request
|
||||
|
||||
Note over S,C: Server requests client operation (task-augmented)
|
||||
S->>C: sampling/createMessage (ttl: 3600000)
|
||||
activate C
|
||||
C->>S: CreateTaskResult (request-789, status: working)
|
||||
deactivate C
|
||||
|
||||
Note over S: Server continues processing<br/>while waiting for result
|
||||
|
||||
Note over S,C: Server polls for result
|
||||
S->>C: tasks/get (request-789)
|
||||
activate C
|
||||
C->>S: working
|
||||
deactivate C
|
||||
|
||||
Note over C,U: Client may present request to user
|
||||
C->>U: Review request
|
||||
U->>C: Approve request
|
||||
|
||||
Note over C,LLM: Client may involve LLM
|
||||
C->>LLM: Request completion
|
||||
LLM->>C: Return completion
|
||||
|
||||
Note over C,U: Client may present result to user
|
||||
C->>U: Review result
|
||||
U->>C: Approve result
|
||||
|
||||
Note over S,C: Server polls and discovers completion
|
||||
S->>C: tasks/get (request-789)
|
||||
activate C
|
||||
C->>S: completed
|
||||
deactivate C
|
||||
|
||||
Note over S,C: Server retrieves result
|
||||
S->>C: tasks/result (request-789)
|
||||
activate C
|
||||
C->>S: Result content
|
||||
deactivate C
|
||||
|
||||
Note over S: Server continues processing
|
||||
|
||||
Note over C: Results retained for ttl period from creation
|
||||
```
|
||||
|
||||
### Task Cancellation Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant C as Client (Requestor)
|
||||
participant S as Server (Receiver)
|
||||
|
||||
Note over C,S: 1. Task Creation
|
||||
C->>S: tools/call (request ID: 42, ttl: 60000)
|
||||
activate S
|
||||
S->>C: CreateTaskResult (task-123, status: working)
|
||||
deactivate S
|
||||
|
||||
Note over C,S: 2. Task Processing
|
||||
C->>S: tasks/get (task-123)
|
||||
activate S
|
||||
S->>C: working
|
||||
deactivate S
|
||||
|
||||
Note over C,S: 3. Client Cancellation
|
||||
Note over C: User requests cancellation
|
||||
C->>S: tasks/cancel (taskId: task-123)
|
||||
activate S
|
||||
|
||||
Note over S: Server stops execution (best effort)
|
||||
Note over S: Task moves to cancelled status
|
||||
|
||||
S->>C: Task (status: cancelled)
|
||||
deactivate S
|
||||
|
||||
Note over C: Client receives confirmation
|
||||
|
||||
Note over S: Server may delete task at its discretion
|
||||
```
|
||||
|
||||
## Data Types
|
||||
|
||||
### Task
|
||||
|
||||
A task represents the execution state of a request. The task state includes:
|
||||
|
||||
- `taskId`: Unique identifier for the task
|
||||
- `status`: Current state of the task execution
|
||||
- `statusMessage`: Optional human-readable message describing the current state (can be present for any status, including error details for failed tasks)
|
||||
- `createdAt`: ISO 8601 timestamp when the task was created
|
||||
- `ttl`: Time in milliseconds from creation before task may be deleted
|
||||
- `pollInterval`: Suggested time in milliseconds between status checks
|
||||
- `lastUpdatedAt`: ISO 8601 timestamp when the task status was last updated
|
||||
|
||||
### Task Status
|
||||
|
||||
Tasks can be in one of the following states:
|
||||
|
||||
- `working`: The request is currently being processed.
|
||||
- `input_required`: The receiver needs input from the requestor. The requestor should call `tasks/result` to receive input requests, even though the task has not reached a terminal state.
|
||||
- `completed`: The request completed successfully and results are available.
|
||||
- `failed`: The associated request did not complete successfully. For tool calls specifically, this includes cases where the tool call result has `isError` set to true.
|
||||
- `cancelled`: The request was cancelled before completion.
|
||||
|
||||
### Task Parameters
|
||||
|
||||
When augmenting a request with task execution, the `task` field is included in the request parameters:
|
||||
|
||||
```json
|
||||
{
|
||||
"task": {
|
||||
"ttl": 60000
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
- `ttl` (number, optional): Requested duration in milliseconds to retain task from creation
|
||||
|
||||
### Related Task Metadata
|
||||
|
||||
All requests, responses, and notifications associated with a task **MUST** include the `io.modelcontextprotocol/related-task` key in `_meta`:
|
||||
|
||||
```json
|
||||
{
|
||||
"io.modelcontextprotocol/related-task": {
|
||||
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This associates messages with their originating task across the entire request lifecycle.
|
||||
|
||||
For the `tasks/get`, `tasks/list`, and `tasks/cancel` operations, requestors and receivers **SHOULD NOT** include this metadata in their messages, as the `taskId` is already present in the message structure.
|
||||
The `tasks/result` operation **MUST** include this metadata in its response, as the result structure itself does not contain the task ID.
|
||||
|
||||
## Error Handling
|
||||
|
||||
Tasks use two error reporting mechanisms:
|
||||
|
||||
1. **Protocol Errors**: Standard JSON-RPC errors for protocol-level issues
|
||||
1. **Task Execution Errors**: Errors in the underlying request execution, reported through task status
|
||||
|
||||
### Protocol Errors
|
||||
|
||||
Receivers **MUST** return standard JSON-RPC errors for the following protocol error cases:
|
||||
|
||||
- Invalid or nonexistent `taskId` in `tasks/get`, `tasks/result`, or `tasks/cancel`: `-32602` (Invalid params)
|
||||
- Invalid or nonexistent cursor in `tasks/list`: `-32602` (Invalid params)
|
||||
- Attempt to cancel a task already in a terminal status: `-32602` (Invalid params)
|
||||
- Internal errors: `-32603` (Internal error)
|
||||
|
||||
Additionally, receivers **MAY** return the following errors:
|
||||
|
||||
- Non-task-augmented request when receiver requires task augmentation for that request type: `-32600` (Invalid request)
|
||||
|
||||
Receivers **SHOULD** provide informative error messages to describe the cause of errors.
|
||||
|
||||
**Example: Task augmentation required**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"error": {
|
||||
"code": -32600,
|
||||
"message": "Task augmentation required for tools/call requests"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example: Task not found**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 70,
|
||||
"error": {
|
||||
"code": -32602,
|
||||
"message": "Failed to retrieve task: Task not found"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Example: Task expired**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 71,
|
||||
"error": {
|
||||
"code": -32602,
|
||||
"message": "Failed to retrieve task: Task has expired"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Note>
|
||||
|
||||
Receivers are not required to retain tasks indefinitely. It is compliant behavior for a receiver to return an error stating the task cannot be found if it has purged an expired task.
|
||||
|
||||
</Note>
|
||||
|
||||
**Example: Task cancellation rejected (already terminal)**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 74,
|
||||
"error": {
|
||||
"code": -32602,
|
||||
"message": "Cannot cancel task: already in terminal status 'completed'"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Task Execution Errors
|
||||
|
||||
When the underlying request does not complete successfully, the task moves to the `failed` status. This includes JSON-RPC protocol errors during request execution, or for tool calls specifically, when the tool result has `isError` set to true. The `tasks/get` response **SHOULD** include a `statusMessage` field with diagnostic information about the failure.
|
||||
|
||||
**Example: Task with execution error**
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"result": {
|
||||
"taskId": "786512e2-9e0d-44bd-8f29-789f820fe840",
|
||||
"status": "failed",
|
||||
"createdAt": "2025-11-25T10:30:00Z",
|
||||
"lastUpdatedAt": "2025-11-25T10:40:00Z",
|
||||
"ttl": 30000,
|
||||
"statusMessage": "Tool execution failed: API rate limit exceeded"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For tasks that wrap tool call requests, when the tool result has `isError` set to `true`, the task should reach `failed` status.
|
||||
|
||||
The `tasks/result` endpoint returns exactly what the underlying request would have returned:
|
||||
|
||||
- If the underlying request resulted in a JSON-RPC error, `tasks/result` **MUST** return that same JSON-RPC error.
|
||||
- If the request completed with a JSON-RPC response, `tasks/result` **MUST** return a successful JSON-RPC response containing that result.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Task Isolation and Access Control
|
||||
|
||||
Task IDs are the primary mechanism for accessing task state and results. Without proper access controls, any party that can guess or obtain a task ID could potentially access sensitive information or manipulate tasks they did not create.
|
||||
|
||||
When an authorization context is provided, receivers **MUST** bind tasks to said context.
|
||||
|
||||
Context-binding is not practical for all applications. Some MCP servers operate in environments without authorization, such as single-user tools, or use transports that don't support authorization.
|
||||
In these scenarios, receivers **SHOULD** document this limitation clearly, as task results may be accessible to any requestor that can guess the task ID.
|
||||
If context-binding is unavailable, receivers **MUST** generate cryptographically secure task IDs with enough entropy to prevent guessing and should consider using shorter TTL durations to reduce the exposure window.
|
||||
Furthermore, receivers that cannot identify requestors **SHOULD NOT** declare the `tasks.list` capability, as listing tasks would expose task metadata to any requestor regardless of task ID entropy.
|
||||
|
||||
If context-binding is available, receivers **MUST** reject `tasks/get`, `tasks/result`, and `tasks/cancel` requests for tasks that do not belong to the same authorization context as the requestor. For `tasks/list` requests, receivers **MUST** ensure the returned task list includes only tasks associated with the requestor's authorization context.
|
||||
|
||||
Additionally, receivers **SHOULD** implement rate limiting on task operations to prevent denial-of-service and enumeration attacks.
|
||||
|
||||
### Resource Management
|
||||
|
||||
1. Receivers **SHOULD**:
|
||||
1. Enforce limits on concurrent tasks per requestor
|
||||
1. Enforce maximum `ttl` durations to prevent indefinite resource retention
|
||||
1. Clean up expired tasks promptly to free resources
|
||||
1. Document maximum supported `ttl` duration
|
||||
1. Document maximum concurrent tasks per requestor
|
||||
1. Implement monitoring and alerting for resource usage
|
||||
|
||||
### Audit and Logging
|
||||
|
||||
1. Receivers **SHOULD**:
|
||||
1. Log task creation, completion, and retrieval events for audit purposes
|
||||
1. Include auth context in logs when available
|
||||
1. Monitor for suspicious patterns (e.g., many failed task lookups, excessive polling)
|
||||
1. Requestors **SHOULD**:
|
||||
1. Log task lifecycle events for debugging and audit purposes
|
||||
1. Track task IDs and their associated operations
|
||||
21
package.json
Normal file
21
package.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "@modelcontextprotocol/experimental-ext-tasks",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"description": "MCP Tasks Extension - Experimental extension for task management in the Model Context Protocol",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"generate:schemas": "tsx scripts/generate-schemas.ts",
|
||||
"check:schema": "npm run check:schema:ts && npm run check:schema:json",
|
||||
"check:schema:ts": "tsc --noEmit",
|
||||
"check:schema:json": "tsx scripts/generate-schemas.ts --check"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||
"ts-to-zod": "^5.1.0",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.0.0",
|
||||
"zod": "^4.4.3"
|
||||
}
|
||||
}
|
||||
1134
schema/draft/schema.json
Normal file
1134
schema/draft/schema.json
Normal file
File diff suppressed because it is too large
Load diff
342
schema/draft/schema.ts
Normal file
342
schema/draft/schema.ts
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
/**
|
||||
* MCP Tasks Extension Schema (spec.types.ts)
|
||||
* Extension Identifier: io.modelcontextprotocol/tasks
|
||||
*
|
||||
* This file contains pure TypeScript interface definitions for the MCP Tasks extension.
|
||||
* These types are the source of truth and are used to generate Zod schemas via `ts-to-zod`.
|
||||
*
|
||||
* - Use `@description` JSDoc tags to generate `.describe()` calls on schemas
|
||||
* - Run `npm run generate:schemas` to regenerate schemas from these types
|
||||
*
|
||||
* @see https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks
|
||||
*/
|
||||
|
||||
import type {
|
||||
Cursor,
|
||||
JSONRPCNotification,
|
||||
JSONRPCRequest,
|
||||
NotificationParams,
|
||||
PaginatedRequest,
|
||||
PaginatedResult,
|
||||
ProgressToken,
|
||||
RequestId,
|
||||
RequestParams,
|
||||
Result,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
/* Tasks */
|
||||
|
||||
/**
|
||||
* The status of a task.
|
||||
*
|
||||
* @category `tasks`
|
||||
*/
|
||||
export type TaskStatus =
|
||||
| "working" // The request is currently being processed
|
||||
| "input_required" // The task is waiting for input (e.g., elicitation or sampling)
|
||||
| "completed" // The request completed successfully and results are available
|
||||
| "failed" // The associated request did not complete successfully. For tool calls specifically, this includes cases where the tool call result has `isError` set to true.
|
||||
| "cancelled"; // The request was cancelled before completion
|
||||
|
||||
/**
|
||||
* Metadata for augmenting a request with task execution.
|
||||
* Include this in the `task` field of the request parameters.
|
||||
*
|
||||
* @category `tasks`
|
||||
*/
|
||||
export interface TaskMetadata {
|
||||
/**
|
||||
* Requested duration in milliseconds to retain task from creation.
|
||||
*/
|
||||
ttl?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata for associating messages with a task.
|
||||
* Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`.
|
||||
*
|
||||
* @category `tasks`
|
||||
*/
|
||||
export interface RelatedTaskMetadata {
|
||||
/**
|
||||
* The task identifier this message is associated with.
|
||||
*/
|
||||
taskId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data associated with a task.
|
||||
*
|
||||
* @category `tasks`
|
||||
*/
|
||||
export interface Task {
|
||||
/**
|
||||
* The task identifier.
|
||||
*/
|
||||
taskId: string;
|
||||
|
||||
/**
|
||||
* Current task state.
|
||||
*/
|
||||
status: TaskStatus;
|
||||
|
||||
/**
|
||||
* 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)
|
||||
*/
|
||||
statusMessage?: string;
|
||||
|
||||
/**
|
||||
* ISO 8601 timestamp when the task was created.
|
||||
*/
|
||||
createdAt: string;
|
||||
|
||||
/**
|
||||
* ISO 8601 timestamp when the task was last updated.
|
||||
*/
|
||||
lastUpdatedAt: string;
|
||||
|
||||
/**
|
||||
* Actual retention duration from creation in milliseconds, null for unlimited.
|
||||
* @nullable
|
||||
*/
|
||||
ttl: number | null;
|
||||
|
||||
/**
|
||||
* Suggested polling interval in milliseconds.
|
||||
*/
|
||||
pollInterval?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A response to a task-augmented request.
|
||||
*
|
||||
* @category `tasks`
|
||||
*/
|
||||
export interface CreateTaskResult extends Result {
|
||||
task: Task;
|
||||
}
|
||||
|
||||
/* Task Operations */
|
||||
|
||||
/**
|
||||
* A request to retrieve the state of a task.
|
||||
*
|
||||
* @category `tasks/get`
|
||||
*/
|
||||
export interface GetTaskRequest extends JSONRPCRequest {
|
||||
method: "tasks/get";
|
||||
params: {
|
||||
/**
|
||||
* The task identifier to query.
|
||||
*/
|
||||
taskId: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The response to a tasks/get request.
|
||||
*
|
||||
* @category `tasks/get`
|
||||
*/
|
||||
export type GetTaskResult = Result & Task;
|
||||
|
||||
/**
|
||||
* A request to retrieve the result of a completed task.
|
||||
*
|
||||
* @category `tasks/result`
|
||||
*/
|
||||
export interface GetTaskPayloadRequest extends JSONRPCRequest {
|
||||
method: "tasks/result";
|
||||
params: {
|
||||
/**
|
||||
* The task identifier to retrieve results for.
|
||||
*/
|
||||
taskId: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @category `tasks/result`
|
||||
*/
|
||||
export interface GetTaskPayloadResult extends Result {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to cancel a task.
|
||||
*
|
||||
* @category `tasks/cancel`
|
||||
*/
|
||||
export interface CancelTaskRequest extends JSONRPCRequest {
|
||||
method: "tasks/cancel";
|
||||
params: {
|
||||
/**
|
||||
* The task identifier to cancel.
|
||||
*/
|
||||
taskId: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The response to a tasks/cancel request.
|
||||
*
|
||||
* @category `tasks/cancel`
|
||||
*/
|
||||
export type CancelTaskResult = Result & Task;
|
||||
|
||||
/**
|
||||
* A request to retrieve a list of tasks.
|
||||
*
|
||||
* @category `tasks/list`
|
||||
*/
|
||||
export interface ListTasksRequest extends PaginatedRequest {
|
||||
method: "tasks/list";
|
||||
}
|
||||
|
||||
/**
|
||||
* The response to a tasks/list request.
|
||||
*
|
||||
* @category `tasks/list`
|
||||
*/
|
||||
export interface ListTasksResult extends PaginatedResult {
|
||||
tasks: Task[];
|
||||
}
|
||||
|
||||
/* Task Notifications */
|
||||
|
||||
/**
|
||||
* Parameters for a `notifications/tasks/status` notification.
|
||||
*
|
||||
* @category `notifications/tasks/status`
|
||||
*/
|
||||
export type TaskStatusNotificationParams = NotificationParams &
|
||||
Task & { [key: string]: unknown };
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @category `notifications/tasks/status`
|
||||
*/
|
||||
export interface TaskStatusNotification extends JSONRPCNotification {
|
||||
method: "notifications/tasks/status";
|
||||
params: TaskStatusNotificationParams;
|
||||
}
|
||||
|
||||
/* Task-Augmented Request Params */
|
||||
|
||||
/**
|
||||
* Common params for any task-augmented request.
|
||||
* Extends the base RequestParams to include an optional `task` field.
|
||||
*
|
||||
* @category `tasks`
|
||||
*/
|
||||
export interface TaskAugmentedRequestParams extends RequestParams {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
task?: TaskMetadata;
|
||||
}
|
||||
|
||||
/* Capability Additions */
|
||||
|
||||
/**
|
||||
* Task-related server capabilities.
|
||||
* Include this in the server's `capabilities` object during initialization.
|
||||
*
|
||||
* @category `tasks`
|
||||
*/
|
||||
export interface TaskServerCapabilities {
|
||||
tasks?: {
|
||||
/**
|
||||
* Whether this server supports tasks/list.
|
||||
*/
|
||||
list?: Record<string, never>;
|
||||
/**
|
||||
* Whether this server supports tasks/cancel.
|
||||
*/
|
||||
cancel?: Record<string, never>;
|
||||
/**
|
||||
* Specifies which request types can be augmented with tasks.
|
||||
*/
|
||||
requests?: {
|
||||
/**
|
||||
* Task support for tool-related requests.
|
||||
*/
|
||||
tools?: {
|
||||
/**
|
||||
* Whether the server supports task-augmented tools/call requests.
|
||||
*/
|
||||
call?: Record<string, never>;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Task-related client capabilities.
|
||||
* Include this in the client's `capabilities` object during initialization.
|
||||
*
|
||||
* @category `tasks`
|
||||
*/
|
||||
export interface TaskClientCapabilities {
|
||||
tasks?: {
|
||||
/**
|
||||
* Whether this client supports tasks/list.
|
||||
*/
|
||||
list?: Record<string, never>;
|
||||
/**
|
||||
* Whether this client supports tasks/cancel.
|
||||
*/
|
||||
cancel?: Record<string, never>;
|
||||
/**
|
||||
* Specifies which request types can be augmented with tasks.
|
||||
*/
|
||||
requests?: {
|
||||
/**
|
||||
* Task support for sampling-related requests.
|
||||
*/
|
||||
sampling?: {
|
||||
/**
|
||||
* Whether the client supports task-augmented sampling/createMessage requests.
|
||||
*/
|
||||
createMessage?: Record<string, never>;
|
||||
};
|
||||
/**
|
||||
* Task support for elicitation-related requests.
|
||||
*/
|
||||
elicitation?: {
|
||||
/**
|
||||
* Whether the client supports task-augmented elicitation/create requests.
|
||||
*/
|
||||
create?: Record<string, never>;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/* Tool-Level Task Support */
|
||||
|
||||
/**
|
||||
* Task support declaration for individual tools.
|
||||
* Include this in the tool's definition returned by tools/list.
|
||||
*
|
||||
* - "forbidden": Tool does not support task-augmented execution (default when absent)
|
||||
* - "optional": Tool may support task-augmented execution
|
||||
* - "required": Tool requires task-augmented execution
|
||||
*
|
||||
* @category `tasks`
|
||||
*/
|
||||
export type ToolTaskSupport = "forbidden" | "optional" | "required";
|
||||
329
scripts/generate-schemas.ts
Normal file
329
scripts/generate-schemas.ts
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
/**
|
||||
* Schema Generation Script using ts-to-zod as a library
|
||||
*
|
||||
* This script generates Zod schemas from schema.ts and performs necessary
|
||||
* post-processing for compatibility with this project.
|
||||
*
|
||||
* Follows the same pattern as ext-apps/scripts/generate-schemas.ts.
|
||||
*
|
||||
* ## Post-Processing Steps
|
||||
*
|
||||
* ### 1. Zod Import Path (`"zod"` → `"zod/v4"`)
|
||||
* ts-to-zod generates `import { z } from "zod"`. We rewrite to `import { z } from "zod/v4"`
|
||||
* to match MCP SDK's zod/v4 schemas.
|
||||
*
|
||||
* ### 2. External Type References (`z.any()` → SDK schemas)
|
||||
* ts-to-zod cannot resolve types imported from @modelcontextprotocol/sdk.
|
||||
* We replace z.any() placeholders with actual SDK schema imports.
|
||||
*
|
||||
* ### 3. Index Signatures (`z.record().and()` → `.passthrough()`)
|
||||
* TypeScript index signatures are translated to z.record().and(z.object({...})).
|
||||
* We replace with z.object({...}).passthrough() for compatibility.
|
||||
*
|
||||
* @see https://github.com/fabien0102/ts-to-zod
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
||||
import { dirname, join, relative } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { generate } from "ts-to-zod";
|
||||
import { toJSONSchema, type $ZodType } from "zod/v4/core";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const PROJECT_ROOT = join(__dirname, "..");
|
||||
|
||||
const SCHEMA_DIR = join(PROJECT_ROOT, "schema", "draft");
|
||||
const SPEC_TYPES_FILE = join(SCHEMA_DIR, "schema.ts");
|
||||
const GENERATED_DIR = join(SCHEMA_DIR, "generated");
|
||||
const SCHEMA_OUTPUT_FILE = join(GENERATED_DIR, "schema.ts");
|
||||
const JSON_SCHEMA_OUTPUT_FILE = join(SCHEMA_DIR, "schema.json");
|
||||
|
||||
/**
|
||||
* Mapping from ts-to-zod generated placeholder names to actual SDK schema exports.
|
||||
* Only includes types that have matching SDK exports.
|
||||
*/
|
||||
const EXTERNAL_TYPE_SCHEMAS: string[] = [
|
||||
"JSONRPCRequestSchema",
|
||||
"JSONRPCNotificationSchema",
|
||||
"PaginatedRequestSchema",
|
||||
"PaginatedResultSchema",
|
||||
"ResultSchema",
|
||||
"RequestIdSchema",
|
||||
"ProgressTokenSchema",
|
||||
"CursorSchema",
|
||||
];
|
||||
|
||||
/**
|
||||
* External types that don't have direct SDK schema exports.
|
||||
* We replace their z.any() placeholders with local Zod definitions.
|
||||
*/
|
||||
const LOCAL_SCHEMA_REPLACEMENTS: Record<string, string> = {
|
||||
RequestParamsSchema: `z.object({
|
||||
_meta: z.object({ progressToken: ProgressTokenSchema.optional() }).passthrough().optional(),
|
||||
}).passthrough()`,
|
||||
NotificationParamsSchema: `z.object({
|
||||
_meta: z.record(z.string(), z.unknown()).optional(),
|
||||
}).passthrough()`,
|
||||
};
|
||||
|
||||
async function main() {
|
||||
const isCheck = process.argv.includes("--check");
|
||||
|
||||
if (isCheck) {
|
||||
console.log("Checking schemas...");
|
||||
} else {
|
||||
console.log("🔧 Generating Zod schemas from schema.ts...\n");
|
||||
}
|
||||
|
||||
const sourceText = readFileSync(SPEC_TYPES_FILE, "utf-8");
|
||||
|
||||
const result = generate({
|
||||
sourceText,
|
||||
keepComments: true,
|
||||
skipParseJSDoc: false,
|
||||
getSchemaName: (typeName: string) => `${typeName}Schema`,
|
||||
});
|
||||
|
||||
if (result.errors.length > 0) {
|
||||
console.error("❌ Generation errors:");
|
||||
for (const error of result.errors) {
|
||||
console.error(` - ${error}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (result.hasCircularDependencies) {
|
||||
console.warn("⚠️ Warning: Circular dependencies detected in types");
|
||||
}
|
||||
|
||||
// Generate Zod schemas file
|
||||
const relativeImportPath = relative(GENERATED_DIR, SCHEMA_DIR).replace(
|
||||
/\\/g,
|
||||
"/"
|
||||
);
|
||||
let schemasContent = result.getZodSchemasFile(
|
||||
`${relativeImportPath}/schema.js`
|
||||
);
|
||||
schemasContent = postProcess(schemasContent);
|
||||
|
||||
if (isCheck) {
|
||||
// Check mode: compare generated output against existing files
|
||||
let hasChanges = false;
|
||||
|
||||
if (existsSync(SCHEMA_OUTPUT_FILE)) {
|
||||
const existing = readFileSync(SCHEMA_OUTPUT_FILE, "utf-8");
|
||||
if (existing !== schemasContent) {
|
||||
console.error(
|
||||
"❌ Generated Zod schemas are out of date. Run: npm run generate:schemas"
|
||||
);
|
||||
hasChanges = true;
|
||||
} else {
|
||||
console.log(" ✓ Zod schemas are up to date");
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
"❌ Generated Zod schemas file does not exist. Run: npm run generate:schemas"
|
||||
);
|
||||
hasChanges = true;
|
||||
}
|
||||
|
||||
// Write temp file to generate JSON Schema for comparison
|
||||
const tempFile = SCHEMA_OUTPUT_FILE + ".check.ts";
|
||||
writeFileSync(tempFile, schemasContent, "utf-8");
|
||||
try {
|
||||
const jsonSchemaContent = await generateJsonSchemaContent(tempFile);
|
||||
if (existsSync(JSON_SCHEMA_OUTPUT_FILE)) {
|
||||
const existing = readFileSync(JSON_SCHEMA_OUTPUT_FILE, "utf-8");
|
||||
if (existing !== jsonSchemaContent) {
|
||||
console.error(
|
||||
"❌ JSON Schema is out of date. Run: npm run generate:schemas"
|
||||
);
|
||||
hasChanges = true;
|
||||
} else {
|
||||
console.log(" ✓ JSON Schema is up to date");
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
"❌ JSON Schema file does not exist. Run: npm run generate:schemas"
|
||||
);
|
||||
hasChanges = true;
|
||||
}
|
||||
} finally {
|
||||
// Clean up temp file
|
||||
const { unlinkSync } = await import("node:fs");
|
||||
try {
|
||||
unlinkSync(tempFile);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("\nAll schemas are up to date!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure generated directory exists
|
||||
mkdirSync(GENERATED_DIR, { recursive: true });
|
||||
|
||||
// Write Zod schemas
|
||||
writeFileSync(SCHEMA_OUTPUT_FILE, schemasContent, "utf-8");
|
||||
console.log(`✅ Written: ${SCHEMA_OUTPUT_FILE}`);
|
||||
|
||||
// Generate and write JSON Schema
|
||||
const jsonSchemaContent = await generateJsonSchemaContent(SCHEMA_OUTPUT_FILE);
|
||||
writeFileSync(JSON_SCHEMA_OUTPUT_FILE, jsonSchemaContent, "utf-8");
|
||||
console.log(`✅ Written: ${JSON_SCHEMA_OUTPUT_FILE}`);
|
||||
|
||||
console.log("\n🎉 Schema generation complete!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate JSON Schema content from the Zod schemas file.
|
||||
*/
|
||||
async function generateJsonSchemaContent(
|
||||
zodSchemasFile: string
|
||||
): Promise<string> {
|
||||
// Convert to file:// URL for dynamic import (required on Windows)
|
||||
const fileUrl = new URL(`file:///${zodSchemasFile.replace(/\\/g, "/")}`);
|
||||
const schemas = await import(fileUrl.href);
|
||||
|
||||
const jsonSchema: {
|
||||
$schema: string;
|
||||
$id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
$defs: Record<string, unknown>;
|
||||
} = {
|
||||
$schema: "https://json-schema.org/draft/2020-12/schema",
|
||||
$id: "https://modelcontextprotocol.io/ext-tasks/schema.json",
|
||||
title: "MCP Tasks Extension",
|
||||
description:
|
||||
"JSON Schema for MCP Tasks extension protocol messages. Extension Identifier: io.modelcontextprotocol/tasks",
|
||||
$defs: {},
|
||||
};
|
||||
|
||||
for (const [name, schema] of Object.entries(schemas)) {
|
||||
if (
|
||||
name.endsWith("Schema") &&
|
||||
typeof schema === "object" &&
|
||||
schema !== null
|
||||
) {
|
||||
const typeName = name.replace(/Schema$/, "");
|
||||
|
||||
// Skip SDK external types and local replacement types — only include task-specific types
|
||||
if (
|
||||
EXTERNAL_TYPE_SCHEMAS.includes(name) ||
|
||||
name in LOCAL_SCHEMA_REPLACEMENTS
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
jsonSchema.$defs[typeName] = toJSONSchema(schema as $ZodType, {
|
||||
unrepresentable: "any",
|
||||
});
|
||||
} catch (error) {
|
||||
console.warn(`⚠️ Could not convert ${name} to JSON Schema: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(jsonSchema, null, 2) + "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Post-process generated Zod schemas for project compatibility.
|
||||
*/
|
||||
function postProcess(content: string): string {
|
||||
// 1. Rewrite to zod/v4 and add MCP SDK schema imports
|
||||
const sdkImports = EXTERNAL_TYPE_SCHEMAS.join(",\n ");
|
||||
|
||||
content = content.replace(
|
||||
'import { z } from "zod";',
|
||||
`import { z } from "zod/v4";
|
||||
import {
|
||||
${sdkImports},
|
||||
} from "@modelcontextprotocol/sdk/types.js";`
|
||||
);
|
||||
|
||||
// 2. Remove z.any() placeholders for SDK-imported external types
|
||||
for (const schemaName of EXTERNAL_TYPE_SCHEMAS) {
|
||||
content = content.replace(
|
||||
new RegExp(
|
||||
`(?:export )?const ${schemaName} = z\\.any\\(\\);\\n?`,
|
||||
"g"
|
||||
),
|
||||
""
|
||||
);
|
||||
}
|
||||
|
||||
// 3. Replace z.any() placeholders for locally-defined types
|
||||
for (const [schemaName, replacement] of Object.entries(
|
||||
LOCAL_SCHEMA_REPLACEMENTS
|
||||
)) {
|
||||
content = content.replace(
|
||||
new RegExp(
|
||||
`(?:export )?const ${schemaName} = z\\.any\\(\\);`,
|
||||
"g"
|
||||
),
|
||||
`const ${schemaName} = ${replacement};`
|
||||
);
|
||||
}
|
||||
|
||||
// 4. Replace z.record().and(z.object({...})) with z.object({...}).passthrough()
|
||||
content = replaceRecordAndWithPassthrough(content);
|
||||
|
||||
// 5. Add header comment
|
||||
content = content.replace(
|
||||
"// Generated by ts-to-zod",
|
||||
`// Generated by ts-to-zod
|
||||
// Post-processed for Zod v4 compatibility and MCP SDK integration
|
||||
// Run: npm run generate:schemas`
|
||||
);
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace z.record(z.string(), z.unknown()).and(z.object({...})) with z.object({...}).passthrough()
|
||||
* Uses brace-counting to handle nested objects correctly.
|
||||
* passthrough() works in both Zod v3 and v4, allowing extra properties.
|
||||
*/
|
||||
function replaceRecordAndWithPassthrough(content: string): string {
|
||||
const pattern = "z.record(z.string(), z.unknown()).and(z.object({";
|
||||
let result = content;
|
||||
let startIndex = 0;
|
||||
|
||||
while (true) {
|
||||
const matchStart = result.indexOf(pattern, startIndex);
|
||||
if (matchStart === -1) break;
|
||||
|
||||
const objectStart = matchStart + pattern.length;
|
||||
let braceCount = 1;
|
||||
let i = objectStart;
|
||||
|
||||
while (i < result.length && braceCount > 0) {
|
||||
if (result[i] === "{") braceCount++;
|
||||
else if (result[i] === "}") braceCount--;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (result.slice(i, i + 2) === "))") {
|
||||
const objectContent = result.slice(objectStart, i - 1);
|
||||
const replacement = `z.object({${objectContent}}).passthrough()`;
|
||||
result = result.slice(0, matchStart) + replacement + result.slice(i + 2);
|
||||
startIndex = matchStart + replacement.length;
|
||||
} else {
|
||||
startIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("❌ Schema generation failed:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
1084
seps/1686-tasks.md
Normal file
1084
seps/1686-tasks.md
Normal file
File diff suppressed because it is too large
Load diff
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["schema/**/*.ts"],
|
||||
"exclude": ["schema/**/generated/**"]
|
||||
}
|
||||
Loading…
Reference in a new issue