diff --git a/docs/for-coding-agents.md b/docs/for-coding-agents.md
index 3887440..a4c2ab0 100644
--- a/docs/for-coding-agents.md
+++ b/docs/for-coding-agents.md
@@ -142,7 +142,7 @@ Use these annotations to help agents make safer decisions:
| `.Destructive()` | May delete or mutate important state; ask for confirmation. |
| `.Idempotent()` | Safe to retry. |
| `.OpenWorld()` | Talks to external systems; expect latency and failures. |
-| `.LongRunning()` | May take time. Documentation hint for now — no protocol-level task advertisement until Repl integrates the SDK Tasks extension. |
+| `.LongRunning()` | May take time. Capable modern MCP clients use Tasks; other clients receive the normal synchronous result. |
| `.AutomationHidden()` | Do not expose this command to MCP automation. |
| `.WithOption(name, o => o.AutomationHidden())` | Keep this one option out of the tool schema; the command stays visible. |
diff --git a/docs/mcp-advanced.md b/docs/mcp-advanced.md
index 90ea3f6..8ce9d9b 100644
--- a/docs/mcp-advanced.md
+++ b/docs/mcp-advanced.md
@@ -180,6 +180,34 @@ Even a weak client can then discover the real tools manually and call them via `
| Client supports tools but misses dynamic refreshes | Enable `DiscoverAndCallShim` |
| Client has both issues | Use soft roots and the dynamic tool shim |
+## Tasks for long-running commands
+
+`.LongRunning()` opts a tool into the modern MCP Tasks extension:
+
+```csharp
+app.Map("deploy", DeployAsync)
+ .LongRunning();
+```
+
+For a client that negotiates the `io.modelcontextprotocol/tasks` extension, the call creates a task. The client can poll it with `tasks/get` and cancel it with `tasks/cancel`; the command's `CancellationToken` is cancelled too. Other tools remain synchronous.
+
+Clients that do not negotiate the extension — including legacy clients — receive the normal synchronous `tools/call` result. Repl does not implement the retired 2025 Tasks wire format (`Tool.Execution` / `taskSupport`); it uses the current extension only.
+
+By default Repl keeps task state in an in-memory store, which is the right fit for a stdio server process. For a stateless HTTP server, multiple server instances, or task recovery after a restart, supply a shared durable implementation of `IMcpTaskStore`:
+
+```csharp
+using ModelContextProtocol.Extensions.Tasks;
+
+app.UseMcpServer(options =>
+{
+ options.TaskStore = taskStore;
+});
+```
+
+`taskStore` must be safe for concurrent access and outlive the individual request. A task can be cancelled by a capable client, but a process shutdown still cancels any in-memory work.
+
+> **Interaction limitation:** the current official SDK cannot compose MCP's multi-round-trip requests (MRTR) with a task-backed `McpServerTool`. Keep task-backed commands non-interactive for now. A client that falls back to synchronous execution can still use Repl's existing interaction features.
+
## MCP Apps advanced patterns
For the basic MCP Apps setup, start with [mcp-overview.md](mcp-overview.md#mcp-apps) and [mcp-reference.md](mcp-reference.md#mcp-apps). This section covers patterns for more complex UIs.
diff --git a/docs/mcp-overview.md b/docs/mcp-overview.md
index ac42537..8502384 100644
--- a/docs/mcp-overview.md
+++ b/docs/mcp-overview.md
@@ -64,7 +64,7 @@ app.Map("deploy", handler).Destructive().LongRunning().OpenWorld();
| `.Destructive()` | Ask user for confirmation, sequential |
| `.Idempotent()` | Safe to retry, can parallelize |
| `.OpenWorld()` | Reaches external systems — expect latency and transient failures |
-| `.LongRunning()` | Slow-operation hint (protocol-level task advertisement returns once Repl integrates the SDK Tasks extension — see [mcp-reference.md](mcp-reference.md#sdk-and-protocol-versions)) |
+| `.LongRunning()` | Uses MCP Tasks with capable modern clients; other clients receive the normal synchronous result. See [advanced configuration](mcp-advanced.md#tasks-for-long-running-commands). |
| `.AutomationHidden()` | Not visible to agents |
**Annotate every command exposed to agents.** Unannotated tools force agents to assume the worst: confirm everything, no parallelism, no retries.
diff --git a/docs/mcp-reference.md b/docs/mcp-reference.md
index c6e6f34..3cb2e12 100644
--- a/docs/mcp-reference.md
+++ b/docs/mcp-reference.md
@@ -516,7 +516,7 @@ Feature support varies across agents. Check [mcp-availability.com](https://mcp-a
- Repl.Mcp builds on the official C# SDK (`ModelContextProtocol`), currently at **2.2.0**. The SDK negotiates the protocol version with each client, including fallback to the legacy `initialize` handshake for older hosts.
- **Roots, Sampling, and Logging** are deprecated by MCP specification 2026-07-28 (SEP-2577). Repl.Mcp keeps supporting them **for existing hosts and applications only** — new applications should not adopt these features (the SDK may remove them) and should prefer Repl's portable abstractions such as `IReplInteractionChannel`. The designated successor for server-initiated flows (SEP-2322, multi-round-trip requests) is available starting with SDK 2.0; Repl has not adopted it yet.
-- **MCP Tasks**: the SDK reorganized Tasks into `ModelContextProtocol.Extensions.Tasks` and dropped the per-tool execution augmentation (`Tool.Execution`) from the protocol surface, so `.LongRunning()` commands no longer advertise task support at the protocol level. The annotation stays in Repl's own model (help/docs); protocol-level task support can return once Repl integrates the Tasks extension, store, and get/update/cancel lifecycle (tracked in issue #72).
+- **MCP Tasks**: `.LongRunning()` uses the SDK's `ModelContextProtocol.Extensions.Tasks` extension and its current `tasks/get` / `tasks/update` / `tasks/cancel` lifecycle. Clients that do not negotiate the extension keep the normal synchronous `tools/call` behavior. Repl intentionally does not implement the retired per-tool `Tool.Execution` / `taskSupport` wire format; see [MCP Tasks for long-running commands](mcp-advanced.md#tasks-for-long-running-commands).
| Feature | Claude Desktop | Claude Code | Codex | VS Code Copilot | Cursor | Continue |
|---|---|---|---|---|---|---|
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index fc66d5a..ae916cb 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -15,6 +15,7 @@
+
diff --git a/src/Repl.Mcp/McpServerHandler.cs b/src/Repl.Mcp/McpServerHandler.cs
index 5cbf9da..c6739bc 100644
--- a/src/Repl.Mcp/McpServerHandler.cs
+++ b/src/Repl.Mcp/McpServerHandler.cs
@@ -1,7 +1,10 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
using ModelContextProtocol;
+using ModelContextProtocol.Extensions.Tasks;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using Repl.Documentation;
@@ -31,6 +34,11 @@ internal sealed class McpServerHandler
private readonly McpSamplingService _sampling;
private readonly McpElicitationService _elicitation;
private readonly McpFeedbackService _feedback;
+ private readonly IMcpTaskStore _taskStore;
+ private readonly IServiceProvider _taskServices;
+ private readonly IReadOnlyList> _taskConfigurators;
+ private readonly System.Collections.Concurrent.ConcurrentDictionary _longRunningToolNames =
+ new(StringComparer.OrdinalIgnoreCase);
private readonly Lock _refreshLock = new();
private readonly Lock _attachLock = new();
@@ -71,6 +79,19 @@ public McpServerHandler(
_sampling = new McpSamplingService(_requestServers);
_elicitation = new McpElicitationService(_requestServers);
_feedback = new McpFeedbackService(_requestServers);
+ _taskStore = options.TaskStore
+ ?? services.GetService(typeof(IMcpTaskStore)) as IMcpTaskStore
+ ?? new InMemoryMcpTaskStore();
+
+ var taskServices = new ServiceCollection();
+ taskServices
+ .AddMcpServer()
+ .WithTasks(_taskStore, taskOptions =>
+ taskOptions.ExecutionModeSelector = ResolveTaskExecutionMode);
+ _taskServices = taskServices.BuildServiceProvider();
+ _taskConfigurators = _taskServices
+ .GetServices>()
+ .ToArray();
}
private McpSessionContext CreateSessionContext()
@@ -169,7 +190,7 @@ internal McpServerOptions BuildDynamicServerOptions()
_ = CreateDocumentationModel(CreateSessionContext().Services);
}
- return new McpServerOptions
+ var serverOptions = new McpServerOptions
{
ServerInfo = new Implementation { Name = serverName, Version = serverVersion },
Capabilities = BuildCapabilities(),
@@ -184,6 +205,8 @@ internal McpServerOptions BuildDynamicServerOptions()
GetPromptHandler = GetPromptAsync,
},
};
+ ConfigureTasks(serverOptions);
+ return serverOptions;
}
internal McpServerOptions BuildStaticServerOptions()
@@ -192,7 +215,7 @@ internal McpServerOptions BuildStaticServerOptions()
var serverVersion = _options.ServerVersion ?? "1.0.0";
var snapshot = BuildSnapshotCore(CreateSessionContext());
- return new McpServerOptions
+ var serverOptions = new McpServerOptions
{
ServerInfo = new Implementation { Name = serverName, Version = serverVersion },
Capabilities = BuildCapabilities(),
@@ -200,6 +223,8 @@ internal McpServerOptions BuildStaticServerOptions()
ResourceCollection = ToResourceCollection(snapshot.Resources),
PromptCollection = ToCollection(snapshot.Prompts),
};
+ ConfigureTasks(serverOptions);
+ return serverOptions;
}
internal McpGeneratedSnapshot BuildSnapshotForTests() => BuildSnapshotCore(CreateSessionContext());
@@ -472,12 +497,29 @@ private McpGeneratedSnapshot BuildSnapshotCore(McpSessionContext context)
command => command,
StringComparer.OrdinalIgnoreCase);
var tools = GenerateAllTools(model, adapter, _separator, commandsByPath);
+ foreach (var tool in tools.OfType().Where(static tool => tool.IsLongRunning))
+ {
+ _longRunningToolNames.TryAdd(tool.ProtocolTool.Name, 0);
+ }
ValidateCompatibilityToolNames(tools);
var resources = GenerateResources(model, adapter, _separator, commandsByPath, context.Services);
var prompts = CollectPrompts(model, adapter, _separator);
return new McpGeneratedSnapshot(adapter, tools, resources, prompts);
}
+ private void ConfigureTasks(McpServerOptions serverOptions)
+ {
+ foreach (var configurator in _taskConfigurators)
+ {
+ configurator.Configure(serverOptions);
+ }
+ }
+
+ private McpTaskExecutionMode ResolveTaskExecutionMode(RequestContext request) =>
+ request.Params?.Name is { } toolName && _longRunningToolNames.ContainsKey(toolName)
+ ? McpTaskExecutionMode.Optional
+ : McpTaskExecutionMode.Synchronous;
+
// The documentation model resolves module-presence predicates against the SESSION's
// services (e.g. IMcpClientRoots), so the model — and everything generated from it —
// reflects the capabilities of the session it is built for.
diff --git a/src/Repl.Mcp/Repl.Mcp.csproj b/src/Repl.Mcp/Repl.Mcp.csproj
index 32299b9..cfba286 100644
--- a/src/Repl.Mcp/Repl.Mcp.csproj
+++ b/src/Repl.Mcp/Repl.Mcp.csproj
@@ -16,6 +16,7 @@
+
diff --git a/src/Repl.Mcp/ReplMcpServerOptions.cs b/src/Repl.Mcp/ReplMcpServerOptions.cs
index 6f363fe..c3f4a2a 100644
--- a/src/Repl.Mcp/ReplMcpServerOptions.cs
+++ b/src/Repl.Mcp/ReplMcpServerOptions.cs
@@ -1,4 +1,5 @@
using ModelContextProtocol.Protocol;
+using ModelContextProtocol.Extensions.Tasks;
using Repl.Documentation;
namespace Repl.Mcp;
@@ -97,6 +98,13 @@ public sealed class ReplMcpServerOptions
///
public DynamicToolCompatibilityMode DynamicToolCompatibility { get; set; } = DynamicToolCompatibilityMode.Disabled;
+ ///
+ /// Optional store used for MCP Tasks created by .LongRunning() commands.
+ /// When null, Repl uses an in-memory store suitable for a single stdio server process.
+ /// Configure a durable, shared store for stateless HTTP or work that must survive process restarts.
+ ///
+ public IMcpTaskStore? TaskStore { get; set; }
+
private readonly List _prompts = [];
private readonly List _uiResources = [];
diff --git a/src/Repl.Mcp/ReplMcpServerTool.cs b/src/Repl.Mcp/ReplMcpServerTool.cs
index a637677..c40af7c 100644
--- a/src/Repl.Mcp/ReplMcpServerTool.cs
+++ b/src/Repl.Mcp/ReplMcpServerTool.cs
@@ -13,18 +13,17 @@ internal sealed class ReplMcpServerTool : McpServerTool
{
private readonly McpToolAdapter _adapter;
private readonly Tool _protocolTool;
+ private readonly bool _isLongRunning;
- // SDK 2.0 extracted MCP Tasks into ModelContextProtocol.Extensions.Tasks (store, task
- // results, client polling) and dropped the per-tool Tool.Execution / ToolTaskSupport
- // augmentation from the protocol surface. Repl keeps .LongRunning() in its own model
- // (help/docs) and deliberately does not advertise task support until Repl integrates
- // the Tasks extension end-to-end (tasks/get|update|cancel) — tracked in issue #72.
+ // The modern MCP Tasks extension selects the execution mode from this marker. The
+ // wire protocol no longer carries the retired per-tool Tool.Execution augmentation.
public ReplMcpServerTool(
ReplDocCommand command,
string toolName,
McpToolAdapter adapter)
{
_adapter = adapter;
+ _isLongRunning = command.Annotations?.LongRunning == true;
_protocolTool = new Tool
{
Name = toolName,
@@ -41,6 +40,9 @@ public ReplMcpServerTool(
///
public override Tool ProtocolTool => _protocolTool;
+ /// Whether this tool opts into the modern MCP Tasks runtime.
+ internal bool IsLongRunning => _isLongRunning;
+
///
public override IReadOnlyList