Skip to content

⚡ refactor: Fast-Fail MCP Tool Discovery on 401 for Non-OAuth Servers#12395

Merged
danny-avila merged 2 commits into
devfrom
refactor/mcp-tool-discovery
Mar 25, 2026
Merged

⚡ refactor: Fast-Fail MCP Tool Discovery on 401 for Non-OAuth Servers#12395
danny-avila merged 2 commits into
devfrom
refactor/mcp-tool-discovery

Conversation

@danny-avila

@danny-avila danny-avila commented Mar 25, 2026

Copy link
Copy Markdown
Owner

Summary

Fixed a 30-second hang during MCP tool discovery triggered when a non-OAuth server returns a 401. Previously, connectClient() emitted oauthRequired and awaited an internal oauthHandledPromise, but when useOAuth was false, no listener was registered to dispatch oauthFailed — leaving the promise unresolved until the outer withTimeout killed it after 30s (with an additional ~90s background floating execution and leaked setTimeout).

  • Removed all if (this.useOAuth) guards around connection.on/removeListener calls in discoverToolsInternal, making oauthHandler registration unconditional.
  • Changed oauthHandler from async to a plain synchronous function, ensuring oauthFailed is emitted synchronously within the oauthRequired dispatch and preventing an unhandled-rejection footgun for future maintainers.
  • Replaced connection.on('oauthRequired', oauthHandler) with connection.once(...) to express the one-shot semantics of this event in the discovery lifecycle.
  • Added an explanatory comment above the once registration documenting why it is unconditional, protecting the fix from being mistakenly reverted as dead code.
  • Added a unit test in MCPConnectionFactory.test.ts verifying that discoverTools resolves in under 5 seconds with { tools: null, oauthRequired: true } when a non-OAuth server returns 401.
  • Added an integration test in MCPOAuthConnectionEvents.test.ts exercising the fix against a real OAuth-gated MCP server via createOAuthMCPServer, asserting sub-5s resolution and correct result shape.
  • Updated the existing oauthRequired test in MCPConnectionFactory.test.ts to intercept once instead of on and invoke the handler synchronously, matching the new implementation.

Change Type

  • Bug fix (non-breaking change which fixes an issue)

Testing

Verified via two test layers:

Unit test (MCPConnectionFactory.test.ts): Mocks MCPConnection with once intercepting oauthRequired, simulates a 401 throw from connect(), and asserts the result resolves in under 5s with { tools: null, oauthRequired: true, oauthUrl: null, connection: null }.

Integration test (MCPOAuthConnectionEvents.test.ts): Spins up a real OAuth-gated MCP server via createOAuthMCPServer, calls MCPConnectionFactory.discoverTools against it with no OAuth options (simulating a non-OAuth client hitting a protected server), and asserts sub-5s resolution with the correct result shape.

Test Configuration:

  • packages/apinpx jest MCPConnectionFactory
  • packages/apinpx jest MCPOAuthConnectionEvents

Checklist

  • My code adheres to this project's style guidelines
  • I have performed a self-review of my own code
  • I have commented in any complex areas of my code
  • My changes do not introduce new warnings
  • I have written tests demonstrating that my changes are effective or that my feature works
  • Local unit tests pass with my changes

Copilot AI review requested due to automatic review settings March 25, 2026 16:38
@danny-avila

Copy link
Copy Markdown
Owner Author

@codex review

Copilot AI left a comment

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.

Pull request overview

This PR fixes a tool discovery hang when MCP servers that are configured as non-OAuth respond with auth errors (e.g., 401). It ensures discovery “fast-fails” by always attaching an oauthRequired handler that emits oauthFailed, unblocking the connection’s internal OAuth-handling wait.

Changes:

  • Always register an oauthRequired listener in discoverToolsInternal() (independent of useOAuth).
  • Always remove the oauthRequired listener during discovery cleanup (independent of useOAuth).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/api/src/mcp/MCPConnectionFactory.ts Outdated
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Nice work!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@danny-avila
danny-avila force-pushed the refactor/mcp-tool-discovery branch from 4c10c6a to 283f6ca Compare March 25, 2026 17:06
@danny-avila

Copy link
Copy Markdown
Owner Author

@codex review again

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Always attach oauthHandler in discoverToolsInternal regardless of
useOAuth flag. Previously, non-OAuth servers hitting 401 would hang
for 30s because connectClient's oauthHandledPromise had no listener
to emit oauthFailed, waiting until withTimeout killed it.
@danny-avila
danny-avila force-pushed the refactor/mcp-tool-discovery branch from 283f6ca to fd38d6a Compare March 25, 2026 17:10
@danny-avila danny-avila changed the title fix: fast-fail MCP discovery for non-OAuth servers on auth errors refactor: fast-fail MCP discovery for non-OAuth servers on auth errors Mar 25, 2026
@danny-avila danny-avila changed the title refactor: fast-fail MCP discovery for non-OAuth servers on auth errors ⚡ refactor: Fast-Fail MCP Tool Discovery on 401 for Non-OAuth Servers Mar 25, 2026
@danny-avila
danny-avila merged commit 221e492 into dev Mar 25, 2026
8 checks passed
@danny-avila
danny-avila deleted the refactor/mcp-tool-discovery branch March 25, 2026 17:18
yidianyiko pushed a commit to yidianyiko/LibreChat that referenced this pull request Apr 13, 2026
…danny-avila#12395)

* fix: fast-fail MCP discovery for non-OAuth servers on auth errors

Always attach oauthHandler in discoverToolsInternal regardless of
useOAuth flag. Previously, non-OAuth servers hitting 401 would hang
for 30s because connectClient's oauthHandledPromise had no listener
to emit oauthFailed, waiting until withTimeout killed it.

* chore: import order
jcbartle pushed a commit to jcbartle/LibreChat that referenced this pull request May 11, 2026
…danny-avila#12395)

* fix: fast-fail MCP discovery for non-OAuth servers on auth errors

Always attach oauthHandler in discoverToolsInternal regardless of
useOAuth flag. Previously, non-OAuth servers hitting 401 would hang
for 30s because connectClient's oauthHandledPromise had no listener
to emit oauthFailed, waiting until withTimeout killed it.

* chore: import order
ThomasVuNguyen pushed a commit to ThomasVuNguyen/LibreChat that referenced this pull request Jul 15, 2026
…danny-avila#12395)

* fix: fast-fail MCP discovery for non-OAuth servers on auth errors

Always attach oauthHandler in discoverToolsInternal regardless of
useOAuth flag. Previously, non-OAuth servers hitting 401 would hang
for 30s because connectClient's oauthHandledPromise had no listener
to emit oauthFailed, waiting until withTimeout killed it.

* chore: import order
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