Skip to content

feat(mcp): add list-indexes discovery tool (RAAE-1605)#630

Merged
vishal-bala merged 4 commits into
feature/raae-1603-mcp-multi-indexfrom
feature/raae-1605-list-indexes
Jul 1, 2026
Merged

feat(mcp): add list-indexes discovery tool (RAAE-1605)#630
vishal-bala merged 4 commits into
feature/raae-1603-mcp-multi-indexfrom
feature/raae-1605-list-indexes

Conversation

@vishal-bala

@vishal-bala vishal-bala commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #629 (RAAE-1604). Review/merge that first; this PR targets the 1604 branch so the diff is scoped to the discovery tool.

Motivation

Once a single MCP server can expose multiple logical indexes (RAAE-1604), clients need a lightweight way to discover what's available so they can pick the right index instead of guessing. This PR (RAAE-1605) adds an always-registered, read-only list-indexes tool for exactly that, and it grounds discovery in the schema the server already inspected at startup rather than asking users to re-declare field metadata in config.

Implementation

The new tool (redisvl/mcp/tools/list_indexes.py) returns one entry per configured binding, in configured order: the logical id, an optional description, an upsert_available flag, the shared filterable fields, and — only when explicitly configured — a limits object. upsert_available is simply not effective_read_only, so it already reflects both the global --read-only flag and the per-index read_only policy resolved at startup. The fields list is built from the binding's effective (inspected + overridden) schema that already lives on its BindingRuntime, so the output stays consistent with what the index actually contains; the vector field and the configured default embed-source text field are omitted because they are implementation inputs rather than fields a client would filter on. The Redis index name (redis_name) is deliberately never exposed. Limits are included only when the operator set them explicitly — detected via the runtime model's model_fields_set — so defaults don't masquerade as deliberate overrides; per the contract this covers max_limit and max_upsert_records.

The tool is registered unconditionally during the server's tool registration (alongside search-records and the conditionally-registered upsert-records) and is gated by the same read scope as search when auth is enabled, since it is read-only.

  • Output is deterministic and ordered by configured binding.
  • No new configuration surface or settings are required.

Verification

  • mypy clean; black/isort formatted.
  • New unit coverage: field omission (vector + embed-source), description/limits inclusion rules, redis_name secrecy, read-only reflection, single- and multi-binding output, and tool registration.
  • New integration test starts a real two-binding server (one vector, one fulltext) and asserts the discovered fields come from the inspected schema and follow the omission rules.
  • Full MCP suite green: 178 unit + 45 integration (2 skipped on Redis-version gates) against Redis 8.

🤖 Generated with Claude Code


Note

Low Risk
Read-only discovery metadata with no new config; behavior is additive and gated like existing search tools when auth is enabled.

Overview
Adds a read-only list-indexes MCP tool so clients can discover logical indexes on multi-binding servers before calling search-records or upsert-records.

The tool is always registered during _register_tools (alongside search; upsert remains conditional). Each binding is returned in config order with id, optional description, upsert_available (not effective_read_only), filterable fields from the startup-inspected schema (vector and default embed-source text omitted), and limits only when max_limit / max_upsert_records were explicitly set. redis_name is never exposed; empty bindings raise RuntimeError like other pre-startup paths. When auth is on, the tool uses the same read scope as search.

Unit and integration tests cover payload rules, registration, and a two-binding startup scenario.

Reviewed by Cursor Bugbot for commit 45ce686. Bugbot is set up for automated code reviews on this repo. Configure here.

@jit-ci

jit-ci Bot commented Jun 16, 2026

Copy link
Copy Markdown

🛡️ Jit Security Scan Results

CRITICAL HIGH MEDIUM

✅ No security findings were detected in this PR


Security scan by Jit

@vishal-bala

Copy link
Copy Markdown
Collaborator Author

CI tests are failing due to flaky issues being fixed by redis-developer/sql-redis#39

@vishal-bala vishal-bala requested a review from nkanu17 June 25, 2026 10:05

