Skip to content

Add MCP shutdown notification, in-flight cap, cancellation plumbing (#1567) - #2210

Merged
Widthdom merged 1 commit into
mainfrom
fix-issue1567
May 16, 2026
Merged

Add MCP shutdown notification, in-flight cap, cancellation plumbing (#1567)#2210
Widthdom merged 1 commit into
mainfrom
fix-issue1567

Conversation

@Widthdom

Copy link
Copy Markdown
Owner

Summary

  • Add JSON-RPC notifications/shutdown handler (with legacy LSP-style notifications/exit alias) that cancels an internal shutdown token, unblocks pending IMcpTransport.ReadFrameAsync, drains the current tool call, and exits the loop without serving frames queued behind the shutdown notification.
  • Acquire a SemaphoreSlim (default cap 8, exposed as McpServer.MaxConcurrency and validated by the constructor) before every ProcessFrame so any future parallel-dispatch transport cannot exceed the bound.
  • Plumb the per-request CancellationToken into DbReader (new DbReader(connection, isReadOnly, cancellation) + DbReader.Cancellation / ThrowIfCancellationRequested) so SQLite work observes shutdown / client-disconnect cancellation. The legacy 2-arg DbReader ctor is preserved as a thin wrapper that passes CancellationToken.None.

Fixes #1567

Codex adversarial review (codex exec review --base origin/main) reported no blocking or actionable correctness issues against the focus areas (race on _currentRequestToken, dispose order vs in-flight requests, notifications/exit alias semantics, JSON-RPC error semantics for OperationCanceledException, callers on removed ctor signatures, loop break semantics on !_running, RateLimiter env read at construction).

Test plan

  • dotnet test — 4894 passed, 0 failed, 3 skipped (post-rebase)
  • New tests added:
    • McpServerTests.Notification_Shutdown_ReturnsNullAndLogsToStderr
    • McpServerTests.Notification_Exit_ReturnsNullAndLogsToStderr
    • McpServerTests.RunAsync_ShutdownNotification_DrainsAndExits
    • McpServerTests.RunAsync_ShutdownNotification_PreemptsRemainingFrames
    • McpServerTests.MaxConcurrency_DefaultExposesIssueBound
    • McpServerTests.MaxConcurrency_ExplicitOverride_TakesEffect
    • McpServerTests.MaxConcurrency_NonPositive_Throws
    • DbReaderTests.Constructor_DefaultOverload_LeavesCancellationNone
    • DbReaderTests.Constructor_ExplicitToken_PropagatedThroughHelpers
  • Codex adversarial review (codex exec review --base origin/main): no blocking issues

Generated with Claude Code

…1567)

The MCP JSON-RPC loop now accepts `notifications/shutdown` (and the legacy
LSP-style `notifications/exit` alias) that cancels an internal shutdown
token, unblocks any pending `IMcpTransport.ReadFrameAsync`, and exits the
loop after the current tool call drains; frames queued behind the
shutdown notification are not served.

`ProcessFrame` is now wrapped in a `SemaphoreSlim` (default cap `8`,
exposed as `McpServer.MaxConcurrency` and validated by the constructor)
so a future parallel-dispatch transport cannot exceed the bound. The
per-request `CancellationToken` is plumbed through `WithDbReader` into
`DbReader` via a new `(connection, isReadOnly, cancellation)` ctor plus
`DbReader.Cancellation` / `ThrowIfCancellationRequested`, so SQLite work
started by a tool observes shutdown / client-disconnect cancellation
instead of running to completion after the client is gone. The legacy
two-argument `DbReader` ctor is preserved as a thin wrapper that passes
`CancellationToken.None`, keeping existing CLI and test call sites
source-compatible.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Widthdom
Widthdom merged commit 0073197 into main May 16, 2026
7 checks passed
@Widthdom
Widthdom deleted the fix-issue1567 branch May 16, 2026 11:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP server lacks shutdown hook, backpressure, and cancellation propagation

1 participant