Skip to content

Implement SEP-2663 Tasks Extension#1579

Merged
PranavSenthilnathan merged 23 commits into
modelcontextprotocol:mainfrom
PranavSenthilnathan:new-tasks
Jun 11, 2026
Merged

Implement SEP-2663 Tasks Extension#1579
PranavSenthilnathan merged 23 commits into
modelcontextprotocol:mainfrom
PranavSenthilnathan:new-tasks

Conversation

@PranavSenthilnathan

@PranavSenthilnathan PranavSenthilnathan commented May 15, 2026

Copy link
Copy Markdown
Contributor

Closes #1573

Implement SEP-2663 Tasks Extension

Re-implements the experimental MCP tasks extension to track SEP-2663, replacing the SEP-1686 implementation removed in cec5d99. Long-running tool invocations can now be offloaded to a background task that the client polls via tasks/get, with cooperative cancellation and multi-round-trip input requests.

Experimental — gated behind MCPEXP001.

Highlights

  • Opt-in per request. A client adds the io.modelcontextprotocol/tasks key to a request''s _meta (under the SEP-2575 clientCapabilities.extensions envelope) to signal task support; the server must not return CreateTaskResult otherwise (enforced in McpServerImpl).
  • Drop-in server config. Set McpServerOptions.TaskStore = new InMemoryMcpTaskStore() and every [McpServerTool] invocation is automatically wrapped, with tasks/get/tasks/update/tasks/cancel handlers wired from the store. Explicit handlers in McpServerOptions.Handlers override any slot.
  • Transparent client polling. McpClient.CallToolAsync injects the extension marker, polls until terminal, dispatches input requests through the registered sampling/elicitation/roots handlers, and dedupes resolved keys across polls. CallToolRawAsync returns the raw ResultOrCreatedTask<T> for manual lifecycle management.
  • Cooperative cancellation. tasks/cancel transitions the task in the store and signals a per-task CancellationTokenSource so the tool''s CancellationToken observes the cancel. Disposal races are resolved with TryRemove.
  • Server-initiated requests inside a task. server.ElicitAsync/server.SampleAsync/server.RequestRootsAsync called from inside a tool scope are redirected through the store as input requests instead of direct JSON-RPC, surfaced to the client via InputRequiredTaskResult.InputRequests, and resumed via the store''s InputResponseReceived event when tasks/update arrives. Custom handlers can opt in via McpServer.CreateMcpTaskScope(taskId, store).

Public API surface

Protocol (ModelContextProtocol.Core/Protocol)

  • CreateTaskResult, GetTaskResult (+ Working/InputRequired/Completed/Cancelled/Failed subtypes)
  • UpdateTaskRequestParams/UpdateTaskResult, CancelTaskRequestParams/CancelTaskResult, GetTaskRequestParams
  • TaskStatusNotificationParams (+ 5 subtypes — scaffolding for SEP-2575 push notifications)
  • ResultOrCreatedTask<T> discriminator with implicit converters
  • McpTaskStatus enum (snake_case wire values)
  • McpExtensions.Tasks constant
  • Result.ResultType discriminator ("task" / "complete")

