Skip to content

🎚️ feat: Let Chat Users Switch a Saved Agent's Tools - #84

Open
TomasPalsson wants to merge 24 commits into
mainfrom
agent-tool-user-toggles
Open

TomasPalsson wants to merge 24 commits into
mainfrom
agent-tool-user-toggles

Conversation

@TomasPalsson

Copy link
Copy Markdown

Pull Request

Summary

Today a saved agent's tools are all-or-nothing: whatever the creator attaches (web search, code execution, file search, MCP servers) runs on every message, and a chat user has no way to turn one off or opt into one. This PR lets a creator mark each built-in tool (web search, code, file search) and each MCP server as locked (today's behavior, the default), switchable, starts on, or switchable, starts off. Chat users see the switchable ones in the chat input of a saved-agent chat, flip them per chat, and the server only ever removes tools the creator made switchable — a request can never add a tool the agent does not have or enable a locked one. Agents with no switchable tools, ephemeral chats and plain-endpoint chats behave exactly as before.

How it works

The setting is stored per agent as tool_options[<tool key>].user_toggle: 'on' | 'off' (absent = locked), validated by the agent create/update schema. MCP servers use the existing sys__server__sys_mcp_<configured name> key. Two pure helpers in librechat-data-provider own the rules: getAgentToolSwitches(agent) returns the switchable set with creator defaults, and applyAgentToolSwitches(agent, requested) resolves the request against those defaults and returns the filtered tool list — it only drops switchable tools that resolve to off.

sequenceDiagram
  participant B as Agent builder
  participant C as Chat input (BadgeRow)
  participant S as Server (loadAgent)
  B->>B: tool_options[key].user_toggle = on/off
  C->>C: getAgentToolSwitches(agent) → show switches, seed per-chat state from defaults
  C->>S: ephemeralAgent { web_search, execute_code, file_search, mcp[] }
  S->>S: primary agent only: applyAgentToolSwitches(agent, ephemeralAgent)
  S->>S: MCP instructions follow the filtered tools, not the request list
Loading
  • Server: packages/api/src/agents/load.ts filters the primary agent only (the memory agent and other agents loaded in the same request are untouched); api/server/services/Endpoints/agents/build.js only passes req.body.ephemeralAgent through. context.ts builds MCP instructions from the filtered tools when the agent has server switches, so a switched-off server's instructions are never injected and a paused/resumed turn rebuilds the same tool set.
  • GET /api/agents/:id for view-only users now also returns tools and a tool_options trimmed to user_toggle only, so shared chat users get their switches.
  • Client: builder setting in UserToggleSelect (built-in and MCP sections); chat switches in BadgeRow / BadgeRowContext / ToolsDropdown; useApplyAgentToolSwitches seeds a new chat from the creator's defaults, keeps the user's choices through the first message and reloads, and clears the previous agent's switches when the agent changes. A saved agent's switchable MCP servers are offered even when chatMenu: false hides them from the general menu.
  • MCP server names are matched by configured name everywhere (builder key, server filter, chat), including names that normalize (spaces) or contain _mcp_.

Type of change

  • Feature
  • Documentation

Testing

  1. Builder: set web search to "switchable, starts off" and an MCP server to "switchable, starts on"; save and reopen — the setting is kept.
  2. New chat with that agent: both switches show with those defaults; the MCP server is selected.
  3. Turn web search on → it is used. Turn the MCP server off, send a message, reload → it stays off. New chat → back to the defaults.
  4. A hand-made request enabling a locked tool, or a tool the agent lacks, loads nothing extra.

Tested environments/configuration: local build on macOS, MongoDB 7 in Docker, a streamable-http MCP server with one tool, Chrome. Manually verified steps 1–3 in the running app.