@nkanu17 nkanu17 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally looks great! I ran into one thing that specified in the comments, it is worth checking the auth access pattern across the tools.

Comment thread redisvl/mcp/tools/list_indexes.py Outdated
Comment thread redisvl/mcp/tools/list_indexes.py Outdated
@vishal-bala vishal-bala requested a review from nkanu17 June 30, 2026 11:39
@vishal-bala vishal-bala force-pushed the feature/raae-1604-config-runtime-model branch from 118b01d to bd2a28a Compare June 30, 2026 13:41

@nkanu17 nkanu17 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@vishal-bala vishal-bala force-pushed the feature/raae-1605-list-indexes branch from e8d750c to 14bbdbf Compare June 30, 2026 20:11
Base automatically changed from feature/raae-1604-config-runtime-model to feature/raae-1603-mcp-multi-index June 30, 2026 20:12
vishal-bala and others added 3 commits June 30, 2026 22:16
Add an always-registered, read-only `list-indexes` MCP tool so clients can
enumerate the logical indexes a multi-index server exposes and choose the
right one before calling search-records or upsert-records.

For each configured binding the tool returns the logical id, an optional
description, whether upsert is available (reflecting both the global
--read-only flag and the per-index read_only policy), the shared filterable
fields, and any explicitly configured runtime limits. Fields are derived from
the binding's already-inspected effective schema rather than user-declared
metadata; the vector field and the configured default embed-source text field
are omitted because they are implementation inputs, not things a client
filters on. The Redis index name (redis_name) is never exposed. Limits are
surfaced only when explicitly set in config (detected via the runtime model's
model_fields_set), so the output reflects deliberate overrides rather than
defaults.

- New redisvl/mcp/tools/list_indexes.py with list_indexes() + register_list_indexes_tool().
- Registered unconditionally in the server's tool registration, alongside search/upsert.
- Output is deterministic and ordered by configured binding.
- TDD: unit coverage for field omission, description/limits inclusion rules,
  redis_name secrecy, read-only reflection, and registration; integration test
  verifying fields are derived from the inspected schema across a vector and a
  fulltext binding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the opaque `rt: Any` parameters in list_indexes.py with the concrete
`BindingRuntime` type and the clearer name `binding_runtime`, and type the
`server` parameters as `RedisVLMCPServer` (via a TYPE_CHECKING import to avoid
the server<->tools import cycle). No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ead (RAAE-1605)

Address review on list_indexes.py:

- Remove the `tool_list_indexes_description` override: that setting does not
  exist on MCPSettings (only tool_search/upsert_description do), so the getattr
  branch was always None and never fired. Pass the default description constant
  directly.
- Read the read scope as `auth_config.read_scope` (a typed field on
  MCPAuthConfig) instead of a silent `getattr(..., "read_scope", None)`. The
  old form would fail open — silently yielding None and skipping auth
  enforcement — if the field were ever renamed; direct access fails loud.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vishal-bala vishal-bala force-pushed the feature/raae-1605-list-indexes branch from 14bbdbf to 2276985 Compare June 30, 2026 20:18

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2276985. Configure here.

Comment thread redisvl/mcp/tools/list_indexes.py
…605)

list_indexes registered the tool instance-level, so it can still be called
before startup or after shutdown when _bindings is empty. Returning
{"indexes": []} there is misleading — a client reads it as "no indexes
configured" rather than "server not ready". Guard with the same
"MCP server has not been started" RuntimeError that resolve_binding raises.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vishal-bala

Copy link
Copy Markdown
Collaborator Author

Failing tests are because of the Redis 8.8 flakiness issue - proceeding with the merge

@vishal-bala vishal-bala merged commit 6bd9d30 into feature/raae-1603-mcp-multi-index Jul 1, 2026
53 of 54 checks passed
@vishal-bala vishal-bala deleted the feature/raae-1605-list-indexes branch July 1, 2026 09:41
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