Skip to content

Commit 4c9c8fb

Browse files
halter73Copilot
andcommitted
Reorder sessions.md: practical sections first, deep-dives last
Move Security up after Server configuration. Promote DI scopes to its own top-level section. Cancellation/disposal and Advanced features stay at the bottom. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 04d825d commit 4c9c8fb

File tree

1 file changed

+22
-26
lines changed

1 file changed

+22
-26
lines changed

docs/concepts/sessions/sessions.md

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -328,12 +328,28 @@ builder.Services.AddMcpServer()
328328
})
329329
.WithTools<DefaultTools>();
330330
```
331+
## Security
332+
333+
### User binding
334+
335+
When authentication is configured, the server automatically binds sessions to the authenticated user. This prevents one user from hijacking another user's session.
336+
337+
#### How it works
338+
339+
1. When a session is created, the server captures the authenticated user's identity from `HttpContext.User`
340+
2. The server extracts a user ID claim in priority order:
341+
- `ClaimTypes.NameIdentifier` (`http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier`)
342+
- `"sub"` (OpenID Connect subject claim)
343+
- `ClaimTypes.Upn` (`http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn`)
344+
3. On each subsequent request, the server validates that the current user matches the session's original user
345+
4. If there's a mismatch, the server responds with `403 Forbidden`
331346

332-
### Service lifetimes and DI scopes
347+
This binding is automatic — no configuration is needed. If no authentication middleware is configured, user binding is skipped (the session is not bound to any user).
348+
## Service lifetimes and DI scopes
333349

334350
How the server resolves scoped services depends on the transport and session mode. The <xref:ModelContextProtocol.Server.McpServerOptions.ScopeRequests> property controls whether the server creates a new `IServiceProvider` scope for each handler invocation.
335351

336-
#### Stateful HTTP
352+
### Stateful HTTP
337353

338354
In stateful mode, the server's <xref:ModelContextProtocol.Server.McpServer.Services> is the application-level `IServiceProvider` — not a per-request scope. Because the server outlives individual HTTP requests, <xref:ModelContextProtocol.Server.McpServerOptions.ScopeRequests> defaults to `true`: each handler invocation (tool call, resource read, etc.) creates a new scope.
339355

@@ -343,7 +359,7 @@ This means:
343359
- **Singleton services** resolve from the application container as usual
344360
- **Transient services** create a new instance per resolution, as usual
345361

346-
#### Stateless HTTP
362+
### Stateless HTTP
347363

348364
In stateless mode, the server uses ASP.NET Core's per-request `HttpContext.RequestServices` as its service provider, and <xref:ModelContextProtocol.Server.McpServerOptions.ScopeRequests> is automatically set to `false`. No additional scopes are created — handlers share the same HTTP request scope that middleware and other ASP.NET Core components use.
349365

@@ -352,11 +368,11 @@ This means:
352368
- **Scoped services** behave exactly like any other ASP.NET Core request-scoped service — middleware can set state on a scoped service and the tool handler will see it
353369
- The DI lifetime model is identical to a standard ASP.NET Core controller or minimal API endpoint
354370

355-
#### stdio
371+
### stdio
356372

357373
The stdio transport creates a single server for the lifetime of the process. The server's <xref:ModelContextProtocol.Server.McpServer.Services> is the application-level `IServiceProvider`. By default, <xref:ModelContextProtocol.Server.McpServerOptions.ScopeRequests> is `true`, so each handler invocation gets its own scope — the same behavior as stateful HTTP.
358374

359-
#### McpServer.Create (custom transports)
375+
### McpServer.Create (custom transports)
360376

361377
When you create a server directly with <xref:ModelContextProtocol.Server.McpServer.Create*>, you control the `IServiceProvider` and transport yourself. If you pass an already-scoped provider, you can set <xref:ModelContextProtocol.Server.McpServerOptions.ScopeRequests> to `false` to avoid creating redundant nested scopes. The [InMemoryTransport sample](https://github.com/modelcontextprotocol/csharp-sdk/blob/51a4fde4d9cfa12ef9430deef7daeaac36625be8/samples/InMemoryTransport/Program.cs#L6-L14) shows a minimal example of using `McpServer.Create` with in-memory pipes:
362378

@@ -375,15 +391,14 @@ await using McpServer server = McpServer.Create(
375391
serviceProvider: scope.ServiceProvider);
376392
```
377393

378-
#### DI scope summary
394+
### DI scope summary
379395

380396
| Mode | Service provider | ScopeRequests | Handler scope |
381397
|------|-----------------|---------------|---------------|
382398
| **Stateful HTTP** | Application services | `true` (default) | New scope per handler invocation |
383399
| **Stateless HTTP** | `HttpContext.RequestServices` | `false` (forced) | Shared HTTP request scope |
384400
| **stdio** | Application services | `true` (default, configurable) | New scope per handler invocation |
385401
| **McpServer.Create** | Caller-provided | Caller-controlled | Depends on `ScopeRequests` and whether the provider is already scoped |
386-
387402
## Cancellation and disposal
388403

389404
Every tool, prompt, and resource handler receives a `CancellationToken`. The source and behavior of that token depends on the transport and session mode. The SDK also supports the MCP [cancellation protocol](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation) for client-initiated cancellation of individual requests.
@@ -429,25 +444,6 @@ For stateless servers, shutdown is even simpler: each request is independent, so
429444
### Stateless per-request logging
430445

431446
In stateless mode, each HTTP request creates and disposes a short-lived `McpServer` instance. This produces session lifecycle log entries at `Trace` level (`session created` / `session disposed`) for every request. These are typically invisible at default log levels but may appear when troubleshooting with verbose logging enabled. There is no user-facing `initialize` handshake in stateless mode — the SDK handles the per-request server lifecycle internally.
432-
433-
## Security
434-
435-
### User binding
436-
437-
When authentication is configured, the server automatically binds sessions to the authenticated user. This prevents one user from hijacking another user's session.
438-
439-
#### How it works
440-
441-
1. When a session is created, the server captures the authenticated user's identity from `HttpContext.User`
442-
2. The server extracts a user ID claim in priority order:
443-
- `ClaimTypes.NameIdentifier` (`http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier`)
444-
- `"sub"` (OpenID Connect subject claim)
445-
- `ClaimTypes.Upn` (`http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn`)
446-
3. On each subsequent request, the server validates that the current user matches the session's original user
447-
4. If there's a mismatch, the server responds with `403 Forbidden`
448-
449-
This binding is automatic — no configuration is needed. If no authentication middleware is configured, user binding is skipped (the session is not bound to any user).
450-
451447
## Advanced features
452448

453449
### Session migration

0 commit comments

Comments
 (0)