Skip to content

Commit a079c0f

Browse files
jeffhandleyCopilot
andcommitted
Address PR feedback on conceptual docs
- Minimize cancellation doc to focus on MCP protocol behavior, not CancellationToken patterns - Simplify ping doc to a single API call example - Soften capabilities overview to not prescribe initialize handshake steps - Remove enum row from JSON Schema type mapping table - Replace file path examples with ID-based lookups to avoid path traversal - Use BlobResourceContents.FromBytes() factory consistently - Switch inline markdown links to reference-style Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent db099e0 commit a079c0f

File tree

6 files changed

+35
-123
lines changed

6 files changed

+35
-123
lines changed

docs/concepts/cancellation/cancellation.md

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -7,46 +7,18 @@ uid: cancellation
77

88
## Cancellation
99

10-
MCP supports [cancellation] of in-flight requests. Either side can cancel a previously issued request by using .NET's [task cancellation] with a `CancellationToken` to send a cancellation notification.
10+
MCP supports [cancellation] of in-flight requests. Either side can cancel a previously issued request, and `CancellationToken` parameters on MCP methods are wired to send and receive `notifications/cancelled` notifications over the protocol.
1111

1212
[cancellation]: https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation
1313
[task cancellation]: https://learn.microsoft.com/dotnet/standard/parallel-programming/task-cancellation
1414

15-
### Overview
15+
### How cancellation maps to MCP notifications
1616

17-
When a client cancels a pending request, a `notifications/cancelled` notification is sent to the server. The server's `CancellationToken` for that request is then triggered, allowing the server-side handler to stop work gracefully. This same mechanism works in reverse for server-to-client requests.
18-
19-
### Client-side cancellation
20-
21-
All client methods accept a `CancellationToken` parameter. Cancel a pending request by canceling the token:
22-
23-
```csharp
24-
using var cts = new CancellationTokenSource();
25-
26-
// Start a long-running tool call
27-
var toolTask = client.CallToolAsync(
28-
"long_running_operation",
29-
new Dictionary<string, object?> { ["duration"] = 60 },
30-
cancellationToken: cts.Token);
31-
32-
// Cancel after 5 seconds
33-
cts.CancelAfter(TimeSpan.FromSeconds(5));
34-
35-
try
36-
{
37-
var result = await toolTask;
38-
}
39-
catch (OperationCanceledException)
40-
{
41-
Console.WriteLine("Tool call was cancelled.");
42-
}
43-
```
44-
45-
When the `CancellationToken` is cancelled, a `notifications/cancelled` notification is automatically sent to the server with the request ID, allowing the server to stop processing.
17+
When a `CancellationToken` passed to a client method (such as <xref:ModelContextProtocol.Client.McpClient.CallToolAsync*>) is cancelled, a `notifications/cancelled` notification is sent to the server with the request ID. On the server side, the `CancellationToken` provided to the tool method is then triggered, allowing the handler to stop work gracefully. This same mechanism works in reverse for server-to-client requests.
4618

4719
### Server-side cancellation handling
4820

49-
Server tool methods should accept a `CancellationToken` parameter and check it during long-running operations:
21+
Server tool methods receive a `CancellationToken` that is triggered when the client sends a cancellation notification. Pass this token through to any async operations so they stop promptly:
5022

5123
```csharp
5224
[McpServerTool, Description("A long-running computation")]
@@ -56,17 +28,14 @@ public static async Task<string> LongComputation(
5628
{
5729
for (int i = 0; i < iterations; i++)
5830
{
59-
// Check for cancellation on each iteration
60-
cancellationToken.ThrowIfCancellationRequested();
61-
6231
await Task.Delay(1000, cancellationToken);
6332
}
6433

6534
return $"Completed {iterations} iterations.";
6635
}
6736
```
6837

69-
When the client sends a cancellation notification, the `CancellationToken` provided to the tool method is automatically triggered. The `OperationCanceledException` propagates back to the client as a cancellation response.
38+
When the client sends a cancellation notification, the `OperationCanceledException` propagates back to the client as a cancellation response.
7039

7140
### Cancellation notification details
7241

@@ -75,9 +44,9 @@ The cancellation notification includes:
7544
- **RequestId**: The ID of the request to cancel, allowing the receiver to correlate the cancellation with the correct in-flight request.
7645
- **Reason**: An optional human-readable reason for the cancellation.
7746

