Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
16ba027
Add types for tasks
LucaButBoring Oct 22, 2025
ecef231
Implement PendingRequest and basic task API
LucaButBoring Oct 22, 2025
41f2124
Implement RelatedTask metadata sends
LucaButBoring Oct 22, 2025
a8fabb6
Implement task state management
LucaButBoring Oct 22, 2025
b3420b3
Attach related task metadata to request handler
LucaButBoring Oct 22, 2025
8e17d04
Create task before calling handler
LucaButBoring Oct 23, 2025
fcd2882
Create task example
LucaButBoring Oct 23, 2025
c73b105
Implement input_required status for tasks
LucaButBoring Oct 23, 2025
b028061
Implement unit tests for task support
LucaButBoring Oct 23, 2025
d9b72f0
Add docs for task augmentation
LucaButBoring Oct 23, 2025
5dc999f
Implement tasks/list method
LucaButBoring Oct 27, 2025
a2d65df
Merge branch 'main' of https://github.com/modelcontextprotocol/typesc…
LucaButBoring Oct 27, 2025
71a9568
Automatically execute tool calls as tasks
LucaButBoring Oct 29, 2025
30043ed
Merge branch 'main' into feat/tasks
LucaButBoring Oct 31, 2025
2167b43
Implement task API tests on both the client and server
LucaButBoring Nov 1, 2025
12d0f66
Make default task polling interval configurable
LucaButBoring Nov 1, 2025
6a28003
Merge branch 'main' into feat/tasks
LucaButBoring Nov 3, 2025
bb28ef7
Exclude relatedTask from RequestHandlerExtra
LucaButBoring Nov 3, 2025
0bf2b42
Mark tasks as cancelled only after confirming abort
LucaButBoring Nov 3, 2025
486e8ed
Store task result before attempting to respond to client
LucaButBoring Nov 3, 2025
06db603
Allow task polling before creation notification arrives
LucaButBoring Nov 3, 2025
723bc7d
Add session ID to TaskStore methods
LucaButBoring Nov 5, 2025
3789080
Implement tasks/delete
LucaButBoring Nov 5, 2025
9ae5f84
Rename pollFrequency to pollInterval
LucaButBoring Nov 5, 2025
719675a
Implement capabilities for tasks
LucaButBoring Nov 5, 2025
7a4f52b
Add taskHint for tool-level signposting
LucaButBoring Nov 5, 2025
01be32d
Only auto-add task ID if server capability is set
LucaButBoring Nov 6, 2025
0b8ced2
Correctly check peer task capabilities on receiving end
LucaButBoring Nov 6, 2025
0db292a
Merge branch 'main' of https://github.com/modelcontextprotocol/typesc…
LucaButBoring Nov 10, 2025
b4207b9
Introduce shim task interface for tools
LucaButBoring Nov 10, 2025
b24ea0f
Remove most capabilities and automatic task management
LucaButBoring Nov 11, 2025
4d570d3
Support customizing task pollInterval on server
LucaButBoring Nov 11, 2025
2382df4
Remove submitted and unknown statuses
LucaButBoring Nov 11, 2025
e4c2695
Merge branch 'main' of upstream into feat/tasks
LucaButBoring Nov 11, 2025
388e603
Fix spec types test compatibility after upstream merge
LucaButBoring Nov 11, 2025
b1cf3cf
Merge branch 'main' of https://github.com/modelcontextprotocol/typesc…
LucaButBoring Nov 17, 2025
8d06d4f
chore: commit new lockfile
LucaButBoring Nov 17, 2025
5975661
Introduce CreateTaskResult to request flow in tools
LucaButBoring Nov 18, 2025
0af275f
Implement tasks/cancel
LucaButBoring Nov 18, 2025
25d0b14
Update TaskStore interface
LucaButBoring Nov 18, 2025
064568b
Preliminary tasks/result implementation updates
LucaButBoring Nov 19, 2025
9296ea8
Handle input_required state in PendingRequest
LucaButBoring Nov 19, 2025
ffc1282
Implement task status notifications
LucaButBoring Nov 19, 2025
0bf0d70
Remove task creation notification
LucaButBoring Nov 19, 2025
b945454
Remove types for task creation notification
LucaButBoring Nov 19, 2025
1c7b332
Use actual notification schema
LucaButBoring Nov 19, 2025
dcd761b
Remove related-task metadata from messages that already include it as…
LucaButBoring Nov 19, 2025
ad220e0
Update taskHint implementation on server
LucaButBoring Nov 19, 2025
a3ebe62
Add tests for task cancellation vs request cancellation
LucaButBoring Nov 19, 2025
901ec9c
Validate task status transitions; allow failure result
LucaButBoring Nov 19, 2025
92dfcf8
Fix registerToolTask type inference with overloads
LucaButBoring Nov 19, 2025
5af038d
Clean up TTL handling
LucaButBoring Nov 19, 2025
5996224
Add task-listing tests
LucaButBoring Nov 19, 2025
b1e1401
Implement cross-request progress
LucaButBoring Nov 20, 2025
f668117
Backfill unit tests for task changes
LucaButBoring Nov 20, 2025
370fd07
Implement end to end tests for tasks
LucaButBoring Nov 20, 2025
304ff0a
Add elicitation integration tests, fix type issues
LucaButBoring Nov 20, 2025
b562a09
Implement SSE side-channeling on tasks/result
LucaButBoring Nov 20, 2025
dba843b
Replace PendingRequest with async generator
LucaButBoring Nov 20, 2025
57ab83b
Remove unneeded casts in requestStream()
LucaButBoring Nov 20, 2025
a279a45
Reuse callToolStream inside of callTool
LucaButBoring Nov 20, 2025
94681c7
Merge branch 'main' of https://github.com/modelcontextprotocol/typesc…
LucaButBoring Nov 21, 2025
e2f6e89
Add missing schema
LucaButBoring Nov 21, 2025
b9c3054
Remove now-unused isomorphic UUID dependency
LucaButBoring Nov 21, 2025
cd89e76
Use same polling interval in waitForTaskUpdate as caller would use
LucaButBoring Nov 21, 2025
70803d8
Make queue implementation swappable
LucaButBoring Nov 21, 2025
702d077
Merge branch 'main' into feat/tasks
LucaButBoring Nov 21, 2025
86cc5cc
Refactor TaskMessageQueue to not store response closures
LucaButBoring Nov 21, 2025
8cf6675
Move annotations.taskHint to execution.taskSupport
LucaButBoring Nov 21, 2025
8acb942
Remove now-unneeded _taskResultWaiters map
LucaButBoring Nov 22, 2025
e7143f1
Don't fail the task or dump the queue on a failed enqueue operation
LucaButBoring Nov 22, 2025
c312270
Merge branch 'main' into feat/tasks
felixweinberger Nov 24, 2025
5fcad03
Update example code for tasks to match impl
LucaButBoring Nov 24, 2025
cea8e6b
Update src/examples/server/simpleStreamableHttp.ts
LucaButBoring Nov 24, 2025
6839899
Update src/examples/server/simpleStreamableHttp.ts
LucaButBoring Nov 24, 2025
0e84bd4
Update src/examples/server/simpleStreamableHttp.ts
LucaButBoring Nov 24, 2025
a24fece
Fix inconsistencies in task example, tweak interfaces
LucaButBoring Nov 24, 2025
05f697f
Simplify request interface further
LucaButBoring Nov 24, 2025
76e27ea
Remove usages of removed params in tests
LucaButBoring Nov 24, 2025
0d7e70d
Update README to reflect latest task changes
LucaButBoring Nov 24, 2025
4029546
Add lastUpdatedAt to Task
LucaButBoring Nov 24, 2025
7220df9
Merge branch 'main' into feat/tasks
felixweinberger Nov 25, 2025
db3280d
Await a few promises
LucaButBoring Nov 25, 2025
bdc5aa4
Validate against CreateTaskResult in low-level client/server
LucaButBoring Nov 25, 2025
4782f9d
Use CreateTaskResult for task ID in tests
LucaButBoring Nov 25, 2025
7c07f38
Merge branch 'main' into feat/tasks
LucaButBoring Nov 25, 2025
44c751a
Fix TaskStatusNotificationParams to spread task fields per spec
felixweinberger Nov 25, 2025
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
199 changes: 199 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
- [Improving Network Efficiency with Notification Debouncing](#improving-network-efficiency-with-notification-debouncing)
- [Low-Level Server](#low-level-server)
- [Eliciting User Input](#eliciting-user-input)
- [Task-Based Execution](#task-based-execution)
- [Writing MCP Clients](#writing-mcp-clients)
- [Proxy Authorization Requests Upstream](#proxy-authorization-requests-upstream)
- [Backwards Compatibility](#backwards-compatibility)
Expand Down Expand Up @@ -1382,6 +1383,204 @@ const client = new Client(
);
```

### Task-Based Execution

Task-based execution enables "call-now, fetch-later" patterns for long-running operations. This is useful for tools that take significant time to complete, where clients may want to disconnect and check on progress or retrieve results later.

Common use cases include:

- Long-running data processing or analysis
- Code migration or refactoring operations
- Complex computational tasks
- Operations that require periodic status updates

#### Server-Side: Implementing Task Support

To enable task-based execution, configure your server with a `TaskStore` implementation. The SDK doesn't provide a built-in TaskStore—you'll need to implement one backed by your database of choice:

```typescript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { TaskStore } from '@modelcontextprotocol/sdk/shared/task.js';
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';

// Implement TaskStore backed by your database (e.g., PostgreSQL, Redis, etc.)
class MyTaskStore implements TaskStore {
async createTask(taskParams, requestId, request, sessionId?): Promise<Task> {
// Generate unique taskId and lastUpdatedAt/createdAt timestamps
// Store task in your database, using the session ID as a proxy to restrict unauthorized access
// Return final Task object
}

async getTask(taskId): Promise<Task | null> {
// Retrieve task from your database
}

async updateTaskStatus(taskId, status, statusMessage?): Promise<void> {
// Update task status in your database
}

async storeTaskResult(taskId, result): Promise<void> {
// Store task result in your database
}

async getTaskResult(taskId): Promise<Result> {
// Retrieve task result from your database
}

async listTasks(cursor?, sessionId?): Promise<{ tasks: Task[]; nextCursor?: string }> {
// List tasks with pagination support
}
}

const taskStore = new MyTaskStore();

const server = new Server(
{
name: 'task-enabled-server',
version: '1.0.0'
},
{
capabilities: {
tools: {},
// Declare capabilities
tasks: {
list: {},
cancel: {},
requests: {
tools: {
// Declares support for tasks on tools/call
call: {}
}
}
}
},
taskStore // Enable task support
}
);

// Register a tool that supports tasks
server.registerToolTask(
'my-echo-tool',
{
title: 'My Echo Tool',
description: 'A simple task-based echo tool.',
inputSchema: {
message: z.string().describe('Message to send')
}
},
{
async createTask({ message }, { taskStore, taskRequestedTtl, requestId }) {
// Create the task
const task = await taskStore.createTask({
ttl: taskRequestedTtl
});

// Simulate out-of-band work
(async () => {
await new Promise(resolve => setTimeout(resolve, 5000));
await taskStore.storeTaskResult(task.taskId, 'completed', {
content: [
{
type: 'text',
text: message
}
]
});
})();

// Return CreateTaskResult with the created task
return { task };
},
async getTask(_args, { taskId, taskStore }) {
// Retrieve the task
return await taskStore.getTask(taskId);
},
async getTaskResult(_args, { taskId, taskStore }) {
// Retrieve the result of the task
const result = await taskStore.getTaskResult(taskId);
return result as CallToolResult;
}
}
);
```

**Note**: See `src/examples/shared/inMemoryTaskStore.ts` in the SDK source for a reference task store implementation suitable for development and testing.

#### Client-Side: Using Task-Based Execution

Clients use `callToolStream()` to initiate task-augmented tool calls. The returned `AsyncGenerator` abstracts automatic polling and status updates:

```typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';

const client = new Client({
name: 'task-client',
version: '1.0.0'
});

// ... connect to server ...

// Call the tool with task metadata using streaming API
const stream = client.callToolStream(
{
name: 'my-echo-tool',
arguments: { message: 'Hello, world!' }
},
CallToolResultSchema
);

// Iterate the stream and handle stream events
let taskId = '';
for await (const message of stream) {
switch (message.type) {
case 'taskCreated':
console.log('Task created successfully with ID:', message.task.taskId);
taskId = message.task.taskId;
break;
case 'taskStatus':
console.log(` ${message.task.status}${message.task.statusMessage ?? ''}`);
break;
case 'result':
console.log('Task completed! Tool result:');
message.result.content.forEach(item => {
if (item.type === 'text') {
console.log(` ${item.text}`);
}
});
break;
case 'error':
throw message.error;
}
}

// Optional: Fire and forget - disconnect and reconnect later
// (useful when you don't want to wait for long-running tasks)
// Later, after disconnecting and reconnecting to the server:
const taskStatus = await client.getTask({ taskId });
console.log('Task status:', taskStatus.status);

if (taskStatus.status === 'completed') {
const taskResult = await client.getTaskResult({ taskId }, CallToolResultSchema);
console.log('Retrieved result after reconnect:', taskResult);
}
```

The `callToolStream()` method also works with non-task tools, making it a drop-in replacement for `callTool()` in applications that support it. When used to invoke a tool that doesn't support tasks, the `taskCreated` and `taskStatus` events will not be emitted.

#### Task Status Lifecycle

Tasks transition through the following states:

- **working**: Task is actively being processed
- **input_required**: Task is waiting for additional input (e.g., from elicitation)
- **completed**: Task finished successfully
- **failed**: Task encountered an error
- **cancelled**: Task was cancelled by the client

The `ttl` parameter suggests how long the server will manage the task for. If the task duration exceeds this, the server may delete the task prematurely. The client's suggested value may be overridden by the server, and the final TTL will be provided in `Task.ttl` in
`taskCreated` and `taskStatus` events.

### Writing MCP Clients

The SDK provides a high-level client interface:
Expand Down
Loading
Loading