pub struct ToolRegistry { /* private fields */ }Expand description
Tool registry: holds tools, responsible for lookup and dispatch by name.
use molo::tool::{SharedState, Tool, ToolContext, ToolError, ToolOutput, ToolRegistry, ToolResult, ToolSchema};
use molo::{RunContext, ToolCall};
use serde_json::json;
struct Calculator;
#[molo::async_trait]
impl Tool for Calculator {
fn schema(&self) -> ToolSchema {
ToolSchema::new("calculator", "Calculate", json!({ "type": "object", "properties": {} }))
}
async fn call(
&self,
_arguments: serde_json::Value,
_context: ToolContext<'_>,
) -> Result<ToolResult, ToolError> {
Ok(ToolOutput::text("42").into())
}
}
let mut registry = ToolRegistry::new();
registry.register(Calculator);
let state = SharedState::new();
let run = RunContext::new("run-1");
let call = ToolCall {
id: "call-1".into(),
name: "calculator".into(),
arguments: "{}".into(),
};
// The model requests a tool by name; error classification (not
// registered / args not JSON / execution failed) rides along with Err.
let result = registry.call(&call, &run, &state).await?;
assert_eq!(result.output_content(), Some("42"));
// Allowlist subset: the sub-registry shares the same tool instances as
// the main registry.
let sub = registry.subset(&["calculator"])?;
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<ToolResult, 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 register_with_source(
&mut self,
tool: impl Tool + 'static,
source: ToolSource,
) -> Result<&mut Self, RegistryError>
pub fn register_with_source( &mut self, tool: impl Tool + 'static, source: ToolSource, ) -> Result<&mut Self, RegistryError>
Register a tool with host-facing source metadata.
Unlike register, this method rejects a same
provider-facing name coming from a different namespace. That prevents
accidental cross-extension shadowing while still allowing a host to
refresh tools inside the same namespace.
§Errors
Returns RegistryError::SourceNameMismatch when the source display
name does not match the tool schema name, and
RegistryError::NameCollision when another namespace already owns
the same provider-facing name.
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 source(&self, display_name: &str) -> Option<&ToolSource>
pub fn source(&self, display_name: &str) -> Option<&ToolSource>
Source metadata for a registered tool.
Tools registered with register have no explicit
source metadata and return None.
Sourcepub fn names_in_namespace(&self, namespace: &ToolNamespace) -> Vec<String>
pub fn names_in_namespace(&self, namespace: &ToolNamespace) -> Vec<String>
Names of tools that belong to a namespace.
Tools without explicit source metadata are treated as local tools for this query.
Sourcepub fn remove_namespace(&mut self, namespace: &ToolNamespace) -> Vec<String>
pub fn remove_namespace(&mut self, namespace: &ToolNamespace) -> Vec<String>
Remove every tool in a namespace and return removed names in their previous registration order.
This is the source-aware unload path for extension layers such as MCP server teardown.
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::{Tool, ToolContext, ToolError, ToolRegistry, ToolResult, ToolSchema};
use serde_json::json;
struct Named(&'static str);
#[molo::async_trait]
impl Tool for Named {
fn schema(&self) -> ToolSchema {
ToolSchema::new(self.0, self.0, json!({}))
}
async fn call(
&self,
_arguments: serde_json::Value,
_context: ToolContext<'_>,
) -> Result<ToolResult, 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 JSON argument
parsing to call Tool::call directly; returns None when not
registered.
Sourcepub async fn call(
&self,
call: &ToolCall,
run: &RunContext,
state: &SharedState,
) -> Result<ToolResult, RegistryError>
pub async fn call( &self, call: &ToolCall, run: &RunContext, state: &SharedState, ) -> Result<ToolResult, RegistryError>
Look up and dispatch a tool call.
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()).
run and state are injected into the tool through
ToolContext; 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 can pass e.to_string() back
to the model as a tool result, and the text is directly readable by
the model.
Sourcepub async fn call_named(
&self,
name: impl Into<String>,
arguments: impl Into<String>,
run: &RunContext,
state: &SharedState,
) -> Result<ToolResult, RegistryError>
pub async fn call_named( &self, name: impl Into<String>, arguments: impl Into<String>, run: &RunContext, state: &SharedState, ) -> Result<ToolResult, RegistryError>
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.
Sourcepub fn subset_by_namespace(
&self,
namespace: &ToolNamespace,
) -> Result<ToolRegistry, RegistryError>
pub fn subset_by_namespace( &self, namespace: &ToolNamespace, ) -> Result<ToolRegistry, RegistryError>
Constructs a source-aware sub-registry for one namespace.
The sub-registry shares tool instances with the main registry and preserves the main registry’s order.
§Errors
This method currently has no failure path. It returns Result to keep
the signature aligned with other subset APIs and leave room for future
namespace validation.
Trait Implementations§
Source§impl Clone for ToolRegistry
impl Clone for ToolRegistry
Source§impl Debug for ToolRegistry
impl Debug for ToolRegistry
Source§impl Default for ToolRegistry
impl Default for ToolRegistry
Source§impl<T> Extend<T> for ToolRegistrywhere
T: Tool + 'static,
impl<T> Extend<T> for ToolRegistrywhere
T: Tool + 'static,
Source§fn extend<I>(&mut self, iter: I)where
I: IntoIterator<Item = T>,
fn extend<I>(&mut self, iter: I)where
I: IntoIterator<Item = T>,
Source§fn extend_one(&mut self, item: T)
fn extend_one(&mut self, item: T)
extend_one)Source§fn extend_reserve(&mut self, additional: usize)
fn extend_reserve(&mut self, additional: usize)
extend_one)