The tasks extension (io.modelcontextprotocol/tasks) of the MCP protocol version 2026-07-28 lets a server run a long tools/call in the background. Instead of waiting for the result, the server answers immediately with a task handle (resultType task) and the client polls it with tasks/get, sends requested input with tasks/update or cancels it with tasks/cancel. The server handles the three tasks/* methods automatically.
server/discover. By default False.
A task is only created when the client declared the tasks extension in the _meta of that request. Use the TaskSupport property of the tool to control it: aimcptsForbidden never creates a task, aimcptsOptional creates it when the client supports it, and aimcptsRequired rejects clients without the extension with error -32021.
Inside OnMCPRequestTool, call CreateTask(aSession, aResponse). If it returns a task object, the response is converted into a task handle: start the work in a background thread and exit the handler. If it returns nil (tasks disabled, legacy client, extension not declared, tool forbids tasks or MaxTasks reached), run the tool synchronously as usual.
procedure TMainForm.MCPServerMCPRequestTool(Sender: TObject;
const aSession: TsgcAI_MCP_Session; const aRequest: TsgcAI_MCP_Request_ToolsCall;
const aResponse: TsgcAI_MCP_Response_ToolsCall);
var
oTask: TsgcAI_MCP_Task;
begin
oTask := MCPServer.CreateTask(aSession, aResponse);
if Assigned(oTask) then
begin
TThread.CreateAnonymousThread(
procedure
var
i: Integer;
begin
for i := 1 to 10 do
begin
if oTask.IsCancelled then
exit;
oTask.SetStatusMessage(Format('Processing %d of 10', [i]));
Sleep(1000);
end;
oTask.Complete('{"content":[{"type":"text","text":"Report ready"}]}');
end).Start;
end
else
aResponse.Result.Content.AddText('Report ready');
end;
The task object is owned by the server and is thread safe, do not free it. Its methods return False when the task is already in a terminal state (completed, failed or cancelled) or has expired.
tools/call result, passing a response object or the raw JSON of the result.tasks/get.
tasks/cancel. Stop the background work; IsCancelled is True from now on.tasks/update with the answers to the inputs requested by RequireInput. aInputResponses is a raw JSON object with one member per key.
Both events are published, assign them in the Object Inspector or in code.
procedure TMainForm.FormCreate(Sender: TObject);
begin
MCPServer.MCPOptions.Tasks.Enabled := True;
MCPServer.OnMCPTaskCancel := OnMCPTaskCancelEvent;
MCPServer.OnMCPTaskUpdate := OnMCPTaskUpdateEvent;
end;
procedure TMainForm.OnMCPTaskCancelEvent(Sender: TObject; const aTask: TsgcAI_MCP_Task);
begin
Log('task cancelled: ' + aTask.TaskId);
end;
procedure TMainForm.OnMCPTaskUpdateEvent(Sender: TObject; const aTask: TsgcAI_MCP_Task;
const aInputResponses: string);
begin
Log('task ' + aTask.TaskId + ' input: ' + aInputResponses);
end;
The read only property TasksCount returns the number of tasks stored by the server, working or finished; expired tasks are purged before counting.
MemoLog.Lines.Add('stored tasks: ' + IntToStr(MCPServer.TasksCount));
Tasks are bound to the principal returned by DoGetRequestPrincipal, so a client can only read, update or cancel its own tasks. An unknown task returns error -32602 "Task not found" and an expired one "Failed to retrieve task: Task has expired".