47+
Cancellation notifications can be observed by registering a handler:
48+
7849
```csharp
79-
// This is sent automatically when a CancellationToken is cancelled,
80-
// but you can also observe cancellation notifications:
8150
mcpClient.RegisterNotificationHandler(
8251
NotificationMethods.CancelledNotification,
8352
(notification, ct) =>

docs/concepts/capabilities/capabilities.md

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,7 @@ MCP uses a [capability negotiation] mechanism during connection initialization.
1313

1414
### Overview
1515

16-
When a client connects to a server, the initialization handshake includes:
17-
18-
1. The **client** sends an `initialize` request with its <xref:ModelContextProtocol.Protocol.ClientCapabilities> and protocol version.
19-
2. The **server** responds with its <xref:ModelContextProtocol.Protocol.ServerCapabilities> and the negotiated protocol version.
20-
3. The client sends an `initialized` notification to confirm the session is ready.
21-
22-
Both sides should check the other's capabilities before using optional features.
16+
During connection setup, clients and servers exchange their supported capabilities so each side can adapt its behavior accordingly. After initialization, both sides should check the other's capabilities before using optional features.
2317

2418
### Client capabilities
2519

@@ -119,9 +113,7 @@ if (client.ServerCapabilities.Completions is not null)
119113

120114
### Protocol version negotiation
121115

122-
The client specifies the MCP protocol version it supports in the `initialize` request. The server responds with the negotiated protocol version, which may differ from the client's requested version if the server supports an earlier version.
123-
124-
After initialization, the negotiated version is available on both sides:
116+
During connection setup, the client and server negotiate a mutually supported MCP protocol version. After initialization, the negotiated version is available on both sides:
125117

126118
```csharp
127119
// On the client

docs/concepts/ping/ping.md

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,45 +11,12 @@ MCP includes a [ping mechanism] that allows either side of a connection to verif
1111

1212
[ping mechanism]: https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/ping
1313

14-
### Overview
15-
16-
The ping operation is a simple request/response exchange. Either the client or the server can initiate a ping, and the other side responds automatically. Ping responses are handled automatically&mdash;callers only need to invoke the method to send a ping.
17-
1814
### Pinging from the client
1915

2016
Use the <xref:ModelContextProtocol.Client.McpClient.PingAsync*> method to verify the server is responsive:
2117

2218
```csharp
23-
await using var client = await McpClient.CreateAsync(transport);
24-
25-
try
26-
{
27-
await client.PingAsync();
28-
Console.WriteLine("Server is responsive.");
29-
}
30-
catch (OperationCanceledException)
31-
{
32-
Console.WriteLine("Ping timed out - server may be unresponsive.");
33-
}
34-
catch (McpException ex)
35-
{
36-
Console.WriteLine($"Ping failed: {ex.Message}");
37-
}
38-
```
39-
40-
A timeout can also be specified using a `CancellationToken`:
41-
42-
```csharp
43-
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
44-
45-
try
46-
{
47-
await client.PingAsync(cancellationToken: cts.Token);
48-
}
49-
catch (OperationCanceledException)
50-
{
51-
Console.WriteLine("Server did not respond within 5 seconds.");
52-
}
19+
await client.PingAsync(cancellationToken);
5320
```
5421

5522
### Automatic ping handling

