Skip to content

Commit 314bdb2

Browse files
jeffhandleyCopilot
andauthored
Refine 2.0.0 concept documentation (#1742)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 782b5bad-4046-49ec-93a6-72768b5b7521 Copilot-Session: 4b938335-418c-4ee7-8287-6c70d9148b89
1 parent 6e2f285 commit 314bdb2

17 files changed

Lines changed: 178 additions & 63 deletions

File tree

docs/concepts/capabilities/capabilities.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,11 @@ if (client.ServerCapabilities.Resources is { Subscribe: true })
8383
// Check if the server supports prompts with list-changed notifications
8484
if (client.ServerCapabilities.Prompts is { ListChanged: true })
8585
{
86-
mcpClient.RegisterNotificationHandler(
86+
client.RegisterNotificationHandler(
8787
NotificationMethods.PromptListChangedNotification,
8888
async (notification, ct) =>
8989
{
90-
var prompts = await mcpClient.ListPromptsAsync(cancellationToken: ct);
90+
var prompts = await client.ListPromptsAsync(cancellationToken: ct);
9191
});
9292
}
9393

docs/concepts/getting-started.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ builder.Services.AddMcpServer()
8787
{
8888
// Stateless mode is recommended for servers that don't need
8989
// server-to-client requests like sampling or elicitation.
90-
// See the Sessions documentation for details.
90+
// See the Stateless and Stateful documentation for details.
9191
options.Stateless = true;
9292
})
9393
.WithToolsFromAssembly();

docs/concepts/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ To install the SDK and build your first MCP client and server, see [Getting star
2020
| - | - |
2121
| [Capabilities](capabilities/capabilities.md) | Learn how client and server capabilities are negotiated during initialization, including protocol version negotiation. |
2222
| [Transports](transports/transports.md) | Learn how to configure stdio, Streamable HTTP, and SSE transports for client-server communication. |
23+
| [Stateless and Stateful](stateless/stateless.md) | Learn when to use stateless vs. stateful mode for HTTP servers and how to configure sessions. |
2324
| [Ping](ping/ping.md) | Learn how to verify connection health using the ping mechanism. |
2425
| [Progress tracking](progress/progress.md) | Learn how to track progress for long-running operations through notification messages. |
2526
| [Cancellation](cancellation/cancellation.md) | Learn how to cancel in-flight MCP requests using cancellation tokens and notifications. |
@@ -43,7 +44,6 @@ To install the SDK and build your first MCP client and server, see [Getting star
4344
| [Completions](completions/completions.md) | Learn how to implement argument auto-completion for prompts and resource templates. |
4445
| [Logging](logging/logging.md) | Learn how to implement logging in MCP servers and how clients can consume log messages. |
4546
| [Pagination](pagination/pagination.md) | Learn how to use cursor-based pagination when listing tools, prompts, and resources. |
46-
| [Stateless and Stateful](stateless/stateless.md) | Learn when to use stateless vs. stateful mode for HTTP servers and how to configure sessions. |
4747
| [HTTP Context](httpcontext/httpcontext.md) | Learn how to access the underlying `HttpContext` for a request. |
4848
| [MCP Server Handler Filters](filters.md) | Learn how to add filters to the handler pipeline. Filters let you wrap the original handler with additional functionality. |
4949

docs/concepts/logging/logging.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ MCP servers can expose log messages to clients through the [Logging utility].
1111

1212
[Logging utility]: https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging
1313

14+
> [!IMPORTANT]
15+
> Logging is **deprecated** as of MCP specification revision `2026-07-28` ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577), [MCP9005](xref:list-of-diagnostics#obsolete-apis)) and may be removed in a future version.
16+
1417
This document describes how to implement logging in MCP servers and how clients can consume log messages.
1518

1619
### Logging levels
@@ -32,8 +35,8 @@ different set of logging levels. The following table shows the levels and how th
3235
| `emergency` | | System is unusable | |
3336

3437
**Note:** .NET's [ILogger] also supports a `Trace` level (more verbose than Debug) log level.
35-
As there is no equivalent level in the MCP logging levels, Trace level logs messages are silently
36-
dropped when sending messages to the client.
38+
As there is no more verbose level in the MCP logging levels, Trace level log messages are mapped to
39+
the MCP `debug` level when sent to the client.
3740

3841
[ILogger]: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.ilogger
3942
[ILoggerProvider]: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.iloggerprovider

docs/concepts/logging/samples/server/Tools/LoggingTools.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public static async Task<string> LoggingTool(
1616
var progressToken = context.Params.ProgressToken;
1717
var stepDuration = duration / steps;
1818

19-
// <snippet_LoggingConfiguration >
19+
// <snippet_LoggingConfiguration>
2020
ILoggerProvider loggerProvider = context.Server.AsClientLoggerProvider();
2121
ILogger logger = loggerProvider.CreateLogger("LoggingTools");
2222
// </snippet_LoggingConfiguration>

docs/concepts/mrtr/mrtr.md

Lines changed: 116 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -247,20 +247,130 @@ public static string WizardTool(
247247
}
248248
```
249249

250-
### Providing custom error messages
250+
### Supporting down-level clients
251251

252-
When MRTR is not supported, you can provide domain-specific guidance:
252+
When <xref:ModelContextProtocol.Server.McpServer.IsMrtrSupported> is `false`, the tool
253+
can't obtain client input through an input request — but that doesn't have to be a dead
254+
end. Handle down-level and stateless callers along a spectrum, from a helpful message to a
255+
fully functional non-interactive path.
256+
257+
#### Return actionable guidance
258+
259+
At minimum, tell the caller how to proceed instead of failing opaquely. Explain what kind
260+
of session would enable the interactive path, and — when the tool exposes one — how to
261+
invoke the non-interactive path described below:
253262

254263
```csharp
255264
if (!server.IsMrtrSupported)
256265
{
257-
return "This tool requires interactive input. To use it:\n"
258-
+ "1. Connect with a client that negotiates MCP protocol revision 2026-07-28, or\n"
259-
+ "2. Use a stateful session using protocol revision 2025-11-25 so the server can resolve the input requests for you.\n"
260-
+ "\nStateless sessions using protocol revision 2025-11-25 and earlier cannot resolve MRTR input requests.";
266+
return "This tool needs interactive input. Connect with a client that negotiates MCP "
267+
+ "protocol revision 2026-07-28, or use a stateful session using revision 2025-11-25 "
268+
+ "so the server can resolve the input requests for you.";
269+
}
270+
```
271+
272+
#### Offer a non-interactive fallback
273+
274+
When a tool *can* complete without a prompt, expose an explicit argument that lets a
275+
down-level or stateless caller opt in directly. The tool stays usable everywhere: it
276+
elicits input when MRTR is available, and otherwise returns guidance the caller
277+
(or its model) can act on by resending with the argument set.
278+
279+
The tool below is just an example that has a sensible non-interactive fallback. It also
280+
shows how to branch on the elicitation **action** rather than assuming acceptance: the
281+
deserialized <xref:ModelContextProtocol.Protocol.ElicitResult> carries an
282+
<xref:ModelContextProtocol.Protocol.ElicitResult.Action> of `"accept"`, `"decline"`, or
283+
`"cancel"`, surfaced through the
284+
<xref:ModelContextProtocol.Protocol.ElicitResult.IsAccepted> shorthand.
285+
286+
```csharp
287+
[McpServerTool, Description("Closes a support ticket, recording why it was closed.")]
288+
public static string CloseSupportTicket(
289+
McpServer server,
290+
RequestContext<CallToolRequestParams> context,
291+
[Description("The ID of the ticket to close")] long ticketId,
292+
[Description("Why the ticket is being closed")] string? closeReason = null)
293+
{
294+
// Handles four client scenarios:
295+
// 1. Provided up-front: client sends `closeReason` in the initial call
296+
// 2. MRTR round-trip request: client confirms via `InputResponses["closeReason"]`
297+
// 3. MRTR initial request: server proposes a default reason and asks for confirmation
298+
// (with automatic SDK down-level bridge)
299+
// 4. Session-less down-level: server returns a guidance message requesting the reason up-front
300+
301+
// The default reason proposed to the caller and used if none is provided.
302+
string defaultCloseReason = "completed";
303+
304+
// (1) Provided up-front. Works on any client, including down-level session-less.
305+
// These requests are typically sent after (4) returns a guidance message.
306+
var confirmedReason = closeReason;
307+
308+
// (2) MRTR round-trip request. Works with native MRTR support or the automatic down-level
309+
// SDK bridge after (3) throws an `InputRequiredException` to request a `closeReason`.
310+
if (string.IsNullOrWhiteSpace(confirmedReason) &&
311+
context.Params?.InputResponses?.TryGetValue("closeReason", out var reasonResponse) is true)
312+
{
313+
var reasonResult = reasonResponse.Deserialize(InputResponse.ElicitResultJsonTypeInfo);
314+
315+
// Branch on the elicitation action: `decline` or `cancel` leaves the ticket open.
316+
if (reasonResult?.IsAccepted is not true) return "Ticket close cancelled";
317+
318+
// Accepted: use the reason the caller confirmed, falling back to the proposed default.
319+
confirmedReason = reasonResult.Content?.TryGetValue("closeReason", out var reasonValue) is true
320+
? reasonValue.GetString()
321+
: null;
322+
confirmedReason = string.IsNullOrWhiteSpace(confirmedReason) ? defaultCloseReason : confirmedReason;
323+
}
324+
325+
// (1) or (2) A reason is in hand; proceed with closing the ticket.
326+
if (!string.IsNullOrWhiteSpace(confirmedReason))
327+
return $"Closed ticket {ticketId}: {confirmedReason}";
328+
329+
// (3) MRTR initial request: propose "completed" as the default reason and ask the caller
330+
// to confirm (or adjust) it. This uses the 2026-07-28 MRTR input request, but the SDK
331+
// provides an automatic bridge to a legacy elicitation on a down-level, stateful
332+
// session. When the bridge can be provided, `server.IsMrtrSupported` is `true` and the
333+
// exception leads to a legacy elicitation response automatically.
334+
if (server.IsMrtrSupported)
335+
{
336+
throw new InputRequiredException(
337+
inputRequests: new Dictionary<string, InputRequest>
338+
{
339+
["closeReason"] = InputRequest.ForElicitation(new ElicitRequestParams
340+
{
341+
Message = $"Close ticket '{ticketId}'? Accept the default reason or provide your own.",
342+
RequestedSchema = new()
343+
{
344+
Properties =
345+
{
346+
["closeReason"] = new ElicitRequestParams.StringSchema
347+
{
348+
Title = "Close reason",
349+
Description = "The reason for closing the ticket",
350+
Default = defaultCloseReason,
351+
},
352+
},
353+
},
354+
})
355+
},
356+
requestState: ticketId.ToString()); // opaque; echoed back to us on the retry
357+
}
358+
359+
// (4) Down-level and stateless: we can't prompt an elicitation through an MRTR
360+
// round-trip request or an elicitation. Return a natural language response
361+
// with guidance for providing the reason up-front.
362+
return "Closing a ticket requires a reason. Resend with `closeReason`.";
261363
}
262364
```
263365

366+
> [!IMPORTANT]
367+
> Form-mode elicitations must set `RequestedSchema`. This tool proposes a default, so its
368+
> schema declares an optional `closeReason` string with a `Default` the caller can accept or
369+
> override. A confirmation prompt that collects no fields still needs a schema — set
370+
> `RequestedSchema = new()` (an empty object schema): it's accepted by native `2026-07-28` MRTR
371+
> and **required** by the down-level stateful elicitation bridge under `2025-11-25`, which
372+
> otherwise throws `ArgumentException: "Form mode elicitation requests require a requested schema."`.
373+
264374
## Compatibility
265375

266376
The SDK supports `InputRequiredException` across two protocol revisions and two session modes:

docs/concepts/progress/samples/server/Tools/LongRunningTools.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static async Task<string> LongRunningTool(
2222
{
2323
await Task.Delay(stepDuration * 1000);
2424

25-
// <snippet_SendProgress >
25+
// <snippet_SendProgress>
2626
if (progressToken is not null)
2727
{
2828
await server.SendNotificationAsync("notifications/progress", new ProgressNotificationParams
@@ -36,7 +36,7 @@ public static async Task<string> LongRunningTool(
3636
},
3737
});
3838
}
39-
// </snippet_SendProgress >
39+
// </snippet_SendProgress>
4040
}
4141

4242
return $"Long running tool completed. Duration: {duration} seconds. Steps: {steps}.";

docs/concepts/roots/roots.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ MCP [roots] allow clients to inform servers about the relevant locations in the
1111

1212
[roots]: https://modelcontextprotocol.io/specification/2025-11-25/client/roots
1313

14+
> [!IMPORTANT]
15+
> Roots are **deprecated** as of MCP specification revision `2026-07-28` ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577), [MCP9005](xref:list-of-diagnostics#obsolete-apis)) and may be removed in a future version.
16+
1417
### Overview
1518

1619
Roots provide a mechanism for the client to tell the server which directories, projects, or repositories are relevant to the current session. A server might use roots to:
@@ -57,7 +60,7 @@ await using var client = await McpClient.CreateAsync(transport, options);
5760

5861
### Requesting roots from the server
5962

60-
Servers can request the client's root list using <xref:ModelContextProtocol.Server.McpServer.RequestRootsAsync*>. This is a server-to-client request, so it requires [stateful mode or stdio](xref:stateless) — it is not available in [stateless mode](xref:stateless#stateless-mode-recommended).
63+
Servers can request the client's root list using <xref:ModelContextProtocol.Server.McpServer.RequestRootsAsync*>. This is a server-to-client request, so it requires [stateful mode or stdio](xref:stateless) — it is not available in [stateless mode](xref:stateless#stateless-mode-recommended). For a stateless-compatible alternative, throw `InputRequiredException` from your handler through MRTR — see [Multi round-trip requests (MRTR)](#multi-round-trip-requests-mrtr).
6164

6265
```csharp
6366
[McpServerTool, Description("Lists the user's project roots")]

docs/concepts/sampling/sampling.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ MCP [sampling] allows servers to request LLM completions from the client. This e
1111

1212
[sampling]: https://modelcontextprotocol.io/specification/2025-11-25/client/sampling
1313

14+
> [!IMPORTANT]
15+
> Sampling is **deprecated** as of MCP specification revision `2026-07-28` ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577), [MCP9005](xref:list-of-diagnostics#obsolete-apis)) and may be removed in a future version.
16+
1417
> [!NOTE]
15-
> Sampling is a **server-to-client request** — the server sends a request back to the client over an open connection. This requires [stateful mode or stdio](xref:stateless). Sampling is not available in [stateless mode](xref:stateless#stateless-mode-recommended) because stateless servers cannot send requests to clients.
18+
> Sampling is a **server-to-client request** — the server sends a request back to the client over an open connection. This requires [stateful mode or stdio](xref:stateless). Sampling is not available in [stateless mode](xref:stateless#stateless-mode-recommended) because stateless servers cannot send requests to clients. For a stateless-compatible alternative, throw `InputRequiredException` from your handler through MRTR — see [Multi round-trip requests (MRTR)](#multi-round-trip-requests-mrtr).
1619
1720
### How sampling works
1821

docs/concepts/tasks/tasks.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,6 @@ Tasks are provided by the `ModelContextProtocol.Extensions.Tasks` package and re
1414
version `2026-07-28` or later. The implementation follows
1515
[SEP-2663 (Tasks Extension)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2663-tasks-extension.md).
1616

17-
### Compatibility with v1 experimental Tasks
18-
19-
The Tasks extension in v2.0.0 replaces the experimental Tasks implementation shipped in v1.3.0 and
20-
v1.4.x. The implementations are not compatible at either the API or protocol level. A v2 Tasks
21-
client or server must use a connection negotiated to `2026-07-28` or later; it cannot fall back to
22-
the down-level implementation.
23-
24-
On a connection negotiated to the down-level `2025-11-25` protocol:
25-
26-
- A v2 client calling a v1 server receives an ordinary tool result. The v2 client does not opt in
27-
to the down-level Tasks protocol, and `GetTaskAsync` rejects use before a `2026-07-28`
28-
connection is negotiated.
29-
- A v1 client calling a v2 server likewise receives an ordinary tool result. The v2 server does
30-
not create Tasks on a down-level connection, and its `tasks/get` endpoint rejects the legacy
31-
request with a method-not-found error.
32-
33-
Upgrade both peers to the v2 Tasks extension before using Tasks. The extension provides no
34-
compatibility bridge for the previous experimental API.
35-
3617
### Overview
3718

3819
A client opts into tasks on a per-request basis by including the `io.modelcontextprotocol/tasks`
@@ -346,6 +327,25 @@ scope, the SDK intentionally skips the normal client-capability negotiation chec
346327
the client opted in by including the extension marker in the originating request, so it's
347328
responsible for handling — or rejecting — the input requests surfaced through `tasks/get`.
348329

330+
#### Compatibility with v1 experimental Tasks
331+
332+
The Tasks extension in v2.0.0 replaces the experimental Tasks implementation shipped in v1.3.0 and
333+
v1.4.x. The implementations are not compatible at either the API or protocol level. A v2 Tasks
334+
client or server must use a connection negotiated to `2026-07-28` or later; it cannot fall back to
335+
the down-level implementation.
336+
337+
On a connection negotiated to the down-level `2025-11-25` protocol:
338+
339+
- A v2 client calling a v1 server receives an ordinary tool result. The v2 client does not opt in
340+
to the down-level Tasks protocol, and `GetTaskAsync` rejects use before a `2026-07-28`
341+
connection is negotiated.
342+
- A v1 client calling a v2 server likewise receives an ordinary tool result. The v2 server does
343+
not create Tasks on a down-level connection, and its `tasks/get` endpoint rejects the legacy
344+
request with a method-not-found error.
345+
346+
Upgrade both peers to the v2 Tasks extension before using Tasks. The extension provides no
347+
compatibility bridge for the previous experimental API.
348+
349349
### Known limitations
350350

351351
- **Server-push task status notifications (SEP-2575)**: not yet implemented. Clients rely on

0 commit comments

Comments
 (0)