Repository navigation
feat(mcp): load code tools from modules named in the config - #761
Open
vishal-bala wants to merge 9 commits into
Open
vishal-bala wants to merge 9 commits into
vishal-bala wants to merge 9 commits into
Conversation
@tool marks an async function as a code tool, with the same arguments as server.custom_tool. server.custom_tool_modules lists modules for the server to import at startup, and every function in them marked with @tool registers alongside tools from server.custom_tool, under the same startup checks. Importing a module runs its code with the server's privileges, so the list is refused unless server.allow_code_tools is true; it defaults to false. A module that cannot be imported or defines no tools fails startup by name.
…p-code-tools/04-module-loader # Conflicts: # redisvl/mcp/tools/code.py # tests/integration/test_mcp/test_code_tools.py # tests/unit/test_mcp/test_code_tools_unit.py # tests/unit/test_mcp/test_server_unit.py
@tool becomes @code_tool and server.custom_tool_modules becomes server.code_tool_modules, matching allow_code_tools and server.code_tool. A decorator applied over @code_tool copies the marker through functools.wraps while the spec still calls the inner function, so the outer decorator would never run; the loader now refuses it by name. The docs note that uvx needs the module installed in its environment, and that the import check has to run outside the project directory to mirror rvl.
…code-tools/04-module-loader
…code-tools/04-module-loader # Conflicts: # tests/unit/test_mcp/test_code_tools_unit.py
This was referenced Oct 20, 2026
vishal-bala
marked this pull request as ready for review
October 27, 2026 13:47
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
…code-tools/04-module-loader
limjoobin
approved these changes
Oct 30, 2026
…code-tools/04-module-loader
…code-tools/04-module-loader
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
server.code_tool(...)from the previous PR only helps when the caller's own code constructs the server. The common deployment isrvl mcp --config, which imports no user code. This PR lets the config name modules for the server to import. Because importing a module runs its code with the server's privileges, nothing is imported unless the operator opts in separately.Changes
@code_tool(...), exported fromredisvl.mcp, takes the same arguments asserver.code_tool. It checks the function and attaches its spec as an attribute, without registering it anywhere:At startup, the server imports each listed module before any binding connects. Every function marked with
@code_toolis validated and registered alongside tools fromserver.code_tool, under the same startup checks.Startup refuses, naming the module or setting:
code_tool_modulesis set withoutallow_code_tools: true(defaultfalse)@code_toolfunction@code_toolfunctools.wrapscopies the marker while the spec still calls the inner function, so the outer decorator would silently never run.The marker lives on the function, not in a module-level registry. Importing a module therefore registers nothing anywhere, and two servers in one process stay independent, as do tests that import a tool module. The loader considers only functions and reads the marker from each function's own
__dict__, so a module attribute with a permissive__getattr__, such as a lazy module proxy, is never consulted. A tool one listed module re-exports from another registers once.The docs gain "Code Tools Loaded From Modules" and a troubleshooting entry. They state that
rvldoes not put the working directory on the import path: modules must be installed or onPYTHONPATH, and underuvxinstalled in the environment it builds.Notes
The loader does not edit
sys.path. Adding the config's directory would make import resolution depend on where the config file happens to live.I verified the end-to-end flow manually with
rvl mcp --transport streamable-http, a scratch module onPYTHONPATHand the server started from outside the project directory. A FastMCP client listedsupport-count-open-ticketswith onlyproductin its schema, and a call returned the expected count.Release Notes
rvl mcpcan load code tools from Python modules. Mark async functions with@code_toolfromredisvl.mcp, list their modules underserver.code_tool_modules, and setserver.allow_code_tools: true, which defaults tofalsebecause importing a module runs its code with the server's privileges. Modules must be importable by the server process.Next Steps
code_toolnames, noctx.guarded(the handle applies the limits itself), id-only writes and no filter-wide bulk writes, no code tools on tenant-scoped indexes, async tools only, and the whole-call timeout.Review record
Reviewed at
d72b479by Claude Code (claude-opus-5-5) withpre-pr-review; fixes in9ef37cc.Lenses: correctness (always); security (imports a module named in config); design (new public
@code_tooland config keys); tests (adds unit and integration tests); docs (loader guide and docstrings); Redis behaviour (stack-wide pass, nothing in this PR). The lenses ran over the whole stack, and findings were attributed to the PR that introduced the line.tooldecorator that does not exist (tools/code.py:31);test_code_tool_modules_are_off_by_defaultduplicated the not-allowed case of the config-load test (test_config.py:1098, deleted).tool_modulefixture is copied between the unit and integration tests (about 15 lines; sharing it would mean the Docker-backed root conftest).Verified:
pytest tests/unit/test_mcp(581 passed);pytest -n 1 tests/integration/test_mcp(84 passed, 2 skipped, Redis 8);make check-types(no issues in 122 files); Sphinx build (no warnings from the new sections).Not checked: FastMCP 2.x (tested on 3.1.0 and 3.3.1); importing a module under
uvx(the guide's claim follows from howuvxbuilds its environment, not from a run).Note
Medium Risk
Adds config-driven Python module import at startup; misconfiguration or untrusted modules could run arbitrary code in the server process, though
allow_code_toolsdefaults off and pairs with strict load-time checks.Overview
rvl mcpcan now register code tools from Python modules, not only viaserver.code_tool()in an embedder process. Operators mark async functions with@code_tool(exported fromredisvl.mcp, same options as the server decorator), list modules underserver.code_tool_modules, and must setserver.allow_code_tools: true(defaults tofalse) because importing runs that code with server privileges.At startup the server imports listed modules before Redis connects, discovers functions tagged with
__redisvl_mcp_tool__, and registers them alongside programmatically registered tools under the same validation (unique names, index binding, read-only/write rules, tenant-scoped index checks). Config load rejects modules without the opt-in, blank/duplicate module names, failed imports, modules with no@code_toolfunctions, and@code_toolnot outermost when another decorator wraps it. Docs add a “loaded from modules” guide and troubleshooting for import path /PYTHONPATH(rvldoes not add the working directory ).Reviewed by Cursor Bugbot for commit fb074d1. Bugbot is set up for automated code reviews on this repo. Configure here.