Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/busy-rice-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---

tasks - disallow requesting a null TTL
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
4 changes: 2 additions & 2 deletions .github/workflows/conformance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false
- uses: actions/setup-node@v6
Expand All @@ -38,7 +38,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false
- uses: actions/setup-node@v6
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- uses: actions/checkout@v6

- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false
- uses: actions/setup-node@v6
Expand All @@ -41,13 +41,13 @@ jobs:
run: bash scripts/generate-multidoc.sh tmp/docs-combined

- name: Configure Pages
uses: actions/configure-pages@v5
uses: actions/configure-pages@v6

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
uses: actions/upload-pages-artifact@v4
with:
path: tmp/docs-combined

- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
uses: actions/deploy-pages@v5
8 changes: 4 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v6

- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
id: pnpm-install
with:
run_install: false
Expand All @@ -44,7 +44,7 @@ jobs:
- uses: actions/checkout@v6

- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
id: pnpm-install
with:
run_install: false
Expand All @@ -71,7 +71,7 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false
- uses: actions/setup-node@v6
Expand Down Expand Up @@ -108,7 +108,7 @@ jobs:
- uses: actions/checkout@v6

- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
id: pnpm-install
with:
run_install: false
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v6

- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@v6

- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
with:
run_install: false

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-spec-types.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
uses: actions/checkout@v6

- name: Install pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
id: pnpm-install
with:
run_install: false
Expand Down
25 changes: 22 additions & 3 deletions docs/migration-SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -433,11 +433,30 @@ const tool = await client.callTool({ name: 'my-tool', arguments: {} });

Remove unused schema imports: `CallToolResultSchema`, `CompatibilityCallToolResultSchema`, `ElicitResultSchema`, `CreateMessageResultSchema`, etc., when they were only used in `request()`/`send()`/`callTool()` calls.

## 12. Client Behavioral Changes
## 12. Experimental: `TaskCreationParams.ttl` no longer accepts `null`

`TaskCreationParams.ttl` changed from `z.union([z.number(), z.null()]).optional()` to `z.number().optional()`. Per the MCP spec, `null` TTL (unlimited lifetime) is only valid in server responses (`Task.ttl`), not in client requests. Omit `ttl` to let the server decide.

| v1 | v2 |
|---|---|
| `task: { ttl: null }` | `task: {}` (omit ttl) |
| `task: { ttl: 60000 }` | `task: { ttl: 60000 }` (unchanged) |

Type changes in handler context:

| Type | v1 | v2 |
|---|---|---|
| `TaskContext.requestedTtl` | `number \| null \| undefined` | `number \| undefined` |
| `CreateTaskServerContext.task.requestedTtl` | `number \| null \| undefined` | `number \| undefined` |
| `TaskServerContext.task.requestedTtl` | `number \| null \| undefined` | `number \| undefined` |

> These task APIs are `@experimental` and may change without notice.

## 13. Client Behavioral Changes

`Client.listPrompts()`, `listResources()`, `listResourceTemplates()`, `listTools()` now return empty results when the server lacks the corresponding capability (instead of sending the request). Set `enforceStrictCapabilities: true` in `ClientOptions` to throw an error instead.

## 13. Runtime-Specific JSON Schema Validators (Enhancement)
## 14. Runtime-Specific JSON Schema Validators (Enhancement)

The SDK now auto-selects the appropriate JSON Schema validator based on runtime:

Expand All @@ -461,7 +480,7 @@ new McpServer({ name: 'server', version: '1.0.0' }, {});

Access validators via `_shims` export: `import { DefaultJsonSchemaValidator } from '@modelcontextprotocol/server/_shims';`

## 14. Migration Steps (apply in this order)
## 15. Migration Steps (apply in this order)