Automated tests:

  • packages/data-provider: npx jest agentToolOptions (20) + tsc --noEmit
  • packages/api: npx jest src/agents/__tests__/load.spec.ts src/agents/validation.spec.ts src/agents/context.spec.ts (125) + tsc --noEmit
  • api: npx jest server/controllers/agents/v1.spec.js (150)
  • client: npx jest SidePanel/Agents Chat/Input utils/__tests__ hooks/Agents hooks/MCP Providers useToolToggle timestamps (162 suites, 2648) + tsc --noEmit
  • npm run static-checks:full -- --against <base> and npm run -w @librechat/api openapi:check / openapi:test pass.

Risk / compatibility

  • No migration: the new field lives in the existing tool_options mixed field; agents without user_toggle are unchanged. The OpenAPI spec is regenerated for user_toggle.
  • View-only GET /api/agents/:id now includes tools (already exposed by the list endpoint) and only the user_toggle part of tool_options.
  • Per-chat choices live in browser storage like the existing plain-chat switches, so they share the same 2-day cleanup: a chat not opened for more than two days returns to the creator's defaults.
  • Known edge cases left for follow-up: a switchable server whose name ends with a locked server's _mcp_<name> suffix (e.g. bar and foo_mcp_bar) can resolve that locked server's tools as its own; a switchable server hidden by chatMenu: false can be pruned if the agent loads after the MCP server list. OpenAI-compatible and Responses API agent endpoints do not apply switches (no switch state there), matching today's behavior. Artifacts, memory and skills are not switchable in this PR.
  • Existing failures in packages/api/src/agents/hooks/reaper.spec.ts and executor.spec.ts (process-group signal escalation) reproduce locally without this change and are untouched here. The repository-wide eslint . --fix reports pre-existing errors in unrelated files; the branch-scoped static checks are clean.
  • Two tests were changed on purpose: a load.spec.ts assertion that required the request's mcp list to be deleted now asserts the request is left untouched, and a catalog.spec.ts case that only asserted the removed, unread userProvidedAuth flag was deleted along with that flag.

Checklist

  • I reviewed my own changes
  • Relevant tests have been added or updated
  • Existing relevant tests pass
  • The change does not introduce new warnings or errors
  • User-facing or complex behavior is documented where necessary
  • Required dependency changes have been merged/published
  • Required documentation PR: N/A

lia-by-librechat Bot and others added 24 commits September 28, 2026 23:44
…I#16455)

* 🕘 feat: Distinguish Tool Preparation From Tool Call Time

* 🎞️ fix: Format timing specs and flatten phase labels

* 🧷 fix: Narrow Tool-Step Timing Before Stream Callback

* ⏱️ fix: Show Measured Subsecond Tool Calls

* 🧮 fix: Attribute Earliest Tool Fragment to Its Call

* 🧵 fix: Preserve Tool Timing Across Replay and Parallel Calls

* 📦 chore: Upgrade Agents SDK To 3.9.8

* 🧭 fix: Restore Tool Timing Across Redis And Child Activity

---------

Co-authored-by: Lia <lia@librechat.ai>
…eChat-AI#16477)

* feat: Route Linked Worktree Requests into Per-Worktree Lanes

Workers that advertise the git_linked_worktree workspace scope run each
.worktrees/<name> of a registered checkout in its own scheduling lane.
Accept the scope in worker status, map worktree-scoped paths and command
working directories onto the worktree field, and restore the prefix on
returned paths so tool output stays relative to the checkout.

* fix: Advertise Worktree cwd Routing in Model-Facing Bash Definitions

* refactor: Normalize the Lane Flag in TypeScript, Pass It Through /api Unchanged

* feat: Gate Linked Worktree Lanes Behind an Environment configSchema Toggle

* test: Give the Lane Toggle Fixture a Default Environment
Saved agents can mark a built-in tool or a whole MCP server as switchable
by the chat user, with an on/off starting state. Adds the option type, the
helpers that read the switchable set and apply a chat's switch state (only
ever removing tools), and validation of the field on agent save.
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 638dfe06-82db-4a8b-a9f0-443901127c16


Comment @coderabbitai help to get the list of available commands.

@TomasPalsson
TomasPalsson marked this pull request as ready for review September 29, 2026 18:28

This branch has not been deployed

No deployments
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