LibreChat/packages/api
Danny Avila b97d2868d4
🛑 feat: Cancel MCP Discovery Work at the Caller Deadline (#15353)
* feat: cancel discovery probes and tools/list at the caller deadline

deadlineMs could only be checked, never enforced: between checkpoints,
the health probe's ping ran to the SDK's 60s default, an in-flight
tools/list page ran to its page timeout, and a spent budget still paid
for credential preparation and a publication-order reservation before
anything noticed.

Derive one AbortSignal per discovery from the deadline and the caller's
own cancellation, and hand it to the work the SDK can genuinely cancel:
client.ping (and its fallback verification) via isConnected(signal), and
every tools/list page via fetchToolsSnapshot. An aborted probe reports
false for that caller only and never mutates connection state, so a
shared app connection stays usable for everyone else.

Phases that cannot be cancelled are no longer started once the budget is
spent: discovery returns before Graph preprocessing and token resolution
begin, and a snapshot returns incomplete before reserving publication
order. Teardown stays deliberately exempt — dispose() must finish.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xr1Dabvdn1mzzyYgpJgU5B

* fix: keep aborted probes honest and combine caller signals

Codex round 1 on the abort threading found three gaps, all real:

- An aborted ping left lastConnectionCheckAt stamped, so a dead shared
  connection read as healthy for the whole CONNECTION_CHECK_TTL. The
  probe now restores the stamp when the caller signal caused the
  failure; the real-SDK test closes the server after an aborted probe
  and asserts the next caller sees it dead.
- The app-connection fast path built its signal from the deadline alone,
  so cancelling the originating request did not cancel a hung probe or
  tools/list there. One shared createDeadlineAbortSignal helper now
  combines both, used by the factory and the manager.
- The entry checkpoint could not see a budget that expired during
  credential preparation; discoverToolsInternal rechecks before token
  resolution begins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xr1Dabvdn1mzzyYgpJgU5B

* fix: consult the caller signal at discovery decision points

Round 2 found the pattern left half-applied: the signal cancels SDK
requests, but two orchestration points ignored it.

- An app-connection probe aborted by the caller fell through to
  discovery fallback, opening a fresh connection for a request that no
  longer exists. The manager now returns before the fallback when the
  caller signal is aborted or the budget spent, and the caller signal
  travels into non-OAuth discovery too — previously only OAuth
  connections could carry one, so signal moves to UserConnectionContext.
- fetchOrderedToolsSnapshot bounded the refresh wait by the deadline but
  not the signal, so a signal-only caller stayed blocked for the shared
  refresh's full cycle. settlesBefore now interrupts on either.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xr1Dabvdn1mzzyYgpJgU5B

* fix: gate every discovery checkpoint on one cancellation predicate

Round 3 exposed the structural cause of the last two rounds: discovery
carries the same fact in two forms — a deadline number and a signal —
and each gate consulted only the deadline. isDiscoveryCancelled() is now
the one predicate at the static entry, the post-preparation entry, and
the pre-fallback gate, matching the manager gate and the snapshot gates
that already checked both.

connect() cannot carry the signal, so a signal-only abort previously
waited out the full connection timeout. connectWithinBudget races
connect against the timeout and the abort with owned cleanup: the timer
is always cleared, the abandoned attempt's rejection is swallowed, and
disposal hands whatever the dead connect still constructs to the
mid-connect disposal guard.
2026-08-30 08:12:04 -04:00
..
src 🛑 feat: Cancel MCP Discovery Work at the Caller Deadline (#15353) 2026-08-30 08:12:04 -04:00
types
.gitignore
babel.config.cjs
jest.config.mjs 📦 chore: update sanitize-html to latest (#14573) 2026-08-01 09:35:57 -04:00
jest.setup.cjs
package.json 📦 chore: bump @librechat/agents to v3.7.8 (#15337) 2026-08-29 16:17:15 -04:00
tsconfig-paths-bootstrap.mjs
tsconfig.build.json
tsconfig.json 🥸 chore: Resolve Agents SDK Path Aliases That Masked Backend Types (#15160) 2026-08-24 08:38:30 -04:00
tsconfig.spec.json
tsdown.config.mjs 🫆 chore: Remove Published Credential Defaults (#14680) 2026-08-07 07:25:05 -04:00