1. Update `package.json`: `npm uninstall @modelcontextprotocol/sdk`, install the appropriate v2 packages
2. Replace all imports from `@modelcontextprotocol/sdk/...` using the import mapping tables (sections 3-4), including `StreamableHTTPServerTransport` → `NodeStreamableHTTPServerTransport`
Expand Down
40 changes: 40 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,46 @@ try {
}
```

### Experimental: `TaskCreationParams.ttl` no longer accepts `null`

The `ttl` field in `TaskCreationParams` (used when requesting the server to create a task) no longer accepts `null`. Per the MCP spec, `null` TTL (meaning unlimited lifetime) is only valid in server responses (`Task.ttl`), not in client requests. Clients should omit `ttl` to let the server decide the lifetime.

This also narrows the type of `requestedTtl` in `TaskContext`, `CreateTaskServerContext`, and `TaskServerContext` from `number | null | undefined` to `number | undefined`.

**Before (v1):**

```typescript
// Requesting unlimited lifetime by passing null
const result = await client.callTool({
name: 'long-task',
arguments: {},
task: { ttl: null }
});

// Handler context had number | null | undefined
server.setRequestHandler('tools/call', async (request, ctx) => {
const ttl: number | null | undefined = ctx.task?.requestedTtl;
});
```

**After (v2):**

```typescript
// Omit ttl to let the server decide (server may return null for unlimited)
const result = await client.callTool({
name: 'long-task',
arguments: {},
task: {}
});

// Handler context is now number | undefined
server.setRequestHandler('tools/call', async (request, ctx) => {
const ttl: number | undefined = ctx.task?.requestedTtl;
});
```

> **Note:** These task APIs are marked `@experimental` and may change without notice.

## Enhancements

### Automatic JSON Schema validator selection by runtime
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/experimental/tasks/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ import type {
* @experimental
*/
export type CreateTaskServerContext = ServerContext & {
task: { store: RequestTaskStore; requestedTtl?: number | null };
task: { store: RequestTaskStore; requestedTtl?: number };
};

/**
* Server context with guaranteed task ID and store for task operations.
* @experimental
*/
export type TaskServerContext = ServerContext & {
task: { id: string; store: RequestTaskStore; requestedTtl?: number | null };
task: { id: string; store: RequestTaskStore; requestedTtl?: number };
};

/**
Expand Down Expand Up @@ -137,7 +137,7 @@ export interface TaskMessageQueue {
*/
export interface CreateTaskOptions {
/**
* Time in milliseconds to keep task results available after completion.
* Duration in milliseconds to retain task from creation.
* If `null`, the task has unlimited lifetime until manually cleaned up.
*/
ttl?: number | null;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/shared/taskManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export interface RequestTaskStore {
export type TaskContext = {
id?: string;
store: RequestTaskStore;
requestedTtl?: number | null;
requestedTtl?: number;
};

export type TaskManagerOptions = {
Expand Down
5 changes: 2 additions & 3 deletions packages/core/src/types/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@ export const CursorSchema = z.string();
*/
export const TaskCreationParamsSchema = z.looseObject({
/**
* Time in milliseconds to keep task results available after completion.
* If `null`, the task has unlimited lifetime until manually cleaned up.
* Requested duration in milliseconds to retain task from creation.
*/
ttl: z.union([z.number(), z.null()]).optional(),
ttl: z.number().optional(),

/**
* Time in milliseconds to wait between task status requests.
Expand Down
11 changes: 5 additions & 6 deletions packages/core/test/experimental/inMemory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,17 +488,16 @@ describe('InMemoryTaskStore', () => {
expect(task).toBeNull();
});

it('should support null TTL for unlimited lifetime', async () => {
// Test that null TTL means unlimited lifetime
const taskParams: TaskCreationParams = {
ttl: null
};
it('should support omitted TTL for unlimited lifetime', async () => {
// Test that omitting TTL means unlimited lifetime (server returns null)
// Per spec: clients omit ttl to let server decide, server returns null for unlimited
const taskParams: TaskCreationParams = {};
const createdTask = await store.createTask(taskParams, 2222, {
method: 'tools/call',
params: {}
});

// The returned task should have null TTL
// The returned task should have null TTL (unlimited)
expect(createdTask.ttl).toBeNull();

// Task should not be cleaned up even after a long time
Expand Down
Loading
Loading