modelcontextprotocol / modelcontextprotocol/csharp-sdk
Restore `[McpServerTool]` composability with MRTR input requests + tasks
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 4.5k
- Forks
- 814
- Avg merge
- 9d 19h
- Merged PRs (30d)
- 4
Description
Context
SEP-2663 (Tasks extension) is being implemented in #1579 and SEP-2322 (MRTR) landed in #1458. Both extensions let a tool ask the user for input mid-execution, but via different protocol mechanisms:
- MRTR:
tools/callround-trips — the server returns aninput_requiredresult, the client re-sendstools/callwith input responses, the server replays the handler. - Tasks:
tasks/update— the server sets the task tostatus = input_requiredwith the pending input requests, the client sendstasks/updatewith input responses, the task body'sElicitAsync/SampleAsync/RequestRootsAsyncawait resumes.
End users writing [McpServerTool] methods don't know (and shouldn't need to know) which mechanism the client is using. Today, after #1579 lands, two composition cases break:
Case 1: [McpServerTool] + tasks _meta opt-in + input requests inside the method body
A [McpServerTool] method that calls ElicitAsync/SampleAsync/RequestRootsAsync works when invoked synchronously (MRTR translates the call into a tools/call round-trip). But when the client signals the tasks opt-in (SEP-2663 §51 _meta envelope), the SDK pre-creates a task and runs the method body in Task.Run. Inside the body, the MRTR backcompat resolver throws InputRequiredException. The task wrapper's generic catch (Exception ex) at McpServerImpl.cs:933 turns this into a Failed task with the literal exception message — no hint that "MRTR can't compose with tasks under this wrapper".
The SEP-1686-era SDK supported this case via the AutomaticInputRequiredStatusTests test class (now deleted in cec5d998), which asserted that ElicitAsync/SampleAsync inside a task body auto-transitioned the task to InputRequired status. SEP-2663 preserves this capability at the protocol level (InputRequiredTaskResult + UpdateTaskRequestParams.InputResponses) but the new task wrapper doesn't wire it through.
Case 2: Sync [McpServerTool] that needs to escalate to a task mid-execution
A [McpServerTool] method may run synchronously for "most" requests but occasionally need to do long-running work. Today, the only way to support this is to write a CallToolWithTaskHandler that drives the task lifecycle manually. The pre-#1458 SDK had a DeferTaskCreation opt-in on [McpServerTool] that I removed in #1458 to keep the MRTR PR minimal — so this capability is currently absent.
Restoring something like DeferTaskCreation would let the method run sync first, then call e.g. context.PromoteToTaskAsync() to create a task and detach.
Proposed approach
Design and implement a unified composition story that addresses both cases. Sketch (not prescriptive — the right design needs more thought):
-
Task-aware MRTR for Case 1. When MRTR sees that it's running inside a task scope (via
McpTaskExecutionContext, which already exists atsrc/ModelContextProtocol.Core/Server/McpTaskExecutionContext.cs), translateElicitAsync/SampleAsync/RequestRootsAsyncinto task-protocol input requests (taskStore.SetInputRequestsAsync(...)and awaittasks/updateresume) instead of MRTR round-trips. The deletedAutomaticInputRequiredStatusTestsis the behavioral contract to restore. -
DeferTaskCreationfor Case 2. Re-introduce the[McpServerTool(DeferTaskCreation = true)]opt-in (or an equivalentMcpServerToolCreateOptionsshape) plus a context API (context.PromoteToTaskAsync(McpTaskInfo?)or similar) that the method can call to escalate. -
Documentation in
docs/concepts/tasks/tasks.mdcovering both patterns with runnable examples.
Open design questions:
- Should
DeferTaskCreationbe the default for async[McpServerTool]methods, or stay opt-in? (Default-on is more ergonomic but changes existing behavior.) - How should
McpTaskExecutionContextbe discovered from within MRTR —AsyncLocal, parameter injection, or something else? - What does Case 1 look like when the client supports neither the tasks extension nor MRTR? (Probably: today's behavior, error returned.)
Out of scope
- Mid-flight transport switching (sticky session vs. resumable HTTP) — orthogonal, blocked on SEP-2575/2567 / #1610.
- Cross-process task store handoff for
DeferTaskCreation—IMcpTaskStorealready abstracts this.
Related
- #1458 — MRTR (removed the original
DeferTaskCreationto keep scope minimal) - #1579 — SEP-2663 Tasks extension
- #1610 — sessionless + handshake-less draft protocol (SEP-2575 + SEP-2567)
- SEP-2663 §51 (per-request
_metaopt-in envelope), §306 (durability), §186 (failed.error shape) - Deleted test (SEP-1686-era behavior contract for Case 1):
tests/ModelContextProtocol.Tests/Server/AutomaticInputRequiredStatusTests.csremoved in commitcec5d998
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/ModelContextProtocol.Core/Server/McpTaskExecutionContext.cs, the task wrapper around McpServerImpl.cs:933, and the deleted tests/ModelContextProtocol.Tests/Server/AutomaticInputRequiredStatusTests.cs to understand the existing behavior. Define and validate task-aware MRTR input handling and synchronous task promotion, then document both patterns with runnable examples in docs/concepts/tasks/tasks.md.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend-api-design
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100