Skip to main content
The tools array on an agent is simple and static: every tool in it is sent to the model on every turn. That works well when you have a handful of tools that are always relevant. Real applications are messier. You might have admin tools that only certain users can access. You might have tools that depend on which phase of a workflow the agent is in. You might be integrating tools from an external MCP server. You might want to add logging or rate-limiting across a whole group of tools without touching each one individually. Toolsets solve all of these. A toolset is an object with a single method, tools(ctx), that returns a list of tool definitions. Because it receives the RunContext, it can decide at the start of each turn which tools to expose. Agents accept any number of toolsets via the toolsets option, and Vibes merges them with the static tools array before each model call.

FunctionToolset — the basic collection

FunctionToolset is the simplest toolset: a plain wrapper around an array of tool definitions. Use it to logically bundle related tools and pass them as a unit.
  1. FunctionToolset takes an optional array in the constructor.
  2. addTool() lets you add tools incrementally — useful when building a toolset dynamically.
  3. Toolsets are passed to toolsets, not tools.

FilteredToolset — all-or-nothing gating

FilteredToolset wraps another toolset and hides it entirely when a predicate returns false. Use it when a whole category of tools should be invisible to the model based on the caller’s context — user role, feature flags, environment, and so on.
  1. The wrapped adminTools is either shown in full or hidden entirely — there’s no partial exposure.
  2. The predicate receives the RunContext and can be async. Return true to expose the inner toolset, false to hide it.
When false, the model won’t see any of these tools. It won’t try to call them and won’t know they exist.
FilteredToolset is all-or-nothing at the toolset level. If you need to filter individual tools within a toolset based on state, use PreparedToolset instead.

PreparedToolset — per-tool control per turn

PreparedToolset gives you fine-grained control over which tools inside a toolset are exposed each turn. The prepare function receives the full RunContext and the complete list of tools from the inner toolset, and returns whichever tools to expose.
  1. The tools parameter is the full resolved list from the inner toolset — you return a subset.
  2. You can apply multiple conditions in sequence. The function just returns an array.
FilteredToolset vs PreparedToolset at a glance:

PrefixedToolset — namespacing tool names

When you combine tools from multiple sources, name collisions become a real risk. PrefixedToolset prepends a string to every tool name in the wrapped toolset, giving you a clean namespace.
  1. The prefix is prepended to every tool name. "search_repos" becomes "github_search_repos". The model’s tool call will use the prefixed name, and Vibes routes it correctly.
For renaming specific tools rather than all of them, use RenamedToolset:

WrapperToolset — middleware for tool execution

WrapperToolset is an abstract base class that intercepts every tool call in the wrapped toolset. Subclass it and implement callTool to add cross-cutting behavior — logging, metrics, rate limiting, error transformation, argument sanitization — without touching the underlying tools.
  1. Provide the deps type as a generic parameter so ctx.deps is properly typed inside callTool.
  2. next is a function that invokes the original tool’s execute. You must call it (unless you want to short-circuit).
  3. You can modify args before calling next, or transform the result it returns.
  4. Re-throwing preserves normal error handling. You can also wrap in a custom error type here.
Another example — rate-limiting a toolset:

CombinedToolset — merging multiple toolsets

CombinedToolset merges any number of toolsets into one. The resolved tools from all member toolsets are combined into a single flat list. If two toolsets expose a tool with the same name, the last one wins.
CombinedToolset is especially useful when you want to aggregate several independently-defined toolsets and expose them as a single unit — for example, packaging a plugin or feature module.

ExternalToolset — tools that execute outside the agent

Sometimes a tool must run in a different environment than the agent’s process. A browser extension needs to manipulate the DOM. A desktop app needs to read local files. A sandboxed service needs to call a restricted API. ExternalToolset handles this. Each tool is described with a JSON Schema instead of Zod. When the model calls any of these tools, the run pauses and throws an ApprovalRequiredError containing the pending DeferredToolRequests. The caller executes the tools externally, collects the results, and calls agent.resume() to continue.
  1. Tool definitions use raw JSON Schema objects — no Zod dependency in the external environment.
  2. The run pauses immediately when an external tool is called.
  3. err.deferred.requests is an array of { toolName, args, toolCallId } objects describing what the model wants to do.
  4. agent.resume() feeds the results back and continues the run. The run may pause again if more external tool calls follow.
See Human-in-the-Loop for the full DeferredToolRequests / DeferredToolResults API and patterns for multi-step external execution.

MCPToolset — tools from MCP servers

The Model Context Protocol (MCP) is an open standard for exposing tools from external servers. MCPToolset connects to an MCP server and exposes all of its tools as a Vibes toolset. Tools are discovered lazily on the first call and cached for 60 seconds by default.
  1. MCPStdioClient spawns the MCP server as a child process. Use MCPHttpClient for HTTP-based servers.
  2. MCPToolset wraps the client and implements the Toolset interface. Tools are fetched from the server and cached.
  3. Disconnect the client when the agent is done to avoid leaving zombie processes.
For connecting to multiple MCP servers at once, use MCPManager:
You can also load server configurations from a JSON file (compatible with Claude Desktop format):

Composing toolsets

All toolset classes implement the same Toolset<TDeps> interface, so they compose freely. You can nest them to build precisely the behavior you need:
The composition reads from the outside in: LoggingToolset wraps a CombinedToolset that merges coreTools with namespacedAdmin. Each layer adds a single concern. None of the layers need to know about the others.

Tools

Build individual tools with the tool() factory

Human-in-the-Loop

Pause and resume runs for external tool execution and approval