Add MCP003 analyzer: warn when WithHttpTransport doesn't set Stateless#1471
Open
Add MCP003 analyzer: warn when WithHttpTransport doesn't set Stateless#1471
Conversation
…r messages Recommend stateless mode as the default for HTTP-based MCP servers across documentation, samples, and error messages. Docs: - Add comprehensive sessions conceptual doc covering stateless (recommended), stateful, and stdio session behaviors - Update getting-started, transports, filters, and other conceptual docs to use stateless mode in examples - Add Sampling to docs table of contents - Clarify ConfigureSessionOptions runs per-request in stateless mode Samples: - Convert ProtectedMcpServer to stateless mode - Add comments to AspNetCoreMcpServer and EverythingServer explaining why they require sessions Error messages: - Improve missing Mcp-Session-Id errors to suggest stateless mode and link to session documentation Tests: - Add tests for progress notifications and ConfigureSessionOptions in stateless mode - Verify error messages reference stateless mode guidance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix relative links to sessions doc from subdirectories - Fix doc URLs in error messages to use .html extension - Strengthen ConfigureSessionOptions test with two requests proving per-request behavior Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
TokenProgress.Report() uses fire-and-forget (no await), so in stateless mode the SSE stream can close before notifications flush. Rewrite the test using TCS coordination: the tool reports progress then waits, giving the notification time to flush before the stream closes. A SynchronousProgress<T> helper avoids the thread pool posting race inherent to Progress<T>. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add quick stateless-vs-stateful decision guide and explain why stateless is recommended but not the default. Document the lack of handler backpressure as a deployment footgun for stateful mode. Normalize cross-doc links to use xref instead of relative paths. Also document stale HttpContext risk with SSE transport.
Every WithHttpTransport() call in samples and docs now explicitly sets Stateless = true or Stateless = false. This prepares for a potential future default change and makes the intent clear in code users may copy. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add 'Service lifetimes and DI scopes' section to sessions.md covering how ScopeRequests controls per-handler scoping in stateful HTTP, how stateless HTTP reuses ASP.NET Core's request scope, and how stdio defaults to per-handler scoping but is configurable. Includes summary table and cross-link from the stdio section. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Group sections by purpose: mode selection (stateless/stateful/comparison), transport details (HTTP lifecycle, deployment considerations, stdio), server configuration (options, ConfigureSessionOptions, DI scopes), security (user binding), and advanced features (migration, resumability). Move comparison table near the decision tree. Move deployment footguns under HTTP transport. Move stateless trade-offs into the stateless section. Combine 'When to use stateful' and 'When stateful shines'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Any active HTTP request (POST or GET) prevents a session from being counted as idle, not just GET/SSE. Fix docs and API comment on MaxIdleSessionCount. Also remove redundant 'async' from 'async scope' in DI documentation since nearly all ASP.NET Core scopes are async. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Split Streamable HTTP into stateless and stateful columns, fix SSE server example that incorrectly showed Stateless = true (SSE endpoints are not mapped in stateless mode), and add cross-reference to sessions doc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Cover why SSE requires stateful mode, the query string session ID mechanism, connection-bound session lifetime via HttpContext.RequestAborted, and clarify that idle timeout, max idle count, and activity tracking are Streamable HTTP specific. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Document per-transport handler cancellation tokens, client-initiated cancellation via notifications/cancelled, McpServer disposal guarantees (awaits in-flight handlers), graceful ASP.NET Core shutdown behavior, stdio process lifecycle, and stateless per-request logging. Add cross-reference from transports.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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>
Cover how tasks work in stateless vs stateful mode, the tasks/cancel vs notifications/cancelled distinction, session-scoped task isolation, and OpenTelemetry integration (mcp.session.id tag, session/operation duration histograms, distributed tracing via _meta). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Explain that handler CTS is session-scoped (not request-scoped) in stateful mode, making this a standard persistent-connection concern rather than an MCP-specific safety issue. Clarify that stateless mode avoids this because DisposeAsync awaits handlers within the HTTP request lifetime. Recommend standard HTTP protections alongside process isolation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the prominent WARNING callout with a nuanced 'Request backpressure' section that explains how each configuration is actually protected: - Default stateful: POST held open until handler responds, bounded by HTTP/2 MaxStreamsPerConnection (100) — same model as gRPC unary - EventStreamStore: advanced opt-in that frees POST early via EnablePollingAsync, removing HTTP-level backpressure - Tasks (experimental): fire-and-forget Task.Run returns task ID immediately, no HTTP backpressure on handlers - Stateless: handler lifetime = request lifetime, best protection Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SSE POST to /message returns 202 immediately, so handlers have no HTTP-level backpressure — same fire-and-forget dispatch pattern as all other modes. The GET stream provides handler cancellation on disconnect (cleanup) but not concurrency limiting. Note the SignalR parallel: both have connection-bound session lifetime, but SignalR also has MaximumParallelInvocationsPerClient (default: 1). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Legacy SSE endpoints (/sse and /message) are now disabled by default because the SSE transport has no built-in HTTP-level backpressure -- POST returns 202 Accepted immediately without waiting for handler completion. This means default stateful and stateless modes now provide identical backpressure characteristics. To opt in, set HttpServerTransportOptions.EnableLegacySse = true (marked [Obsolete] with MCP9003) or use the AppContext switch ModelContextProtocol.AspNetCore.EnableLegacySse. SSE endpoints remain always disabled in stateless mode regardless of this setting. Update sessions.md, transports.md, and list-of-diagnostics.md to document the change, and migrate HttpTaskIntegrationTests to use Streamable HTTP since they were only incidentally using SSE. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
IIS and HTTP.sys also enforce MaxStreamsPerConnection and request timeouts, so the backpressure discussion should not be Kestrel-specific. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clarify that legacy SSE sessions are not subject to IdleTimeout or MaxIdleSessionCount — their lifetime is tied to the GET /sse request. Add backpressure remark to SseResponseStreamTransport warning callers about the lack of HTTP-level backpressure when POST returns immediately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add client-side session behavior documentation covering session lifecycle, expiry detection, reconnection patterns, and transport options. Move Sessions to Base Protocol in toc.yml. Add session cross-references to sampling, roots, tools, prompts, progress, and cancellation docs. Restructure sessions.md: merge redundant stateless sections, promote Tasks, Request backpressure, and Observability to top-level sections, move client section before Advanced features, and fold stream reconnection into lifecycle. Add reconnection integration test (Client_CanReconnect_AfterSessionExpiry) using MapMcp with middleware to simulate 404 session expiry. Clarify the two session ID concepts: transport session ID (McpSession.SessionId, shared between client and server) vs telemetry session ID (mcp.session.id tag, per-instance). Document that middleware should read Mcp-Session-Id from the response header after await next() to capture it on the initialize request. Initialize EnableLegacySse from AppContext switch in property initializer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…vior Rewrite sessions.md intro to lead with the Stateless property recommendation, clarify that sessions enabled is the current C# SDK default (not a protocol requirement), and note the spec requires clients use sessions when servers request them. Replace middleware example with minimal API endpoint filter. Fix AllowNewSessionForNonInitializeRequests docs to call out spec non-compliance. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Restructure document flow: move client-side behavior up, fold security into server configuration, move legacy SSE to its own section near the end. Replace middleware example with minimal API endpoint filter using Activity.AddTag for the transport session ID. Migrate SSE anchors across transports.md, list-of-diagnostics.md, and filters.md. Fix endpoint filter test to avoid strict request count assertion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a Roslyn DiagnosticAnalyzer (MCP003) that warns when WithHttpTransport is called without explicitly setting HttpServerTransportOptions.Stateless. This protects users from breaking changes if the default changes from stateful to stateless in the future. The analyzer detects: - No delegate passed: .WithHttpTransport() - Null literal: .WithHttpTransport(null) - Lambda without Stateless assignment - Method groups and delegate variables (cannot trace) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add a Roslyn DiagnosticAnalyzer (MCP003) that warns when WithHttpTransport is called without explicitly setting HttpServerTransportOptions.Stateless. This protects users from breaking changes if the default changes from stateful to stateless in the future.
The analyzer detects: