Skip to content

feat(mcp): add index routing to search-records (RAAE-1606)#631

Open
vishal-bala wants to merge 5 commits into
feature/raae-1603-mcp-multi-indexfrom
feature/raae-1606-search-routing
Open

feat(mcp): add index routing to search-records (RAAE-1606)#631
vishal-bala wants to merge 5 commits into
feature/raae-1603-mcp-multi-indexfrom
feature/raae-1606-search-routing

Conversation

@vishal-bala

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

Copy link
Copy Markdown
Collaborator

Motivation

With a single MCP server now able to expose multiple logical index bindings (RAAE-1604) and advertise them via list-indexes (RAAE-1605), the search-records tool still implicitly resolved the sole binding. On a multi-binding server it had no way to say which index a query should run against. This change (RAAE-1606) makes that routing explicit while keeping the public contract backwards-safe for existing single-index callers.

The design goal is that v1 clients see no behavioral change: when exactly one binding is configured and the caller omits index, the request resolves to that binding exactly as before. Routing only becomes mandatory once ambiguity exists. Query construction, validation, pagination, filtering, and the configured search mode all remain owned by the selected binding — this ticket adds only the selection and a confirmation echo, not new query behavior.

Implemented changes

search-records gains an optional index argument naming the logical binding to query. Resolution flows through the shared resolve_binding routing introduced in RAAE-1604, so the three cases fall out consistently: an omitted index with one binding resolves to that binding; an omitted index with multiple bindings returns invalid_request; and an unknown id returns invalid_request. The resolved logical id is echoed back as the index field of the response payload so multi-index clients can confirm where a query actually ran. All downstream work (limits, schema, vectorizer, search config) continues to read from the resolved binding's runtime.

When multiple bindings are configured the tool description is ambiguous about fields, so instead of emitting per-field filter hints it now appends a short routing note directing the client to call list-indexes first and pass the chosen id as index. Single-binding servers keep their full schema-derived description unchanged.

Minor additional changes:

  • The FastMCP wrapper exposes index as a tool parameter so MCP clients can supply it.
  • Unit coverage for default-to-sole-binding, named routing, unknown-id rejection, the wrapper param, and the ambiguous-schema description note.
  • Integration coverage for a two-binding server: routing to each named binding, omitted-index rejection, unknown-id rejection, and single-binding echo.

Verification

  • make format (isort + black) and mypy clean on changed files.
  • Full MCP suite: 232 passed, 2 skipped (Redis-version-gated) across unit + integration.

Stacking

This PR targets feature/raae-1605-list-indexes so its diff stays scoped to search routing. Review/merge bottom-up: #629#630 → this PR.

🤖 Generated with Claude Code


Note

Low Risk
Backward-compatible for single-index callers; changes are limited to MCP search routing and an additive response field, with validation delegated to existing resolve_binding.

Overview
The MCP search-records tool now accepts an optional index argument so clients can target a specific logical binding on multi-index servers. Resolution goes through resolve_binding: omitting index still works when only one binding exists; with multiple bindings, index is required and unknown ids return invalid_request.

Successful responses include a new index field with the resolved binding id so callers can confirm routing. The FastMCP wrapper exposes index as a tool parameter.

When the server has multiple indexes, the tool description skips per-schema filter/return-field hints and instead tells clients to call list-indexes and pass the chosen id as index. Single-index servers keep the full schema-derived description.

Unit and integration tests cover named routing, omitted-index errors on multi-binding setups, unknown-index rejection, single-binding echo behavior, and the ambiguous-schema description.

Reviewed by Cursor Bugbot for commit c2f30d5. 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

@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
@vishal-bala vishal-bala marked this pull request as ready for review June 30, 2026 20:14

@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 2 potential issues.

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 80a6a78. Configure here.

Comment thread redisvl/mcp/tools/upsert.py
Comment thread redisvl/mcp/tools/upsert.py
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
@vishal-bala vishal-bala force-pushed the feature/raae-1606-search-routing branch from 80a6a78 to 3d41055 Compare June 30, 2026 20:20
vishal-bala and others added 2 commits July 1, 2026 11:19
…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>
Add an optional `index` argument to the search-records tool so a single
multi-binding MCP server can target a specific logical index. The argument
is optional when exactly one binding is configured (preserving single-index
behavior) and resolves through the same resolve_binding routing used
elsewhere, so an omitted index on a multi-binding server and unknown ids both
surface as invalid_request. The resolved logical id is echoed back as the
`index` field in the response.

- Expose `index` on the FastMCP wrapper param list.
- Append a routing note to the tool description when the schema is ambiguous
  (multiple bindings) directing clients to call list-indexes first.
- Add unit + integration coverage for routing, omitted-index rejection,
  unknown ids, and single-binding backward compatibility.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vishal-bala vishal-bala force-pushed the feature/raae-1606-search-routing branch from 3d41055 to c2f30d5 Compare July 1, 2026 09:21
Base automatically changed from feature/raae-1605-list-indexes to feature/raae-1603-mcp-multi-index 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