fix(mcp): adopt ModelContextProtocol 2.1.0 without losing invalid_client discard - #1865
Merged
Aaronontheweb merged 4 commits intoAug 11, 2026
Merged
Conversation
…ent discard Bump the MCP SDK from 2.0.0 to 2.1.0. SDK 2.1 sends a server/discover probe before the initialize handshake. The probe reads a 400 response as an unsupported-protocol signal and falls back to the initialize handshake. During interactive OAuth, that fallback calls the one-shot authorization callback a second time. The second call fails as "authorization already in progress" and hides the token endpoint invalid_client rejection. The manager then keeps a dead dynamic client registration instead of discarding it. Add OAuthClientRejectionHandler to the shared MCP HTTP pipeline. The handler reads the invalid_client error for the authorization_code grant and throws McpOAuthClientRejectedException before the SDK sees the 400. The SDK fallback does not catch that type, so the true cause reaches the manager and the dead identity is discarded. A refresh failure keeps the SDK graceful null path. This supersedes Dependabot PRs netclaw-dev#1777 and netclaw-dev#1778.
Aaronontheweb
enabled auto-merge (squash)
August 11, 2026 01:23
Merged
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.
Summary
Adopt the ModelContextProtocol 2.1.0 SDK (
ModelContextProtocol.CoreandModelContextProtocol.AspNetCore, both driven by the sharedModelContextProtocolVersionproperty). This supersedes the two failingDependabot PRs #1777 (AspNetCore) and #1778 (Core), which can be closed.
The straight version bump fails one test:
McpSdkOAuthFlowIntegrationTests.RejectedClientIdentityIsDiscardedSoTheNextAuthorizationRegistersAfresh.Root cause
SDK 2.1 sends a
server/discoverprobe before theinitializehandshake. Itsnew catch reads any
400/404response as an unsupported-protocol signal andfalls back to the initialize handshake. During interactive OAuth, the probe's
authorization-code exchange returns
400 invalid_clientwhen the provider hasdropped the dynamic client registration. The SDK swallows that
400as aprotocol-fallback signal and re-invokes the one-shot authorization callback a
second time. The second call trips the flow's single-owner guard and throws
OAuth authorization is already in progress, which hides the realinvalid_client.McpClientManager.IsInvalidClientFailureno longer matches,so the dead dynamic client identity is never discarded and every retry repeats
the failure.
ClientOAuthProvideritself is byte-identical between 2.0.0 and 2.1.0(decompiled and diffed). The change is entirely in
McpClientImpl's newdiscover/fallback path.
Fix
Add
OAuthClientRejectionHandlerto the shared MCP HTTP pipeline(
McpHttpClientFactory). The handler reads the token endpoint'sinvalid_clienterror for the
authorization_codegrant and throwsMcpOAuthClientRejectedExceptionbefore the SDK sees the400. The SDK fallbackdoes not catch that type, so the true cause reaches the manager and the dead
identity is discarded — restoring the exact 2.0.0 behavior without disabling
protocol negotiation. The handler is scoped to the authorization-code grant, so
a refresh failure keeps the SDK's graceful null path.
Testing
McpSdkOAuthFlowIntegrationTests— 23/23 pass (was 22/23)Netclaw.Daemon.Testsfull — 1001 passNetclaw.Cli.Tests— 1318 passNetclaw.Actors.Tests— 3042 pass (1 Windows-only skip)Netclaw.Configuration.TestsHttp/Mcp — 16 pass, plus 5 newOAuthClientRejectionHandlerTestslocking the handler's discriminators(authorization-code invalid_client throws; refresh invalid_client, other
errors, success, and non-form requests pass through)
dotnet slopwatch analyzereports 0 issues;copyright headers verified