Skip to content

Fix server/discover timeout interrupting OAuth#1719

Open
halter73 wants to merge 1 commit into
mainfrom
halter73-release-priority-triage
Open

Fix server/discover timeout interrupting OAuth#1719
halter73 wants to merge 1 commit into
mainfrom
halter73-release-priority-triage

Conversation

@halter73

Copy link
Copy Markdown
Contributor

Summary

Start DiscoverProbeTimeout only after the server/discover request has been sent. This keeps transport setup and OAuth authorization under the overall InitializationTimeout, while preserving the short response timeout used to fall back when an older server silently drops server/discover.

The regression test now deliberately keeps protected-resource metadata discovery in flight longer than the probe timeout and verifies that the fallback paths are requested exactly once.

Main CI failure

This fixes the failure in Build and Test run 29739945453, Ubuntu Debug job 88344129696:

Assert.Equal() Failure: Collections differ
Expected: ["/.well-known/oauth-protected-resource/mcp", "/.well-known/oauth-protected-resource"]
Actual:   ["/.well-known/oauth-protected-resource/mcp", "/.well-known/oauth-protected-resource", "/.well-known/oauth-protected-resource/mcp", "/.well-known/oauth-protected-resource"]

The logs show the initial OAuth flow beginning for server/discover, then the five-second probe timeout starting initialize before the first token exchange completed:

[11:53:26] ... sending method 'server/discover' request.
[11:53:26] ... POST https://localhost:7029/token
[11:53:31] ... sending method 'initialize' request.
[11:53:32] ... POST https://localhost:7029/token - 499 ... 5830.9035ms

That overlap launched a second OAuth flow and repeated protected-resource metadata discovery.

Tests

  • dotnet build --no-restore
  • OAuth regression test on net8.0, net9.0, and net10.0
  • July 2026 protocol fallback tests on net472, net8.0, net9.0, and net10.0
  • Full non-manual ModelContextProtocol.Tests suite on net9.0 (2,286 passed)

Start the short server/discover timeout only after the request has been sent so OAuth authentication remains governed by the overall initialization timeout.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@halter73

Copy link
Copy Markdown
Contributor Author

@PranavSenthilnathan I'm also supportive of increasing the test timeouts, but I think it's a functional issue to have the probe timeout affected by the OAuth flow. I think it makes sense for the initialize timeout to still be in force, but how long the OAuth flow takes shouldn't affect protocol negotiation.

Comment on lines 310 to +313
var probeTimeout = _options.DiscoverProbeTimeout;
using var probeCts = CancellationTokenSource.CreateLinkedTokenSource(initializationCts.Token);
if (_options.InitializationTimeout > probeTimeout)
{
probeCts.CancelAfter(probeTimeout);
}
var discoverResponseTimeout = _options.InitializationTimeout > probeTimeout
? probeTimeout
: Timeout.InfiniteTimeSpan;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move these closer to the use site in the local function?


// Start the response timeout after sending completes so transport setup and authentication remain
// governed by the caller's cancellation token rather than consuming the response budget.
using var responseTimeoutCts = responseTimeout == Timeout.InfiniteTimeSpan

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes DiscoverProbeTimeout prematurely canceling OAuth, but it no longer bounds the Streamable HTTP response wait. SendToRelatedTransportAsync awaits and processes the POST response before returning, so the timeout starts only after that response has been read and queued. If the server never responds, the client waits for InitializationTimeout instead of falling back after DiscoverProbeTimeout.

Passing the probe token into the entire send would reintroduce the OAuth bug because that operation may receive a 401, perform separate OAuth requests, and retry the POST internally. The HTTP/OAuth layer likely needs to apply the probe timeout while awaiting each MCP POST response, exclude the OAuth requests, and use a fresh timeout for the authenticated retry, while InitializationTimeout remains active throughout.

AI-assisted review comment.

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.

2 participants