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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ repos:
hooks:
- id: ty
name: ty check
entry: uv run ty check
entry: uv run --isolated ty check
language: system
types: [python]
files: ^src/|^tests/
Expand Down
155 changes: 155 additions & 0 deletions docs/clients/tasks.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
title: Background Tasks
sidebarTitle: Background Tasks
description: Execute operations asynchronously and track their progress
icon: clock
tag: "NEW"
---

import { VersionBadge } from "/snippets/version-badge.mdx"

<VersionBadge version="2.14" />

The [MCP task protocol](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) lets you request operations to run asynchronously. This returns a Task object immediately, letting you track progress, cancel operations, or await results.

See [Server Background Tasks](/servers/tasks) for how to enable this on the server side.

## Requesting Background Execution

Pass `task=True` to run an operation as a background task:

```python
from fastmcp import Client

async with Client(server) as client:
# Start a background task
task = await client.call_tool("slow_computation", {"duration": 10}, task=True)

print(f"Task started: {task.task_id}")

# Do other work while it runs...

# Get the result when ready
result = await task.result()
```

This works with all three operation types:

```python
# Tools
tool_task = await client.call_tool("my_tool", args, task=True)

# Resources
resource_task = await client.read_resource("file://large.txt", task=True)

# Prompts
prompt_task = await client.get_prompt("my_prompt", args, task=True)
```

## Task Objects

All task types share a common interface:

### Getting Results

```python
task = await client.call_tool("analyze", {"text": "hello"}, task=True)

# Wait for and get the result
result = await task.result()

# Or use await directly (shorthand for .result())
result = await task
```

### Checking Status

```python
status = await task.status()

print(f"Status: {status.status}") # "working", "completed", "failed", "cancelled"
print(f"Message: {status.statusMessage}") # Progress message from server
```

### Waiting for Completion

```python
# Wait for task to complete (with timeout)
status = await task.wait(timeout=30.0)

# Wait for a specific state
status = await task.wait(state="completed", timeout=30.0)
```

### Cancelling Tasks

```python
await task.cancel()
```

### Status Notifications

Register callbacks to receive real-time status updates:

```python
def on_status_change(status):
print(f"Task {status.taskId}: {status.status} - {status.statusMessage}")

task.on_status_change(on_status_change)

# Async callbacks also supported
async def on_status_async(status):
await log_status(status)

task.on_status_change(on_status_async)
```

## Graceful Degradation

You can always pass `task=True` regardless of whether the server supports background tasks. Per the [MCP specification](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks), servers that don't support tasks will execute the operation immediately and return the result inline. Your code works either way:

```python
task = await client.call_tool("my_tool", args, task=True)

if task.returned_immediately:
print("Server executed immediately (no background support)")
else:
print("Running in background")

# Either way, this works
result = await task.result()
```

This means you can write task-aware client code without worrying about server capabilities—the Task API provides a consistent interface whether the operation runs in the background or completes immediately.

## Complete Example

```python
import asyncio
from fastmcp import Client

async def main():
async with Client(server) as client:
# Start background task
task = await client.call_tool(
"slow_computation",
{"duration": 10},
task=True,
)

# Subscribe to updates
def on_update(status):
print(f"Progress: {status.statusMessage}")

task.on_status_change(on_update)

# Do other work
print("Doing other work while task runs...")
await asyncio.sleep(2)

# Wait for completion and get result
result = await task.result()
print(f"Result: {result.data}")

asyncio.run(main())
```
4 changes: 3 additions & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@
"servers/progress",
"servers/proxy",
"servers/sampling",
"servers/storage-backends"
"servers/storage-backends",
"servers/tasks"
]
},
{
Expand Down Expand Up @@ -171,6 +172,7 @@
"clients/logging",
"clients/progress",
"clients/sampling",
"clients/tasks",
"clients/messages",
"clients/roots"
]
Expand Down
Loading
Loading