Primitives, Capabilities & Utilities in MCP
Tasks (Client & Server Side)
Tasks exist because not every request should block.
Sometimes a tool call is expensive, slow, batchy, or tied to a job system that already works asynchronously (think Kubernetes jobs, video rendering queues, ETL pipelines, security scans, backups ..etc). For those, MCP adds an optional "task wrapper" so the receiver can accept the request quickly, run it in the background, and let the requestor come back later to check progress and retrieve results.
A Task is a durable little state machine owned by the receiver (the client or the server that accepts the task-augmented request).
It has a receiver-generated taskId, a status (working, input_required, completed, failed, cancelled), timestamps, and optional knobs like ttl (how long it should be kept) and pollInterval (how often the requestor should check).
The key conceptual shift is this is the following: With normal MCP requests, you send tools/call and the response contains the tool result. With task-augmented requests, you still send tools/call, but you include a task field.
Here is the conceptual contrast in JSON format:
First, a normal tools/call request. The response contains the actual result immediately.
// Normal tools/call request
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "generate_report",
"arguments": {
"month": "January"
}
}
}
// Normal response (final result returned directly)
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "Report for January generated successfully."
}
],
"isError": false
}
}
Now the same operation, but task-augmented. You still call tools/call, but you include a task field and other details like ttl (the time the task should be kept alive):
// Task-augmented tools/call request
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "generate_report",
"arguments": {
"month": "January"
},
"task": {
"ttl": 60000
}
}
}
// Immediate response (task created, work not finished yet)
// The response does not contain the tool result.
// It contains a task handle (taskId + status)
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"task": {
"taskId": "task-12345",
"status": "working",
"createdAt": "2026-11-25T10:30:00Z",
"lastUpdatedAt": "2026-11-25T10:30:00Z",
"ttl": 60000,
"pollInterval": 5000
}
}
}
The requestor can then poll tasks/get with the taskId to check the status:
// Polling task status
{
"jsonrpc": "2.0",
"id": 3,
"method": "tasks/get",
"params": {
"taskId": "task-12345"
}
}
// Response with current task status
{Practical MCP with FastMCP & LangChain
Engineering the Agentic ExperienceEnroll now to unlock current content and receive all future updates for free. Your purchase supports the author and fuels the creation of more exciting content. Act fast, as the price will rise as the course nears completion!
