Skip to content

feat(mcp): support modern task execution - #78

Draft
carldebilly wants to merge 9 commits into
dev/cdb/issue-51-mcp-v2from
dev/cdb/issue-72-mcp-tasks
Draft

carldebilly wants to merge 9 commits into
dev/cdb/issue-51-mcp-v2from
dev/cdb/issue-72-mcp-tasks

Conversation

@carldebilly

@carldebilly carldebilly commented Aug 13, 2026

Copy link
Copy Markdown
Member

Summary

Adds modern MCP Tasks support for Repl commands marked .LongRunning().

Behavior

  • Uses the official ModelContextProtocol.Extensions.Tasks 2.2.0 extension.
  • A modern client that negotiates io.modelcontextprotocol/tasks receives a task handle for a long-running command and can poll or cancel it.
  • Commands without .LongRunning() remain synchronous.
  • Clients without Tasks support, including legacy clients, receive the existing synchronous tools/call response.
  • Uses an in-memory task store by default, suitable for stdio; ReplMcpServerOptions.TaskStore allows a durable shared store for HTTP or restart-resilient deployments.

Documentation

Documents the modern-only protocol, the default store, and the current SDK limitation: task-backed MCP tools cannot compose with MRTR/interactive flows yet.

Validation

  • C# integration tests using the official SDK client cover task creation and polling, synchronous behavior, legacy fallback, and cancellation.
  • MSTest runs the official MCP Inspector 2.2.0 against the real stdio sample, covering normal resources and the long-running synchronous fallback.
  • MCP suite: 234 passing, 2 Inspector tests opt-in; Inspector run: 2/2 passing.

Closes #72.

- Bump ModelContextProtocol 1.4.1 -> 2.0.0-preview.3.
- Remove the Tool.Execution mapping for .LongRunning() commands: SDK 2.0
  dropped the experimental MCP Tasks tool augmentation (Tasks SEP deferred
  out of the 2.0 protocol release). The annotation stays in Repl's model;
  protocol-level task support returns with the SDK Tasks runtime.
- Keep supporting Roots, Sampling, and Logging: deprecated by spec
  2026-07-28 (SEP-2577, MCP9005) with no replacement, still relied on by
  current hosts. Scoped, documented pragmas at the feature touchpoints.
- Document the SDK/protocol version posture in docs/mcp-reference.md.

Full suite green against the new SDK (1312 passed, 1 known skip),
including all MCP capability, tool-call, roots, sampling, and logging
regressions.

Note: re-pin to the stable 2.0.0 release before cutting stable 0.12.

Refs #51
…e shape (review)

- Correct the migration rationale: MCP Tasks was EXTRACTED to
  ModelContextProtocol.Extensions.Tasks (store, task results, client
  polling), not removed; the per-tool Tool.Execution augmentation is gone
  from the protocol surface. Comments and docs now say so, and Repl still
  deliberately does not advertise task support without the runtime.
- Name the designated successor (SEP-2322 multi-round-trip requests) in the
  deprecation pragmas instead of claiming 'no replacement API'.
- Narrow MCP9005 pragmas to their touchpoints in McpServerHandler and
  Given_McpIntegration (file-scoped kept only where usage is dense).
- Lock the SDK-2.0 tools/list wire shape: a .LongRunning() tool serializes
  its annotations and emits no task/execution augmentation.
- Align remaining .LongRunning() doc mentions (overview, coding-agents
  guide, package README) with the current no-advertisement posture.
…ed server field

SDK 2.0's 2026-07-28 protocol path hands each request a destination-bound
McpServer, and one handler can serve several sessions. The four capability
services (roots, sampling, elicitation, feedback) stored the last-attached
server in a shared mutable field, so a concurrent request from another
session could cross-wire capabilities mid-call (IsSupported flipping while
a handler was awaiting).

- McpRequestServerAccessor: AsyncLocal request binding flowing with the
  invocation, session-level server as fallback for code outside a request
  (routing notifications, roots list-changed handler).
- Services resolve the effective server through the accessor with a single
  read per operation (no torn check-then-use).
- McpServerHandler splits session-level attach (RunAsync, once) from
  request-level binding (every handler); externally hosted servers adopt
  the first observed server for session concerns.
- Deterministic regression: two sessions on one handler, sampling-capable
  client pauses mid-call while a sampling-less client is served — the
  paused call must keep observing ITS client's capabilities (RED observed:
  'True|False' on the pre-fix code, exactly the reported repro).
… Tasks wording (review)

- Regression pinning the last initialize-era protocol revision (2025-11-25):
  asserts the negotiated version and a tool list + call — the default client
  negotiates 2026-07-28 and never exercised the fallback path.
- Roots/Sampling/Logging documented as legacy-compatibility only: deprecation
  notices in mcp-agent-capabilities.md and mcp-advanced.md steer new
  applications toward IReplInteractionChannel / soft roots; mcp-reference.md
  no longer reads as an endorsement.
- Tasks wording corrected everywhere: the SDK has shipped the Tasks extension
  (ModelContextProtocol.Extensions.Tasks); what is pending is Repl's
  integration (issue #72) — including the CommandAnnotations.LongRunning XML
  doc that still promised task-based execution.
…le (review)

- Hard roots are now SESSION state: entries keyed by destination server in
  a ConditionalWeakTable (weak keys die with the session), with a global
  version stamp for roots-list-changed invalidation. One session can no
  longer receive another session's cached workspace roots, and the
  root-dependent snapshot builds from the right workspace (RED observed:
  client B received client A's roots).
- Session attachment is reference-counted: the handler tracks every active
  session, discovery notifications fan out to ALL of them, the accessor
  fallback moves to a surviving session on close, and the routing
  subscription is dropped only when the LAST session ends. A first-session
  close no longer silences the survivors (RED observed: surviving session
  timed out waiting for tools/list_changed).
- Roots list-changed handler registered once per session (per-server
  registration replaces the single global flag).
One handler serves several sessions; everything that varied per client was
still handler-global after the earlier point fixes. McpSessionContext now
owns it all, per the architecture review:

- hard AND soft roots: McpClientRootsService is one instance per session
  (plain fields again — the ConditionalWeakTable keying is gone); a
  session's 'workspace init' no longer sets another session's workspace.
- generated snapshot + version + gate: the tool graph can be gated on
  session capabilities, so each session caches its own build against the
  handler-global routing version (RED observed: the roots-less session saw
  the roots-gated tool of the other session).
- compatibility-shim intro: per-session flag, reset for every active
  session on routing invalidation (RED observed: only the first session
  received the discover_tools/call_tool intro).
- per-session service overlay handed to McpServer.Create; request handlers
  recover their session through request.Server.Services instead of using a
  destination-bound per-request server as a surrogate session key.
- externally hosted servers (BuildDynamicServerOptions) share one explicit
  lazy fallback context instead of racing a last-attached field.

Request-bound OUTBOUND capabilities (sampling/elicitation/feedback) keep
flowing through the per-request AsyncLocal accessor — finer than the
session, unchanged.

Related to #70 (per-session DI scopes generalize the lifetime contract;
this context will construct from the session-scoped provider once both
merge).
…in SDK 2.0

The deprecation pragmas and the reference doc claimed the SEP-2322
multi-round-trip successor was 'not yet consumable in the SDK'; preview.3
actually ships it experimentally (MrtrContext/MrtrContinuation/MrtrExchange).
Reworded to 'shipped experimentally, not adopted by Repl yet' — adoption is
a follow-up under the compliance track.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement MCP Tasks runtime for .LongRunning() commands (ModelContextProtocol.Extensions.Tasks)

1 participant