pub struct ToolRegistry { /* private fields */ }Expand description
Tool registry: holds tools, responsible for lookup and execution by name.
use molo::tool::{SharedState, Tool, ToolError, ToolRegistry, ToolSchema};
use serde_json::json;
struct Calculator;
#[molo::async_trait]
impl Tool for Calculator {
fn schema(&self) -> ToolSchema {
ToolSchema {
name: "calculator".into(),
description: "Calculate".into(),
parameters: json!({ "type": "object", "properties": {} }),
}
}
async fn call(
&self,
_arguments: serde_json::Value,
_state: &SharedState,
) -> Result<String, ToolError> {
Ok("42".into())
}
}
let mut registry = ToolRegistry::new();
registry.register(Calculator);
let state = SharedState::new();
// The model requests a tool by name; error classification (not
// registered / args not JSON / execution failed) rides along with Err.
let result = registry.call("calculator", "{}", &state).await?;
assert_eq!(result, "42");
// Allowlist subset: the sub-registry shares the same tool instances as
// the main registry.
let sub = registry.subset(&["calculator"]).unwrap();
assert_eq!(sub.names(), vec!["calculator"]);- same-named tools: later registration replaces — registering a same-named tool replaces it in place, so the registry never holds duplicates (the semantics of updating a registered tool, no new entry, stable order);
- internally held as a single
IndexMap<String, Arc<dyn Tool>>: O(1) lookup by name while preserving registration order (order affects how the model chooses tools on the wire); Arc<dyn Tool>sharing: tool instances can be shared across registries — the sub-registry produced bysubsetshares the same tool instances as the main registry (the scenario where a main agent creates sub-agents with a restricted tool set);callreturnsResult<String, RegistryError>— classification rides along withErr(tool not found / args not JSON / execution failed), andErr’s Display is the “error-to-text” the agent loop can pass straight back to the model (seeRegistryError); callers that need to bypass the registry’s argument parsing can grab the tool directly withget;Debugprints the registration-name list (in registration order, handy for debugging).
Implementations§
Source§impl ToolRegistry
impl ToolRegistry
Sourcepub fn register(&mut self, tool: impl Tool + 'static) -> &mut Self
pub fn register(&mut self, tool: impl Tool + 'static) -> &mut Self
Register a tool; returns self for chaining.
When a same-named tool is registered again, the later one replaces the earlier (keeping its original position), which fits updating a registered tool with a new instance.
Sourcepub fn names(&self) -> Vec<String>
pub fn names(&self) -> Vec<String>
Names of currently registered tools, in registration order (same-named tools already deduplicated).
Sourcepub fn remove(&mut self, name: &str) -> bool
pub fn remove(&mut self, name: &str) -> bool
Remove a registered tool; returns true when removed, false when
the tool does not exist.
For swapping tool sets at runtime (e.g. removing framework-injected tools when switching assembly modes); remaining tools keep their registration order.
Sourcepub fn retain(&mut self, keep: impl FnMut(&str) -> bool) -> Vec<String>
pub fn retain(&mut self, keep: impl FnMut(&str) -> bool) -> Vec<String>
Bulk-trim in place by name: removes tools whose names fail keep,
returning the removed tool names (in original registration order);
remaining tools keep their registration order.
Complements subset: subset leaves this
table untouched and produces an allowlist sub-table sharing tool
instances with the main table; this method mutates this table in
place, suitable for bulk-removing tools at runtime — e.g. clearing
all tools of an MCP server by its namespace prefix when unloading
it:
use molo::tool::{SharedState, Tool, ToolError, ToolRegistry, ToolSchema};
use serde_json::json;
struct Named(&'static str);
#[molo::async_trait]
impl Tool for Named {
fn schema(&self) -> ToolSchema {
ToolSchema {
name: self.0.into(),
description: self.0.into(),
parameters: json!({}),
}
}
async fn call(
&self,
_arguments: serde_json::Value,
_state: &SharedState,
) -> Result<String, ToolError> {
Ok("ok".into())
}
}
let mut registry = ToolRegistry::new();
registry
.register(Named("filesystem__read_file"))
.register(Named("filesystem__list_dir"))
.register(Named("calculator"));
// Strip all tools of an MCP server (their names carry the
// "filesystem__" prefix):
let removed = registry.retain(|name| !name.starts_with("filesystem__"));
assert_eq!(removed, ["filesystem__read_file", "filesystem__list_dir"]);
assert_eq!(registry.names(), ["calculator"]);Sourcepub fn schemas(&self) -> Vec<ToolSchema>
pub fn schemas(&self) -> Vec<ToolSchema>
All tools’ definitions for the model, in registration order (no duplicates, guaranteed at registration).
Sourcepub fn get(&self, name: &str) -> Option<&dyn Tool>
pub fn get(&self, name: &str) -> Option<&dyn Tool>
Get a tool reference by name, bypassing the registry’s argument
parsing to call Tool::call directly; returns None when not
registered.
Sourcepub async fn call(
&self,
name: &str,
arguments: &str,
state: &SharedState,
) -> Result<String, RegistryError>
pub async fn call( &self, name: &str, arguments: &str, state: &SharedState, ) -> Result<String, RegistryError>
Look up and execute a tool by name.
A single call completes three steps — “lookup → argument parsing →
execution”; failure classifications are described by
RegistryError:
- tool not registered →
RegistryError::NotFound; - arguments not valid JSON, or valid JSON with a mismatched
structure (missing fields, etc.) →
RegistryError::InvalidArguments; - tool execution failed →
RegistryError::Execution(the underlyingToolErroris reachable viasource()).
state is injected into the tool at call time (see
SharedState); the agent loop passes its own state straight
through, so tools read and write the caller-provided instance.
Tool panics do not escape this method: the panic is caught and
converted into RegistryError::Execution,
with the message carrying the tool name and panic content, for the
caller to pass back to the model.
§Errors
The three failure classes are described above; Err’s Display is
the “error-to-text” — the agent loop passes e.to_string() back
to the model as a ToolResult, and the text is directly readable by
the model.
Sourcepub fn subset(&self, names: &[&str]) -> Result<ToolRegistry, MissingTools>
pub fn subset(&self, names: &[&str]) -> Result<ToolRegistry, MissingTools>
Trim a sub-registry by name (an allowlist) sharing the same tool instances as the main registry.
Used to restrict a sub-agent’s tool set: when the main agent creates a sub-agent, this method trims an allowlisted registry, and both tables share the same tool instances (consistent state). The sub-registry keeps the main registry’s registration order.
§Errors
When an allowlisted name is not found in the main registry,
MissingTools is returned; what to do with the missing list
(error / warn / silent) is the caller’s decision — the library
does not choose for the caller.
Trait Implementations§
Source§impl Clone for ToolRegistry
impl Clone for ToolRegistry
Source§fn clone(&self) -> ToolRegistry
fn clone(&self) -> ToolRegistry
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more