docs/concepts/prompts/prompts.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,11 @@ public static IEnumerable<ChatMessage> AnalyzeImage(
9494
For protocol-specific content types like <xref:ModelContextProtocol.Protocol.EmbeddedResourceBlock>, use <xref:ModelContextProtocol.Protocol.PromptMessage> instead of `ChatMessage`. `PromptMessage` has a `Role` property and a single `Content` property of type <xref:ModelContextProtocol.Protocol.ContentBlock>:
9595

9696
```csharp
97-
[McpServerPrompt, Description("A prompt that includes a file resource")]
97+
[McpServerPrompt, Description("A prompt that includes a document resource")]
9898
public static IEnumerable<PromptMessage> ReviewDocument(
99-
[Description("Path to the document to review")] string path)
99+
[Description("The document ID to review")] string documentId)
100100
{
101-
string content = File.ReadAllText(path);
101+
string content = LoadDocument(documentId); // application logic to load by ID
102102
return
103103
[
104104
new PromptMessage
@@ -113,7 +113,7 @@ public static IEnumerable<PromptMessage> ReviewDocument(
113113
{
114114
Resource = new TextResourceContents
115115
{
116-
Uri = $"file:///{path}",
116+
Uri = $"docs://documents/{documentId}",
117117
MimeType = "text/plain",
118118
Text = content
119119
}

docs/concepts/resources/resources.md

Lines changed: 13 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -39,40 +39,25 @@ Template resources use [URI templates (RFC 6570)] with parameters. They are retu
3939

4040
```csharp
4141
[McpServerResourceType]
42-
public class FileResources
42+
public class DocumentResources
4343
{
44-
// Configure a root directory for all file:// resources
45-
private static readonly string RootDirectory = Path.GetFullPath(AppContext.BaseDirectory);
46-
47-
[McpServerResource(UriTemplate = "file:///{path}", Name = "File Resource")]
48-
[Description("Reads a file by its path within the configured root directory")]
49-
public static ResourceContents ReadFile(string path)
44+
[McpServerResource(UriTemplate = "docs://articles/{id}", Name = "Article")]
45+
[Description("Returns an article by its ID")]
46+
public static ResourceContents GetArticle(string id)
5047
{
51-
if (string.IsNullOrWhiteSpace(path))
52-
{
53-
throw new McpException("Path must be provided.");
54-
}
48+
string? content = LoadArticle(id); // application logic to load by ID
5549
56-
// Combine the requested path with the root directory and canonicalize it
57-
var fullPath = Path.GetFullPath(Path.Combine(RootDirectory, path));
58-
59-
// Ensure the final path is still under the allowed root directory
60-
if (!fullPath.StartsWith(RootDirectory, StringComparison.OrdinalIgnoreCase))
50+
if (content is null)
6151
{
62-
throw new McpException("Requested file path is outside the allowed directory.");
52+
throw new McpException($"Article not found: {id}");
6353
}
6454

65-
if (File.Exists(fullPath))
55+
return new TextResourceContents
6656
{
67-
return new TextResourceContents
68-
{
69-
Uri = $"file:///{path}",
70-
MimeType = "text/plain",
71-
Text = File.ReadAllText(fullPath)
72-
};
73-
}
74-
75-
throw new McpException($"File not found: {path}");
57+
Uri = $"docs://articles/{id}",
58+
MimeType = "text/plain",
59+
Text = content
60+
};
7661
}
7762
}
7863
```
@@ -83,7 +68,7 @@ Register resource types when building the server:
8368
builder.Services.AddMcpServer()
8469
.WithHttpTransport()
8570
.WithResources<MyResources>()
86-
.WithResources<FileResources>();
71+
.WithResources<DocumentResources>();
8772
```
8873

8974
### Reading text resources

docs/concepts/tools/tools.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,16 @@ Return an <xref:ModelContextProtocol.Protocol.EmbeddedResourceBlock> to embed a
8484
The resource can contain either text or binary data through <xref:ModelContextProtocol.Protocol.TextResourceContents> or <xref:ModelContextProtocol.Protocol.BlobResourceContents>:
8585

8686
```csharp
87-
[McpServerTool, Description("Returns a file as an embedded resource")]
88-
public static EmbeddedResourceBlock ReadFile(string path)
87+
[McpServerTool, Description("Returns a document as an embedded resource")]
88+
public static EmbeddedResourceBlock GetDocument()
8989
{
9090
return new EmbeddedResourceBlock
9191
{
9292
Resource = new TextResourceContents
9393
{
94-
Uri = $"file:///{path}",
94+
Uri = "docs://readme",
9595
MimeType = "text/plain",
96-
Text = File.ReadAllText(path)
96+
Text = "This is the document content."
9797
}
9898
};
9999
}
@@ -102,13 +102,13 @@ public static EmbeddedResourceBlock ReadFile(string path)
102102
For binary resources, use <xref:ModelContextProtocol.Protocol.BlobResourceContents>:
103103

104104
```csharp
105-
[McpServerTool, Description("Returns a binary file as an embedded resource")]
106-
public static EmbeddedResourceBlock ReadBinaryFile(string path)
105+
[McpServerTool, Description("Returns a binary resource")]
106+
public static EmbeddedResourceBlock GetBinaryData(string id)
107107
{
108+
byte[] data = LoadData(id); // application logic to load data by ID
108109
return new EmbeddedResourceBlock
109110
{
110-
Resource = BlobResourceContents.FromBytes(
111-
File.ReadAllBytes(path), $"file:///{path}", "application/octet-stream")
111+
Resource = BlobResourceContents.FromBytes(data, $"data://items/{id}", "application/octet-stream")
112112
};
113113
}
114114
```
@@ -291,7 +291,6 @@ Tool parameters are described using [JSON Schema 2020-12]. JSON schemas are auto
291291
| `int`, `long` | `integer` |
292292
| `float`, `double` | `number` |
293293
| `bool` | `boolean` |
294-
| `enum` | `string` with `enum` values |
295294
| Complex types | `object` with `properties` |
296295

297296
Use `[Description]` attributes on parameters to populate the `description` field in the generated schema. This helps LLMs understand what each parameter expects.

0 commit comments

Comments
 (0)