Skip to content

feat(mcp): add custom tool profile config models - #676

Open
vishal-bala wants to merge 1 commit into
feat/mcp-custom-tool-profilesfrom
feat/mcp-profile-config
Open

feat(mcp): add custom tool profile config models#676
vishal-bala wants to merge 1 commit into
feat/mcp-custom-tool-profilesfrom
feat/mcp-profile-config

Conversation

@vishal-bala

@vishal-bala vishal-bala commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Stack position: 2 of 3. Base is #675.

The declarative surface for custom tool profiles: what an author may put in a custom_tools: YAML entry, and every way that entry can be wrong.

A profile is search-records with some arguments frozen by the author and the rest still exposed. lock holds what the author decides; params holds the exposure and cap policy for what the model may still pass. Anything unlisted stays exposed, so a profile that only locks a filter keeps the rest of the built-in's contract. A locked projection is the one implicit case: locking it means the model cannot also choose one.

Read this before approving: it must not reach main alone

Nothing consumes these models yet — registration and execution land in #676. That ordering is deliberate, because this is the complete authoring contract and is fully testable on its own, so it reads before the machine that honors it.

But this must not be merged to main by itself. A custom_tools: block would validate here and then register nothing, with no warning and no error — exactly the silent-misconfiguration failure these models otherwise exist to prevent. That is the whole reason the stack targets an integration branch instead of main.

Validation

Front-loaded to config load wherever the information exists there, since the alternative is a tool that looks locked and enforces nothing. Covered:

  • a name that collides with a built-in, uses a reserved redisvl-/redisvl_ prefix (both separators, since the name pattern permits either), or falls outside the character set MCP hosts commonly accept
  • a duplicate tool name
  • a missing index when several bindings exist, or one naming an unknown binding
  • a params key that is not a real argument
  • max on anything but limit
  • a cap above the bound index's own runtime.max_limit, which could never be satisfied
  • hiding query, which a search profile needs
  • locking return_fields while also exposing them

Field checks that need the inspected schema stay at startup, in #676.

All three models set extra="forbid". A misspelled key would otherwise be dropped in silence, which is the worst outcome available here: lock: {return_field: [...]} would read as a locked projection while enforcing nothing at all.

Verification

  • MCP unit tests: 304 passing
  • make check-types: clean

Note

Medium Risk
Config-only change with no runtime wiring yet—safe in isolation but merging without follow-up would silently ignore authored profiles; validation rules affect how operators author MCP tools.

Overview
Adds a custom_tools YAML surface and Pydantic models so authors can declare search profile tools: specialized wrappers around search-records with frozen lock values (filter, return_fields), per-argument params exposure and limit caps, optional index pinning, and strict naming rules (built-in collisions, redisvl- / redisvl_ prefixes, MCP-friendly name pattern).

MCPConfig now loads and validates profiles at config time: duplicate names, index requirements when multiple bindings exist, unknown param keys, forbidden policies (hidden query, max on non-limit args, lock vs expose conflicts for return_fields), and params.limit.max above the binding’s runtime.max_limit. Helpers param_exposed, param_max, and resolved_profile_index encode how a future registrar should expose arguments. Schema-dependent lock checks are deferred to startup in a follow-up PR; nothing registers these tools yet, so custom_tools alone would validate but not publish tools until registration lands.

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

Base automatically changed from feat/mcp-locked-filter-merge to feat/mcp-custom-tool-profiles August 14, 2026 14:57
The declarative surface for custom tool profiles: what an author may put
in a `custom_tools:` YAML entry, and every way that entry can be wrong.

A profile is `search-records` with some arguments frozen by the author and
the rest still exposed. `lock` holds what the author decides;`params`
holds the exposure and cap policy for what the model may still pass.
Anything unlisted stays exposed, so a profile that only locks a filter
keeps the rest of the built-in's contract. A locked projection is the one
implicit case: locking it means the model cannot also choose one.

Nothing consumes these models yet -- registration and execution land in
the next commit in this stack. That ordering is deliberate: this is the
complete authoring contract and it is fully testable on its own, so it
reads before the machine that honors it. It must not reach main by itself,
though. A `custom_tools:` block would validate here and then register
nothing, with no warning -- exactly the silent-misconfiguration failure
these models otherwise guard against.

Validation is front-loaded to config load wherever the information exists
there, since the alternative is a tool that looks locked and enforces
nothing. Covered: a name that collides with a built-in, uses a reserved
`redisvl-`/`redisvl_` prefix (both separators, since the pattern permits
either), or falls outside the character set MCP hosts commonly accept; a
duplicate name; a missing `index` when several bindings exist, or one
naming an unknown binding; a `params` key that is not a real argument;
`max` on anything but `limit`; a cap above the bound index's own
`runtime.max_limit`, which could never be satisfied; hiding `query`, which
a search profile needs; and locking `return_fields` while also exposing
them. Field checks that need the inspected schema stay at startup.

All three models set `extra="forbid"`. A misspelled key would otherwise be
dropped in silence, which is the worst outcome available here: `lock:
{return_field: [...]}` would read as a locked projection while enforcing
nothing at all.
@vishal-bala
vishal-bala force-pushed the feat/mcp-profile-config branch from b2a1cb4 to 95485e5 Compare August 14, 2026 14:57
@vishal-bala
vishal-bala marked this pull request as ready for review August 14, 2026 14:57
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.

1 participant