LibreChat/packages/api/src
Danny Avila 928b14f5bc
🔒 fix: Single-Flight MCP OAuth Token Refresh per User/Server (#14596)
* 🔒 fix: Single-Flight MCP OAuth Token Refresh per User/Server

Concurrent refresh-token redemptions (tool-call 401, ping, reconnect
retries, expired-token reads) each replayed the same stored refresh
token at the OAuth token endpoint. RFC 9700 reuse detection treats the
replay as theft and revokes the entire grant family, forcing manual
re-consent every access-token expiry.

MCPTokenStorage.forceRefreshTokens is the choke point every refresh
path converges on; it now single-flights redemptions per
(tenantId, userId, serverName) so concurrent callers share one wire
call and receive the same rotated result. The refresh token is re-read
from storage inside the locked execution — never from a caller
snapshot — so a redemption starting after another refresh completed
uses the rotated token instead of replaying the consumed one.

Fixes #14583

* 🧪 test: Isolate Single-Flight Keys per Test via Unique Server Names

* 🔒 fix: Evict Stalled Refresh Slots, Decouple Waiter Aborts from Shared Redemption

Codex review round 1:
- A redemption that never settles no longer wedges the single-flight
  slot until process restart: a stale-entry timer evicts the map entry
  so later refreshes start fresh, while existing waiters keep their
  promise.
- Caller AbortSignals no longer thread into the shared redemption. An
  impatient waiter (silent refresh's short timeout) resolves its own
  wait with null via a per-waiter race; the shared wire call proceeds
  for everyone else, bounded by transport timeouts plus eviction.

* 🔒 fix: Abort Stalled Refreshes Before Slot Release, Hook Cache Invalidation to Redemption

Codex review round 2:
- The stale timer now aborts the wedged execution instead of deleting
  its slot; the slot frees only once the execution has settled, and an
  abort guard before the token-endpoint call stops a woken pre-wire
  stall from replaying a refresh token a successor already rotated.
- New onRefreshSuccess hook runs inside the shared redemption after
  rotated tokens persist, so the silent-refresh path's mcp_get_tokens
  cache invalidation fires even when the initiating waiter timed out
  before the redemption completed.

* 📝 docs: Record Post-Dispatch Abort Recovery Rationale on Stale-Refresh Valve
2026-08-02 06:39:44 -04:00
..
acl 🔒 fix: Prevent Role Grants on Shared Links (#14137) 2026-07-07 07:04:52 -04:00
actions 🔀 fix: Reconcile Agent Action Credential Merges (#13559) 2026-06-06 15:09:58 -04:00
admin 🪢 feat: Langfuse Fanout Connection Setting (#14108) 2026-07-29 18:33:10 -04:00
agents 🪪 fix: Strip Unresolved Header Placeholders at Final Resolution (#14595) 2026-08-02 06:38:06 -04:00
apiKeys ️ refactor: Migrate @librechat/api build to tsdown (#13595) 2026-06-08 10:54:48 -04:00
app 🗝️ feat: Custom Endpoint API Key Encryption via Array Secret Registry (#14510) 2026-07-29 15:37:46 -04:00
artifacts 🧵 fix: Preserve Fenced Markdown Artifacts (#14121) 2026-07-05 12:04:59 -04:00
auth 📉 perf: cache OpenID JWT user documents (#14187) 2026-07-12 07:53:50 -04:00
cache feat: add low-noise Redis observability (#14309) 2026-07-16 10:22:03 -04:00
cdn 🔥 fix: Firebase CDN Initialization Under tsdown CJS Interop (#14046) 2026-07-01 08:20:40 -04:00
cluster feat: add low-noise Redis observability (#14309) 2026-07-16 10:22:03 -04:00
crypto 🧵 refactor: Migrate Endpoint Initialization to TypeScript (#10794) 2025-12-11 16:37:16 -05:00
db 🗂️ feat: Add Private Chat Projects (#13467) 2026-06-03 15:29:18 -04:00
endpoints 🪪 fix: Strip Unresolved Header Placeholders at Final Resolution (#14595) 2026-08-02 06:38:06 -04:00
favorites 🧰 feat: Redesign Agent Builder with Unified Tools Marketplace, Skills & Orchestration (#13952) 2026-07-05 11:30:12 -04:00
files 📄 fix: Filter Non-PDF Documents on the Anthropic Encode Path (#14535) 2026-07-31 09:57:02 -04:00
flow 🤫 refactor: Silent MCP OAuth Refresh on Mid-Session 401 (#13369) 2026-06-10 13:12:42 -04:00
html ⚙️ refactor: lazy-load React Query Devtools (#13639) 2026-06-10 13:06:20 -04:00
langfuse 🪢 feat: Langfuse Fanout Connection Setting (#14108) 2026-07-29 18:33:10 -04:00
mcp 🔒 fix: Single-Flight MCP OAuth Token Refresh per User/Server (#14596) 2026-08-02 06:39:44 -04:00
memory 🧠 fix: Bound Memory Agent Input (#13606) 2026-06-09 14:38:21 -04:00
middleware 🤏 fix: Filter Admin Config Reads by Section-Scoped Read Capability (#14472) 2026-07-28 07:38:37 -04:00
modelSpecs 🥷 feat: Add showInMenu Option to Model Specs (#14034) 2026-06-30 19:32:59 -04:00
oauth ️ refactor: Migrate @librechat/api build to tsdown (#13595) 2026-06-08 10:54:48 -04:00
projects ️ refactor: Migrate @librechat/api build to tsdown (#13595) 2026-06-08 10:54:48 -04:00
prompts 🧵 fix: Preserve Fenced Markdown Artifacts (#14121) 2026-07-05 12:04:59 -04:00
rum 📈 fix: Isolate RUM Telemetry Proxy Auth from App Auth (#13765) 2026-06-15 12:49:44 -04:00
shared-links 🔒 fix: Prevent Role Grants on Shared Links (#14137) 2026-07-07 07:04:52 -04:00
skills 🪆 fix: Serve Nested Skill Files Through a Wildcard Route (#14191) 2026-07-09 16:52:11 -04:00
storage 🌀 ci: Deterministic Circular Dependency Checks (#14579) 2026-08-01 14:43:26 -04:00
stream 🦗 fix: Deliver Abort Acknowledgements on Zero-Subscriber Replicas (#14569) 2026-08-01 09:00:41 -04:00
telemetry perf: Reduce Agent Chat Startup Latency (#14423) 2026-07-25 07:58:20 -04:00
tools 🌀 ci: Deterministic Circular Dependency Checks (#14579) 2026-08-01 14:43:26 -04:00
types 🌀 ci: Deterministic Circular Dependency Checks (#14579) 2026-08-01 14:43:26 -04:00
utils 🪪 fix: Strip Unresolved Header Placeholders at Final Resolution (#14595) 2026-08-02 06:38:06 -04:00
web 🛟 refactor: Gracefully Skip Unavailable Web Search Rerankers (#13191) 2026-05-19 09:48:12 -04:00
index.ts 🔗 fix: Resolve MCP Tool-Key Boundary Against Configured Server Names (#14448) 2026-07-27 14:45:38 -04:00
telemetry.ts ️ refactor: Migrate @librechat/api build to tsdown (#13595) 2026-06-08 10:54:48 -04:00