tool_loader
Auto-discover and load tools from a directory of Python scripts.
Each .py file in the tools directory must expose either:
Single-tool format (one tool per file):
TOOL_NAME– unique name for the tool (str).TOOL_DESCRIPTION– human-readable description (str).TOOL_PARAMETERS– JSON Schemaobjectfor accepted args (dict).async def run(**kwargs) -> str– the tool handler.
Multi-tool format (multiple tools per file):
TOOLS– a list of dicts, each with keys:name,description,parameters,handler.
Malformed files are logged as warnings but do not prevent the rest of the tools from loading.
- tool_loader.load_tool_manifest_allowlist(tools_path)[source]
Read the manifest allow-list of tool script basenames.
Loads and parses
tools/_manifest.jsonto obtain the explicit set of*.pyfilenames thatload_toolsis permitted to import. This allow-list is the security gate that stops arbitrary or stray files in the tools directory from being executed at load time, so a missing or unreadable manifest deliberately yields an empty set (and therefore no tools load) rather than falling back to “load everything”.It locates the file via
_manifest_path, reads it from the filesystem, and JSON-decodes it; any read or parse error is logged and swallowed, returning an empty set. No Redis, network, or LLM interaction. Called byload_tools(to filter the directory scan) and byappend_tool_manifest(to merge in a new entry).
- tool_loader.append_tool_manifest(tool_basename, tools_dir='tools')[source]
Atomically add a tool basename to the manifest allow-list.
Extends
tools/_manifest.jsonso that a newly written tool script becomes loadable on the next scan. This is the write counterpart toload_tool_manifest_allowlistand exists so tools that author other tools at runtime can self-register without a manual manifest edit; the write is done atomically so a concurrent or crashed run never leaves a half-written manifest.It resolves the manifest via
_manifest_path(raising if it does not exist), mergestool_basenameinto the current allow-list read back viaload_tool_manifest_allowlist, then writes the JSON to a temp file in the same directory andos.replace-es it over the original (cleaning up the temp file on error). Filesystem only — no Redis, network, or LLM. Called by thewrite_python_tooltool handler (tools/write_python_tool.py), which invokes it viaasyncio.to_threadafter persisting a generated tool file.- Parameters:
- Raises:
FileNotFoundError – If the manifest file does not already exist.
- Return type:
- tool_loader.load_tools(directory, registry)[source]
Discover, import, and register every allow-listed tool script.
Walks a tools directory and turns each permitted
*.pyfile into one or more registered tools onregistry. This is the entry point that populates the live tool set the LLM can call, supporting both the single-tool module format (TOOL_NAME/TOOL_DESCRIPTION/TOOL_PARAMETERS/run) and the multi-toolTOOLSlist format, plus optional aliases and per-tool flags. It is written to be resilient: a malformed or failing file is logged and skipped so one bad tool never blocks the rest.It reads the manifest allow-list via
load_tool_manifest_allowlistand refuses to load anything not listed (an empty allow-list loads nothing). For each allowed file it imports the module throughimportlib— reusing an already-importedtools.<stem>fromsys.moduleswhen present, or executing the file fresh and registering it under both its canonical and a private alias name — then mutatesregistry._toolsdirectly to insertToolDefinitionentries (including anyTOOL_ALIASES). Touches the filesystem (directory glob and module exec) but no Redis, network, or LLM. Called from many places that build or rebuild the registry: the classifier index builders (classifiers/build_tool_index.py,classifiers/update_tool_embeddings.py), the livereload_toolstool, thewrite_python_toolandimport_mcp_toolhandlers after authoring a new tool, and several tests.- Parameters:
directory (
str|Path) – Path to the tools directory to scan (e.g."tools"). A missing directory is warned about and skipped.registry (
ToolRegistry) – TheToolRegistryto populate; its internal_toolsmapping is mutated in place.
- Return type: