🔄 fix: Per-Entry Reset to YAML for MCP Servers & Custom Endpoints - #113
🔄 fix: Per-Entry Reset to YAML for MCP Servers & Custom Endpoints#113dustinhealy wants to merge 5 commits into
Conversation
YAML-defined MCP servers and custom endpoints edited in the admin panel accumulate DB overrides that could never be cleared. The delete affordance was hidden (MCP) or silently ineffective (custom endpoints, where LibreChat merges the override array into the YAML array by name and re-adds unmatched YAML items), leaving entries permanently shadowed even after librechat.yaml changed. Each YAML-defined entry with stored overrides now shows a reset action that, after confirmation, clears its override layer: MCP servers unset the mcpServers.<key> subtree in the base override document; custom endpoints rewrite the endpoints.custom override array without the name-matched item, or unset the array path when it was the only override. The rewrite is built from a freshly fetched override document rather than the client cache, so a concurrent admin's change to the array is never discarded by a stale read-modify-write; an item already removed resolves as a no-op. The action is base-mode only and locked while unsaved edits are pending. Scope-profile overrides are untouched. Custom endpoints defined in YAML also hide the trash button (deletion is impossible at the merge layer and previously reappeared on reload) and lock the name field, since name is the merge identity and renaming orphans the override while duplicating the entry. Fixes #73 Fixes #108
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8a8a2e1d94
ℹ️ 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".
…e After Entry Reset Override documents written by panel versions before #92 PATCHed indexed field paths (endpoints.custom.2) directly, which Mongo's dot-path $set materializes as a numeric-key object instead of an array. The reset affordance and the rewrite plan only recognized the array shape, so endpoints overridden through such documents showed no reset action and could not be cleared. toOverrideArraySource now normalizes both shapes into a dense array, and the entry-key derivation moved into collectEntryOverrideKeys so it shares the normalization and is unit-tested. A successful entry reset now also bumps the edit session id, remounting session-keyed field state such as a SecretField opened for Replace but left untouched, which query invalidation alone does not reach.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1fbd35dc79
ℹ️ 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".
…enance Is Unavailable When the auxiliary baseOnly request failed while the main config request succeeded, yamlCustomEndpointKeys came back undefined and the client coerced it to an empty set, presenting every YAML-defined custom endpoint as admin-created: rename and delete enabled, reset hidden, and a staged delete would both fail at the merge layer and freeze the whole array into the override document. extractCustomEndpointNames now always returns an array for a successfully fetched baseOnly config, so a missing or malformed endpoints.custom means known-empty provenance and undefined yamlCustomEndpointKeys occurs only when the provenance fetch itself failed. CustomEndpointsRenderer treats that undefined as provenance-unavailable and fails closed, hiding the trash, locking the name field, and hiding the reset action for every entry until provenance is known, while the config editor keeps rendering.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2c5bb90186
ℹ️ 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".
The MCP renderer never accepted or forwarded editSessionId, so SecretFields inside MCP entry cards were keyed with a constant session and kept local replacement state across every edit-session bump: discard, save, base reset, and the per-entry overrides reset. An opened Replace input therefore survived a successful reset instead of returning to the masked display. editSessionId now flows from the renderer props through McpEntryRow and McpEntryFields into renderInlineField, matching how the endpoints renderer already forwards it, so session-keyed secret fields remount whenever ConfigPage bumps the session. Also pins the client contract the endpoint reset relies on: rewritten override arrays carry sibling items in the omit shape (no apiKey or apiKeyPreview keys, passthrough env references verbatim) that the backend's name-identity secret preservation expects.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e8ff6935d9
ℹ️ 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".
A YAML MCP server keyed __proto__, constructor, or prototype showed the per-entry reset action, but safeFieldPath rejects those segments so the confirm always failed server-side. Such keys now get the same read-only treatment as dotted legacy keys, since every field-path write or unset for them fails the same validation.
|
@codex review |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Summary
An MCP server or custom endpoint defined in librechat.yaml and then edited in the admin panel gets DB overrides that can never be cleared. For MCP servers the trashcan is hidden for YAML-defined entries, and even editing the YAML has no effect because the stored override is deep-merged over it on every load. For custom endpoints the situation is worse: LibreChat merges the
endpoints.customoverride array into the YAML array bynameand preserves unmatched YAML items, so deleting a YAML-defined endpoint silently fails (it reappears on reload), renaming one duplicates it, and any save copies the entire merged array into the override document, freezing every YAML endpoint against future YAML changes.YAML-defined entries with stored overrides now show a per-entry "reset to YAML" action on the entry card (base config mode only). After confirmation it clears just that entry's override layer: MCP servers unset the
mcpServers.<key>subtree in the base override document; custom endpoints rewrite the storedendpoints.customoverride array without the name-matched item, or unset the array path when it was the only override item. The endpoint rewrite is built from a freshly fetched override document rather than the client cache, so a concurrent admin's change to the array is never discarded by a stale read-modify-write, and an item already removed by someone else resolves as a no-op. Stored endpoint secrets survive the rewrite via the backend's omit-to-keep name-identity preservation (rewritten arrays carry redacted siblings with no secret keys, the exact shapepreserveArraySecretsrestores), and masked previews are stripped client-side before the write. The action is disabled while unsaved edits are pending, matching the global reset button, and a successful reset bumps the edit session so open-but-untouched secret Replace inputs remount instead of surviving the reset (the MCP renderer now threads the session id down to its fields for this). Resets target only the base override document (the__base__role config); scope-profile overrides for roles, groups, and users are unaffected.Custom endpoints defined in YAML also now hide the trashcan (deletion is impossible at the merge layer and previously reappeared on reload) and lock the
namefield, sincenameis the merge identity and renaming orphans the override while spawning a duplicate entry. Admin-created endpoints keep delete and rename, and scope mode is unchanged.Review hardening rounds added three robustness cases: override documents that store
endpoints.customin the legacy numeric-key object form (written by pre-#92 indexed-path saves) are normalized so those entries still get the reset action and a correct rewrite; when the YAML-provenance fetch fails while the main config load succeeds, custom endpoint identity actions fail closed (trash hidden, name locked, reset hidden) instead of presenting YAML endpoints as admin-created; and MCP keys the field-path validator rejects (__proto__,constructor,prototype) get the same read-only treatment as dotted legacy keys, since no field-path write or unset for them can succeed.Fixes #73
Fixes #108
Change Type
Testing
Unit coverage spans the reset-plan builder (
buildEntryOverridesResetPlan, including legacy numeric-key object storage and the sibling-secret omit-shape contract), the fresh-document reset flow (executeEntryOverridesReset, including the concurrent-edit race and already-removed no-op), the MCP per-entry reset affordance (shown only for overridden YAML servers in base mode, disabled while dirty, hidden for validator-rejected keys), edit-session remounting of MCP secret fields, provenance fail-closed behavior, and the firstCustomEndpointsRenderertests covering trash hiding, name locking, and the reset target payload. The new behavior tests fail on main and pass on this branch; full suite is 833 passing with eslint and tsc clean.Checklist