You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(mcp): let operators disable individual built-in tools (#668)
`list-indexes`, `search-records`, and `upsert-records` all registered
unconditionally, so an operator who wanted a narrower tool surface had
no way to get one. A deployment that should never advertise writes still
published `upsert-records` whenever any binding was writable, and a
single-purpose server still published discovery.
## The config
```yaml
server:
builtin_tools:
upsert-records: disabled
```
Omitted names stay enabled, so **existing configs are unaffected**. Only
the three real names are accepted: `search_records` with underscores
fails at startup rather than silently disabling nothing while reading as
though it had.
## Two unusable-but-valid tool surfaces now warn
Both shapes are legal config that presents to a client as a server that
simply does not work, and both were previously silent:
- **Everything disabled** leaves a server that connects and offers
nothing. The warning deliberately does not name a cause, since
`upsert-records` can also be absent because every binding is read-only.
- **Discovery disabled on a multi-index server** leaves `search-records`
demanding a logical index id that clients have no way to learn — its own
description tells them to call `list-indexes` first.
Neither is fatal, because an operator may be mid-rollout.
The multi-index discovery case had **no coverage anywhere**, so this
adds it, along with the negative case: with a sole binding the `index`
argument defaults, so disabling discovery there is legitimate and stays
quiet. I verified by mutation that the new test fails when the warning
condition is neutered.
## Verification
- MCP unit tests: 237 passing
- `make check-types`: clean
- Pre-commit: passing
## Note on sequencing
This is independently useful and mergeable on its own. It is also a
prerequisite for the custom tool profiles work that follows, since a
profile name must be checked against the built-in names this PR
introduces.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes which MCP tools clients see and discovery/write advertising;
misconfiguration can yield empty or hard-to-use tool surfaces, though
defaults preserve existing behavior and validation plus warnings reduce
silent failures.
>
> **Overview**
> Adds **`server.builtin_tools`** so operators can disable any of
`list-indexes`, `search-records`, or `upsert-records` individually
(omitted tools stay enabled). Unknown names fail at **startup**
validation instead of being ignored.
>
> **Registration** gates each built-in on config; **`list-indexes`**
sets **`upsert_available`** to false when upsert is disabled even if
bindings are writable. On **multi-index** servers with discovery off,
**search** and **upsert** tool descriptions **inline** logical index
ids. **Startup warnings** cover empty tool lists, discovery disabled
with multiple indexes, and **config changes** after tools were already
registered (process restart required to change the published surface).
>
> Docs in **`docs/concepts/mcp.md`** describe the config and behavior.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f323df5. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Copy file name to clipboardExpand all lines: docs/concepts/mcp.md
+22-3Lines changed: 22 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -86,7 +86,7 @@ MCP-reserved score metadata field names for the configured search mode.
86
86
87
87
## Read-Only and Read-Write Modes
88
88
89
-
RedisVL MCP always registers `search-records` and `list-indexes`.
89
+
RedisVL MCP registers `search-records` and `list-indexes` by default (see [Tool Surface](#tool-surface) for turning a built-in off deliberately).
90
90
91
91
Write availability is enforced at two levels:
92
92
@@ -105,12 +105,31 @@ For configuration and the gateway boundary, see {doc}`/user_guide/how_to_guides/
105
105
106
106
## Tool Surface
107
107
108
-
RedisVL MCP exposes up to three tools:
108
+
RedisVL MCP exposes up to three built-in tools:
109
109
110
-
-`list-indexes` enumerates the configured logical indexes for discovery (always available)
110
+
-`list-indexes` enumerates the configured logical indexes for discovery
111
111
-`search-records` searches a selected index using that index's server-owned search mode
112
112
-`upsert-records` validates and upserts records into a selected writable index, embedding them only when that capability is configured
113
113
114
+
Any of the three can be turned off with `server.builtin_tools` — useful for a server that should only ever read, or one that should not advertise discovery:
115
+
116
+
```yaml
117
+
server:
118
+
builtin_tools:
119
+
upsert-records: disabled
120
+
```
121
+
122
+
Only the three names above are accepted; anything else fails at startup rather than being silently ignored.
123
+
124
+
Disabling a built-in adjusts what the rest of the surface advertises, so the published contract never points at something the server withholds:
125
+
126
+
- `list-indexes` reports `upsert_available: false` for every binding when `upsert-records` is disabled, since a writable binding still cannot be written to through a tool that is not published.
127
+
- On a multi-index server with `list-indexes` disabled, every tool that requires an `index` — `search-records` and `upsert-records` alike — names the available index ids in its own description instead of deferring to a discovery tool that does not exist. That server still logs a startup warning naming the affected tools, because inlining the ids is a fallback rather than an endorsement of the shape.
128
+
129
+
A server whose tool set ends up unusable — no tools at all, or discovery disabled on a multi-index server — logs a warning at startup.
130
+
131
+
Tools register once per process. `builtin_tools` is re-read on restart, but the registered tool set is not rebuilt, so a stop/start against an edited config keeps the previous tools and logs a warning saying so. Start a new process to change the tool surface.
132
+
114
133
These tools follow a stable contract:
115
134
116
135
- request validation happens before query or write execution
0 commit comments