Server (ModelContextProtocol.Core/Server)

  • IMcpTaskStore + InMemoryMcpTaskStore (immutable record snapshots, lock-free CAS)
  • McpTaskInfo, InputResponseReceivedEventArgs
  • McpServerOptions.TaskStore
  • McpServerHandlers.{CallToolWithTaskHandler, GetTaskHandler, UpdateTaskHandler, CancelTaskHandler} (CallToolHandlerCallToolWithTaskHandler mutual exclusion enforced at set time)
  • McpServer.CreateMcpTaskScope(taskId, store)
  • McpServer.SendTaskStatusNotificationAsync (polymorphic notification dispatch for notifications/tasks/*)

Client (ModelContextProtocol.Core/Client)

  • McpClient.CallToolAsync (task-aware auto-polling)
  • McpClient.CallToolRawAsync (no auto-polling)
  • McpClient.GetTaskAsync / UpdateTaskAsync / CancelTaskAsync
  • McpClientOptions.MaxConsecutiveStuckPolls (configures the stuck-task detector threshold; default 60)

Safety nets baked in

  • Handler-set validation. Returning a CreateTaskResult without tasks/get wired throws InvalidOperationException at request time so misconfigured deployments fail loudly instead of producing unpollable tasks.
  • Stuck-task detector. CallToolAsync gives up after a configurable number of consecutive InputRequired polls with no new input request keys (default 60, tunable via McpClientOptions.MaxConsecutiveStuckPolls), issues a best-effort tasks/cancel, and throws McpException so a misbehaving server can''t trap the client in an unbounded poll loop.
  • Status semantics. Tools returning IsError = true produce Completed (per SEP) — Failed is reserved for JSON-RPC protocol errors, with the payload shaped as a JSON-RPC error object { code, message }.
  • Capability bypass inside a task scope. Elicit/Sample/RequestRoots skip the negotiated-capability checks when called inside a task scope, since the tasks extension itself is the negotiated capability (the client opted in via _meta).

Tests

  • 1953 ModelContextProtocol.Tests passing on .NET 10, 364 ModelContextProtocol.AspNetCore.Tests passing.
  • New test files: McpServerTaskTests, McpTaskStoreTests, McpServerTasksNoStoreTests, InMemoryMcpTaskStoreTests, McpClientTaskMethodsTests, TaskSerializationTests, TaskCancellationIntegrationTests, TaskHandlerConfigurationValidationTests, TaskPollStuckDetectorTests, TaskStoreOrphanedTaskTests.
  • Coverage includes wire-format round-trips for every GetTaskResult subtype, store CAS/idempotency, terminal-state transitions, isErrorCompleted mapping, capability bypass, mutual exclusion of CallToolHandler/CallToolWithTaskHandler, handler-set validation, stuck-detector cancellation flow with the configurable threshold, server→client notifications/tasks/* round-trip via SendTaskStatusNotificationAsync, roots/list redirection through the task store, no-store sync fallback when the client opts in but the server has none configured, and parity tests preserved/restored from the removed SEP-1686 implementation.

Sample

  • samples/TasksExtension/ runs a server (configured with InMemoryMcpTaskStore) and a client over an in-memory pipe in a single process. Demonstrates both CallToolAsync (auto-poll) and CallToolRawAsync (manual GetTaskAsync loop with full GetTaskResult subtype handling).

Documentation

  • Rewrote docs/concepts/tasks/tasks.md end-to-end: API examples, status/cancellation semantics, stuck-detector + configuration, custom store guidance (including strong-consistency requirements on CreateTaskAsync per SEP-2663 §306 and singleton-under-stateless-HTTP requirements), capability bypass, known limitations.
  • Expanded XML docs on IMcpTaskStore, the three task lifecycle handlers, McpTaskExecutionContext, McpClientOptions.MaxConsecutiveStuckPolls, and the CallToolAsync(string, …) overload.

Known limitations (carried forward)

  • Server-push task status notifications (SEP-2575) — SendTaskStatusNotificationAsync is plumbed end-to-end but auto-emission from the task wrapper is not wired yet; clients still rely on polling.
  • Lazy task creation — CreateTaskAsync is invoked eagerly even for tools that return inline.
  • Mid-execution promotion to task — [McpServerTool] methods can''t start sync and then escalate; use CallToolWithTaskHandler for that pattern.
  • ServerCapabilities.Extensions source-gen round-trip remains lossy for arbitrary object payloads.

# Conflicts:
#	docs/concepts/tasks/tasks.md
#	docs/list-of-diagnostics.md
#	src/ModelContextProtocol.Core/Client/McpClient.cs
#	src/ModelContextProtocol.Core/Client/McpClientImpl.cs
@PranavSenthilnathan PranavSenthilnathan marked this pull request as ready for review May 29, 2026 05:46
@PranavSenthilnathan PranavSenthilnathan changed the title [WIP] Implement SEP-2663 Tasks Extension Implement SEP-2663 Tasks Extension May 29, 2026
halter73 added a commit that referenced this pull request Jun 3, 2026
Reverts most of commit 18c0df7's removal of MrtrContext/MrtrContinuation/MrtrExchange, gating it to stateful sessions only. Tools calling ElicitAsync/SampleAsync/RequestRootsAsync under DRAFT-2026-v1 on stdio and stateful Streamable HTTP again transparently suspend the handler via TCS and emit InputRequiredResult to the client, with retries resumed via continuation lookup on requestState.

Stateless Streamable HTTP still requires explicit InputRequiredException for MRTR: the WrapHandlerWithMrtr gate skips the implicit machinery when !IsStatefulSession() and routes through InvokeWithInputRequiredResultHandlingAsync, which already throws when the client doesn't support MRTR on stateless.

Deferred-task related machinery (DeferredTask / DeferredTaskCreationResult / DeferTaskCreation / HandleDeferredTaskCreationAsync) is NOT restored. That work was superseded by SEP-2663 (PR #1579), which uses an entirely different API surface (McpServerOptions.TaskStore + per-request task metadata) and would just have to delete the restored SEP-1686 code during its rebase.

Test coverage restored: MrtrIntegrationTests (Client), MrtrHandlerLifecycleTests / MrtrMessageFilterTests / MrtrSessionLimitTests (Server), plus the deleted SessionDelete_* + RetryWithInvalidRequestState_* tests in MrtrProtocolTests and the Mrtr_ParallelAwaits theory rows in MapMcpTests.Mrtr.cs.

All previously wrapped methods (tools/call, prompts/get, resources/read) are wired through the MRTR interceptor again; updated InputRequiredResult XML doc accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merges upstream/main which includes the MRTR landing (SEP-2322, modelcontextprotocol#1458).

Replaces the tasks PR's ad-hoc IDictionary<string, JsonElement> input-request/response envelopes with MRTR's typed InputRequest/InputResponse DTOs. Wire format is unchanged; this simply reuses the shared MRTR types across the tasks/get, tasks/update, and notifications/tasks paths so the two extensions share a single set of typed types.

Conflict resolutions:

- McpServerImpl.cs: combined task cancellation infrastructure with MRTR continuations; rethrow InputRequiredException from BuildInitialCallToolFilter/BuildInitialTaskToolFilter so the MRTR backcompat resolver (InvokeWithInputRequiredResultHandlingAsync) can catch it.

- McpClientImpl.cs: collapsed the duplicate JsonElement-typed ResolveInputRequestsAsync into MRTR's typed override.

- McpServer.Methods.cs: SendRequestViaTaskAsync now stores an InputRequest and unwraps InputResponse via Deserialize<T>(typeInfo).

- UpdateTaskRequestParams: drops the redeclared InputResponses property and uses the inherited RequestParams.InputResponses from MRTR.

- Result.cs: includes the 'task' resultType value alongside MRTR's 'complete' and 'input_required'.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@halter73 halter73 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had an agent take a careful pass against SEP-2663, SEP-2575, and the MRTR landing in #1458. Overall this is in great shape. The MRTR integration is clean, the typed input-request/response flow reads well, and the roots/list dispatch you added in this revision closes the gap from our earlier conversation.

Highlights from the inline comments, in priority order:

  1. Wire-format _meta envelope (most important). SEP-2663 §51 / §58–62 says the per-request opt-in is the SEP-2575 envelope: _meta.io.modelcontextprotocol/clientCapabilities.extensions.io.modelcontextprotocol/tasks. We're currently writing and reading the bare _meta.io.modelcontextprotocol/tasks key on both sides, so two C# peers agree but a strictly-spec-shaped non-C# peer won't. Suggested change is symmetric on client (McpClient.Methods.cs) and server (McpServerImpl.cs); I'd also pair it with a small JSON-shape contract test (one that asserts the literal JSON path) so a future refactor can't silently regress to a bare key.
  2. Failed payload structure + JsonDocument lifetime. Two real bugs in the same five-line catch block in McpServerImpl.cs: the failed payload only emits {"message": ...} (SEP-2663 §186 says it MUST be a JSON-RPC error object with at least code), and the JsonDocument.Parse(...).RootElement is never using-disposed so its pooled buffer can be recycled while the element is still in the store. Inline suggestion fixes both.
  3. Roots/list tests. Production-side dispatch looks good — just needs a RootsTool fixture next to sample-tool and one RootsTool_ViaTask_RedirectsThroughStore test mirroring the existing sample/elicit pair. Inline suggestions provided.
  4. Two related task-composition failure modes — both should fail loudly instead of silently. A CallToolWithTaskHandler that returns IsTask = true inside a TaskStore wrapper orphans the store's pre-created task (client polls a task that never completes); and an InputRequiredException thrown from a [McpServerTool] inside a task wrapper becomes a Failed task with a misleading message. Both fixes are a few lines — turn the orphan into a SetFailedAsync with a clear payload (Comment 5), and add a dedicated catch (InputRequiredException) that produces a "MRTR + tasks composition isn't supported under [McpServerTool] yet — use CallToolWithTaskHandler" error (Comment 9). I filed #1635 for the broader composability story as a follow-up.
  5. IMcpTaskStore.CreateTaskAsync docs. SEP-2663 §306 imposes a strong-consistency requirement (tasks/get MUST resolve immediately after CreateTaskAsync returns). Worth adding a <remarks> paragraph so custom store implementers on eventually-consistent storage don't accidentally violate it.

Forward-compat notes from a parallel cross-check against #1610 (sessionless + handshake-less, SEP-2575/2567):

  • IMcpTaskStore lifetime under stateless HTTP. Once #1610 lands and flips HTTP to Stateless = true by default, each POST spins up a fresh server instance. CallToolAsync → CallToolRawAsync → PollTaskToCompletionAsync issues tasks/get as fresh POSTs, so IMcpTaskStore MUST be a singleton DI service (or backed by external storage) for the lookup to resolve across polls. Worth a sentence in docs/concepts/tasks/tasks.md and a class-level remarks paragraph on IMcpTaskStore itself.

Nit on the PR body: the "Known limitations" list still includes roots/list as input request — server emits, client doesn't dispatch yet, which is now stale given the dispatch you added.

Things I'd be happy to move to follow-up issues instead of holding this PR for:

  • Replacement sample for the removed samples/LongRunningTasks/ (the new doc page covers the concepts, but a runnable sample is more useful for ecosystem adoption).
  • A test that pins down what happens when a client sends the tasks _meta opt-in but the server has no TaskStore configured (currently silent fallback to sync — fine if intentional).
  • Behavioral coverage for SendTaskStatusNotificationAsync (today there's only round-trip serialization coverage — nothing verifies an actual server→client emission end-to-end, and nothing in the task wrapper auto-emits).
  • Make PollTaskToCompletionAsync's stuck-detector threshold (currently MaxConsecutiveStuckPolls = 60) configurable on McpClientOptions — useful for slow networks and debugging.
  • Once SEP-2575 server-push (subscriptions/listen / notifications/tasks) lands, wire auto-emit from the task wrapper so the client can opt out of polling.

Thanks!

Comment thread src/ModelContextProtocol.Core/Client/McpClient.Methods.cs
Comment thread src/ModelContextProtocol.Core/Server/McpServerImpl.cs
Comment thread src/ModelContextProtocol.Core/Server/McpServerImpl.cs Outdated
Comment thread tests/ModelContextProtocol.Tests/Server/McpTaskStoreTests.cs
Comment thread src/ModelContextProtocol.Core/Server/McpServerImpl.cs
Comment thread tests/ModelContextProtocol.Tests/Server/McpTaskStoreTests.cs
Comment thread src/ModelContextProtocol.Core/Server/IMcpTaskStore.cs
Comment thread src/ModelContextProtocol.Core/Server/McpServerImpl.cs
@halter73

halter73 commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

The CI is red, but my agent claims to know what's up:

root cause is test-only: tests use anonymous types with JsonSerializer.SerializeToElement(...) (no JsonTypeInfo), which falls back to reflection. The test project has JsonSerializerIsReflectionEnabledByDefault=false on net9.0 specifically, so all those tests fail there but pass on net8.0/net10.0/net472. Examples: McpServerTaskTests.cs:184 (_taskStore.FailTask(taskId, new { code = -32000, message = ... }) → flows into JsonSerializer.SerializeToElement(entry.Error) at line 521) and TaskSerializationTests.cs:141 (same shape). Easiest fix is to swap the anonymous types for JsonNode.Parse("""{...}""") or a small named record registered with [JsonSerializable].

PranavSenthilnathan and others added 10 commits June 9, 2026 17:15
Three related fixes to the task-store wrapper in McpServerImpl, all in
the same catch chain around L920-970:

modelcontextprotocol#2 - Failed payload shape + JsonDocument lifetime (SEP-2663 186)
- Register JsonRpcErrorDetail in McpJsonUtilities source-gen context.
- Replace JsonDocument.Parse(...).RootElement (leaks pooled buffer)
  with typed JsonRpcErrorDetail + SerializeToElement.
- Emit {code, message} per spec; McpProtocolException preserves its
  ErrorCode + Message (documented safe to propagate); all other
  exceptions redact to InternalError + generic message.
- Test: McpProtocolException_FromTool_StoresAsFailedWithJsonRpcErrorShape.

modelcontextprotocol#3 - Orphan-on-IsTask=true
- When TaskStore is configured AND CallToolWithTaskHandler returns
  IsTask=true, the store's pre-created task was left in Working
  forever. Now fails the store's task with a clear InternalError
  identifying the misconfiguration (use only one mechanism).
- New test class: TaskStoreOrphanedTaskTests.

modelcontextprotocol#4 - Dedicated InputRequiredException catch
- When [McpServerTool] throws InputRequiredException under the task
  wrapper, it fell through to the generic catch and surfaced as a
  misleading Failed task. The taskId was already returned to the
  client synchronously, so InputRequiredResult cannot be surfaced
  retroactively. Now fails the task with an actionable InvalidRequest
  message pointing the user to CallToolWithTaskHandler.
- Test: InputRequiredException_FromTool_FailsTaskWithActionableMessage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Docs: clarify IMcpTaskStore strong-consistency contract (SEP-2663 modelcontextprotocol#306)
  on CreateTaskAsync and document the singleton requirement under stateless
  HTTP; extend tasks.md custom-store requirements list to match (modelcontextprotocol#5, modelcontextprotocol#7).
- Tests: add RootsTool fixture + E2E test covering server-initiated
  roots/list redirection through the task store (modelcontextprotocol#6); add two E2E tests
  for SendTaskStatusNotificationAsync covering the Working/Completed and
  Failed branches (modelcontextprotocol#9); add McpServerTasksNoStoreTests pinning the silent
  sync fallback when a client opts into tasks but no TaskStore is
  configured (modelcontextprotocol#8).
- Rename CompletedTaskResult.TaskResult and CompletedTaskNotificationParams.TaskResult
  to Result for consistency with the JSON wire name. Wire format unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The stuck-in-InputRequired guard in PollTaskToCompletionAsync was
hard-coded to 60 consecutive polls. Expose it as McpClientOptions
.MaxConsecutiveStuckPolls (default 60, validated >= 1) so callers
can tune the effective wall-clock timeout against their server's
configured poll cadence (~MaxConsecutiveStuckPolls * pollIntervalMs).

- Added private protected abstract plumbing on McpClient with override
  in McpClientImpl that surfaces _options.MaxConsecutiveStuckPolls.
- PollTaskToCompletionAsync reads the option once at entry; both the
  threshold check and the exception message now reflect the configured
  value verbatim.
- Documented the option, default, and timeout formula in tasks.md.
- Tests: HonorsConfiguredThreshold (custom 3 surfaces "3 consecutive
  polls" in the McpException message), RejectsNonPositive theory
  (0, -1, MinValue), and DefaultsTo60.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous LongRunningTasks sample was removed in cec5d99 with the
prior SEP-1686 implementation. Add a fresh self-contained sample for
the SEP-2663 surface so external adopters have a runnable starting
point.

samples/TasksExtension/:
- Program.cs wires an in-process server + client over an in-memory
  Pipe (no external transport required). Server registers
  InMemoryMcpTaskStore and a single `run-report` tool; client
  invokes it twice — first via CallToolAsync (auto-poll) and then
  via CallToolRawAsync to demonstrate manual GetTaskAsync polling.
- README.md describes both paths, prints the expected console
  output, and points at docs/concepts/tasks/tasks.md for production
  guidance.
- Registered in ModelContextProtocol.slnx between
  QuickstartWeatherServer and TestServerWithHosting.

docs/concepts/tasks/tasks.md:
- Updated manual-poll comment `CompletedTaskResult by deserializing
  TaskResult` to `deserializing its Result property` after the
  rename in 0b8944f.
- Fixed the custom IMcpTaskStore.ResolveInputRequestsAsync signature
  in the implementer example from `IDictionary<string, JsonElement>`
  to the actual `IDictionary<string, InputResponse>`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two trivial doc-only conflicts in src/ModelContextProtocol.Core/Server/McpServer.Methods.cs from upstream PR modelcontextprotocol#1609 (Add diagnostics for messages dropped on the GET SSE stream), which added <remarks> blocks to SampleAsync and ElicitAsync about preferring RequestContext under Streamable HTTP. Resolution: accepted both upstream <remarks> blocks; kept our SampleAsync signature (ValueTask, non-async) which the prior MRTR merge (12c522f) deliberately reverted from async because SEP-2663 replaces MRTR's SendRequestWithTaskStatusTrackingAsync with our SendRequestViaTaskAsync.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The server emits the Working and Completed notifications in strict order
(each SendTaskStatusNotificationAsync awaits the transport write), but the
client-side McpSessionHandler dispatches each incoming message via a
fire-and-forget Task with a forced thread-pool yield, so user-registered
notification handlers may observe them out of receipt order. Net10's
thread-pool scheduling exposed this race intermittently.

Give the two notifications distinct LastUpdatedAt values on the server
and sort by that timestamp before asserting types/payloads, so the test
asserts the round-trip without depending on client-side dispatch order.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- tasks.md: ImmutableDictionary\\2 BCL xref does not resolve under docfx
  (no xref service configured for System.Collections.Immutable). Replace
  with inline code.
- stateless.md: correct namespace on IMcpTaskStore/InMemoryMcpTaskStore
  xrefs (ModelContextProtocol.Server.*, not ModelContextProtocol.*) and
  update the broken bookmark to ''tasks.md#fault-tolerant-task-implementations''
  (a heading from the old MRTR-era tasks.md that this PR replaces) to point
  at the SEP-2663 tasks.md section ''implementing-a-custom-task-store''.

Verified: dotnet docfx docs/docfx.json --warningsAsErrors true reports
0 warnings, 0 errors locally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…zed notification

The test asserts strict counts for both requests and notifications observed by
the incoming message filter, but ListToolsAsync only synchronizes on the
request/response exchanges (initialize, tools/list). The notifications/initialized
message in between is fire-and-forget on the client, and the server dispatches
each incoming message via McpSessionHandler.ProcessMessageAsync as a
fire-and-forget Task with a forced thread-pool yield. So the assertion can run
before the notification has even reached the filter pipeline, surfacing as
''Expected: 1, Actual: 0'' for the notification count.

Observed on Ubuntu/Release/net9.0 CI; reproduces under load on any target.

Apply the same TaskCompletionSource pattern already used by the immediately
following AddIncomingMessageFilter_Multiple_Filters_Execute_In_Order test:
set a TCS inside the filter when the InitializedNotification is observed,
and await it before snapshotting the counts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This PR replaces the experimental MRTR (SEP-1686) task APIs shipped in
ModelContextProtocol.Core 1.3.0 with the SEP-2663 tasks extension. NuGet
Package Validation (EnablePackageValidation=true,
PackageValidationBaselineVersion=1.3.0) flags the resulting type and member
removals as breaking changes (CP0001/CP0002/CP0011) and fails 'dotnet pack'
on Release.

Follow the established repo pattern (compare PR modelcontextprotocol#1368 "Add client completion
notification and details", which introduced a 60-line suppressions file in
the same PR that removed McpClient.Completion; that file was deleted in PR
modelcontextprotocol#1621 "Bump version to 2.0.0-preview.1" once the new baseline already
excluded the removed APIs).

Generated via:
  dotnet pack -c Release -p:ApiCompatGenerateSuppressionFile=true

The 320 entries (80 per TFM x 4 TFMs: net8.0, net9.0, net10.0, netstandard2.0)
are entirely MRTR types and members that were marked
[Experimental("MCPEXP001")] in 1.3.0 and are being replaced. A future PR that
bumps PackageValidationBaselineVersion past 2.0.0-preview.1 (once published)
should delete this file in the same way PR modelcontextprotocol#1621 deleted its predecessor.

Verified: 'dotnet pack -c Release' completes with 0 warnings / 0 errors.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/ModelContextProtocol.Core/CompatibilitySuppressions.xml

@halter73 halter73 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Thanks!

@PranavSenthilnathan PranavSenthilnathan merged commit dbb7a20 into modelcontextprotocol:main Jun 11, 2026
11 checks passed

@jeffhandley jeffhandley left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Submitting as post-merge feedback to consider.

/// <summary>
/// Gets or sets the default time-to-live in milliseconds for new tasks, or <see langword="null"/> for unlimited.
/// </summary>
public long? DefaultTtlMs { get; set; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm tempted to recommend the spelled-out DefaultTimeToLiveMs because DefaultTtlMs is tough to read, but I don't think my signal is strong enough to actually recommend it. Just a "worth considering for readability" suggestion instead.


/// <summary>
/// Provides an in-memory implementation of <see cref="IMcpTaskStore"/> for development and testing.
/// Provides an in-memory implementation of <see cref="IMcpTaskStore"/> for development and testing scenarios.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooh, interesting. If this is only for development/testing scenarios, should we conjure up a new package for dev/test scenarios to keep this kind of API out of the core/production packages?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it'd be worth it for this one type, and I cannot think of too many other types that would fit. Perhaps the System.IO.Stream-backed transports, StreamServerTransport and StreamClientTransport, but those aren't big either.

I see this as a little more like SignalR having an in-memory backplane by default except it require the developer using it to be even more explicit about relying on an in-memory store. I think that there are plenty of scenarios beyond dev and testing where you have a constrained number of concurrent tasks and it's fine to drop tasks when the process crashes. I see calling out development and testing scenarios as one of the main use cases though.

halter73 added a commit that referenced this pull request Jun 11, 2026
Brings in PR #1579 SEP-2663 Tasks (squash dbb7a20), SEP-990 Enterprise
Managed Authorization (8202bcc), SEP-2243 alignment (ed19286), ttlMs
renames in McpSessionHandler (711e5bb), and several quality-of-life
fixes that landed between the previous merge and today.

Conflict resolutions:
- src/ModelContextProtocol.Core/McpJsonUtilities.cs: keep both sides'
  JsonSerializable additions. Our draft additions (JsonElement,
  Implementation, ClientCapabilities, ServerCapabilities, LoggingLevel)
  coexist with origin/main's IDictionary<string,object> addition.
- src/ModelContextProtocol.Core/Protocol/NotificationMethods.cs: take
  origin/main's renamed TaskStatusNotification value ('notifications/tasks',
  formerly 'notifications/tasks/status') and the updated XML docs from
  PR #1579. Keep all our draft additions (RelatedTaskMetaKey,
  SubscriptionsAcknowledgedNotification, ProtocolVersionMetaKey,
  ClientInfoMetaKey, ClientCapabilitiesMetaKey, LogLevelMetaKey,
  SubscriptionIdMetaKey).
- tests/ModelContextProtocol.AspNetCore.Tests/HttpTaskIntegrationTests.cs:
  removed. Our pre-rebase tweak to the old SEP-1686 file is moot now
  that PR #1579's reimplementation deleted it; the new task tests live
  elsewhere.

PR #1579 author addressed the reconciliation items predicted in the
preview-merge analysis: '17f95f79 Fix _meta' nests tasks opt-in inside
the SEP-2575 capabilities envelope (preview commit 89295fb3 no longer
needed); '8b47086d Address PR feedback' fixes Failed task payload shape +
JsonDocument lifetime (preview commit 8817c9fc no longer needed);
'0b8944f9 Address PR feedback: docs' adds the IMcpTaskStore lifetime/
stateless docs (preview commit 072222db no longer needed). The only
preview reconciliation that may still be required is gating per-request
capability merge to stateful sessions only (preview commit 8b95d2ca),
which is evaluated separately after this merge by re-running
StatelessServerTests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
halter73 added a commit that referenced this pull request Jun 11, 2026
PR #1579's GetMetaWithTaskCapability writes a partial SEP-2575 capabilities
envelope (only `extensions.io.modelcontextprotocol/tasks`) on every
`tools/call`, regardless of negotiated protocol version. The server's
`CreateDraftStateSyncFilter` was treating the envelope as authoritative and
overwriting the session-scoped `_clientCapabilities` with the partial value -
wiping out whatever the initialize handshake had captured. Most visibly, a
legacy client that handshook with `Elicitation = new()` would lose
elicitation support the moment it issued a tools/call, and the back-compat
MRTR resolver would then fail with "Client does not support elicitation
requests".

Switch the per-request synchronization to a defensive merge that preserves
fields the envelope leaves null and additively merges extension keys. Gate the
merge behind `IsStatefulSession()` so per-request envelope state doesn't
leak into `_clientCapabilities` on stateless HTTP sessions (where
StatelessServerTests rely on the null invariant to surface "X is not
supported in stateless mode" errors).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
halter73 added a commit that referenced this pull request Jun 11, 2026
PR #1579 (SEP-2663) replaced the SEP-1686 McpTask type with CreateTaskResult
and switched its ttl/pollInterval to bare `long?` properties, so the
TimeSpanMillisecondsConverter no longer has a second consumer. The shared
regression suite cherry-picked from PR #1623 references the now-removed
McpTask type and stops compiling.

The converter's clamp-instead-of-throw branches are still fully exercised by
CacheableResultTests (oversized, large-negative, +Inf, -Inf round-trips), so
no coverage is lost. Drop the file.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

SEP-2663: Tasks Extension

4 participants