LibreChat/api/server
Danny Avila a3c2277c38
🚧 fix: Narrow Chat MCP Selections to What the Picker May Offer (#15478)
* 🚧 fix: Narrow Chat MCP Selections to What the Picker May Offer

The chat MCP picker offers a server only when the operator has not hidden it
(`chatMenu: false`) and the user reaches it directly rather than through an
agent (`consumeOnly`). Nothing enforced either condition on the request, so a
stale client, a replayed body, or a hand-written one could name any server and
get its tools — `consumeOnly` in particular is a permission the registry derives
per user, so ignoring it let a user reach in plain chat a server they had only
been granted through an agent.

Narrow the picker's selection in the two ephemeral loaders, against the same
predicate the client's `selectableServers` uses. The resolver is injected, so
`packages/api` stays free of the registry and the loaders keep their existing
`deps` shape.

Provenance is why this lives in the loaders rather than at the tool-resolution
chokepoint: only here is a server name known to have come from the picker.
Servers a model spec pins are the operator's own choice and are added after the
narrowing, so pinning a chat-hidden server to a spec keeps working.

`chatMenu` only ever comes from the config tier, so the overlay settles it with
no lookup at all. Lookups for the rest run concurrently and are cached per
server and user. A name the registry cannot resolve — request-tier, declared on
the body and never registered — is kept, as is one whose lookup fails: this
narrows an already-authenticated selection and is not the authorization
boundary.

* 📝 docs: Say What chatMenu Now Bounds

The schema still described `chatMenu` as dropdown visibility alone, which is
misleading now that it also bars a server from the chat selection a request
carries. Spell out both halves, and what it deliberately does not restrict:
agents, and servers a model spec pins.

* 🛡️ fix: Close Four Gaps in the Chat MCP Narrowing

Dedupe before resolving. The selection is request-supplied and the loaders used
to build a `Set` immediately; mapping the raw array first let one body repeat a
name enough times to launch a resolver — registry, cache, and ACL reads, with no
single-flight — per occurrence.

Resolve names through the tool-loading aliases. A configured `private server` is
addressable as `private_server` downstream, so an exact-key lookup let the
normalized spelling walk past a `chatMenu: false` check and still load tools.
Names are returned as sent; only the decision is canonicalized.

Let the request overlay settle `chatMenu`. The config tier is where the flag
lives and is what the picker itself was built from, so an overlay that re-enables
a server now outranks the durable config the registry returns, which was silently
dropping a valid selection. `consumeOnly` still comes from the registry.

Narrow the request body, not just the loader's copy. `applyContextToAgent` reads
`req.body.ephemeralAgent.mcp` directly and prefers it over the agent's tools, so
a hidden server's `serverInstructions` reached the model context even after its
tools were dropped.

* 🧭 fix: Ask the Registry Which MCP Servers the Picker May Offer

Three rounds of review found the same mistake in three disguises: this narrowing
re-derived the registry's tier precedence instead of asking for it. A config
entry was treated as authoritative over a user-DB or process-backed server of the
same name; the alias map was built from config names only, so a DB server named
`private_server` was misrouted to a config `private server`; and the overlay
short-circuit ran before any of that could be consulted.

Resolve the accessible set through `resolveAllMcpConfigs` instead — the same call
behind `GET /api/mcp/servers`, so what the request may use and what the picker
was offered come from one place and cannot drift. Precedence, cross-tier name
collisions, and the config overlay are the registry's job again, and the
selectability test is a single flag check over what it returns.

The result is smaller: no overlay branch, no per-server lookups, one catalog read
regardless of how long the selection is, and aliases built over the whole
accessible set so an exact name always outranks another server's normalized form.

Publish the servers actually in play back onto the request body, spec pins
included. `applyContextToAgent` prefers that list over the agent's tools, so
narrowing to the picker selection alone had started dropping a spec-pinned
server's `serverInstructions` while suppressing a hidden one's.
2026-09-01 18:47:36 -04:00
..
controllers 🚧 fix: Narrow Chat MCP Selections to What the Picker May Offer (#15478) 2026-09-01 18:47:36 -04:00
middleware 🛫 feat: Enable Self-Service Code Worker Pairing (#15367) 2026-09-01 18:46:33 -04:00
routes 🛫 feat: Enable Self-Service Code Worker Pairing (#15367) 2026-09-01 18:46:33 -04:00
services 🚧 fix: Narrow Chat MCP Selections to What the Picker May Offer (#15478) 2026-09-01 18:47:36 -04:00
utils 🎟️ refactor: Require Credentials for Local Image Access by Default (#15252) 2026-08-27 09:55:27 -04:00
cleanup.js
csp.spec.js 🔣 fix: Escape SPA Language Attribute (#15248) 2026-08-26 07:37:36 -04:00
experimental.js 🧳 feat: Register Principal-Owned Code Environments (#15365) 2026-08-30 19:33:19 -04:00
experimental.spec.js 🧾 feat: Store Durable Event Actor Receipts (#15265) 2026-08-27 06:00:43 -04:00
index.js 🧳 feat: Register Principal-Owned Code Environments (#15365) 2026-08-30 19:33:19 -04:00
index.metrics.spec.js ⏱️ feat: Run Scheduled Chats Through Durable Agent Triggers (#14939) 2026-08-20 11:51:30 -04:00
index.spec.js 🦺 feat: Configurable Baseline HTTP Security Headers (#14445) 2026-08-25 08:21:39 -04:00
socialLogins.js ⏳ feat: Make OpenID Token Reuse Window Configurable (#13546) 2026-06-06 15:15:58 -04:00
socialLogins.spec.js ⏳ feat: Make OpenID Token Reuse Window Configurable (#13546) 2026-06-06 15:15:58 -04:00
telemetry.js
telemetry.spec.js