The SDK ships ten toolset classes in jsr:@vibesjs/sdk. All implement the Toolset<TDeps> interface and can be passed in the toolsets array when constructing an Agent.
import {
FunctionToolset,
FilteredToolset,
PreparedToolset,
PrefixedToolset,
RenamedToolset,
WrapperToolset,
CombinedToolset,
ApprovalRequiredToolset,
ExternalToolset,
} from "jsr:@vibesjs/sdk";
MCP-specific toolsets live in the same package and are covered below:
import {
MCPToolset,
MCPManager,
MCPStdioClient,
MCPHttpClient,
} from "jsr:@vibesjs/sdk";
Summary
| Toolset | Purpose |
|---|
FunctionToolset | Wrap a static array of tool definitions |
FilteredToolset | Show or hide an entire toolset based on a per-turn predicate |
PreparedToolset | Filter individual tools per turn with a prepare function |
PrefixedToolset | Add a namespace prefix to every tool name |
RenamedToolset | Rename specific tools with a mapping object |
WrapperToolset | Intercept every tool call (middleware / logging / tracing) |
CombinedToolset | Merge multiple toolsets into one |
ApprovalRequiredToolset | Mark every tool in a toolset as requiring human approval |
ExternalToolset | Expose tools with raw JSON Schema (no Zod) executed outside the agent |
MCPToolset | Expose all tools from one MCP server connection |
MCPManager | Manage and merge tools from multiple MCP servers |
The simplest toolset: wraps an array of ToolDefinition objects. Use it when you have a fixed set of tools that do not need dynamic filtering.
constructor(tools?: ToolDefinition<TDeps>[])
Methods:
| Method | Signature | Description |
|---|
addTool(t) | (ToolDefinition<TDeps>) => void | Append a tool after construction |
tools() | () => ToolDefinition<TDeps>[] | Return all tool definitions |
import { FunctionToolset, tool } from "jsr:@vibesjs/sdk";
import { z } from "npm:zod";
const searchTool = tool({
name: "search",
description: "Search the web",
parameters: z.object({ query: z.string() }),
execute: async (_ctx, { query }) => fetchResults(query),
});
const toolset = new FunctionToolset([searchTool]);
toolset.addTool(anotherTool);
const agent = new Agent({ model, toolsets: [toolset] });
Wraps an inner toolset with a per-turn boolean predicate. When the predicate returns false the entire inner toolset is hidden from the model for that turn. When it returns true the full inner toolset is exposed.
This is an all-or-nothing gate. For per-tool filtering, use PreparedToolset.
constructor(
inner: Toolset<TDeps>,
predicate: (ctx: RunContext<TDeps>) => boolean | Promise<boolean>,
)
import { FilteredToolset } from "jsr:@vibesjs/sdk";
// Only expose admin tools when the user is an admin
const adminGated = new FilteredToolset(
adminToolset,
(ctx) => ctx.deps.user.isAdmin,
);
const agent = new Agent({ model, toolsets: [adminGated] });
Wraps an inner toolset with a prepare function that runs once per turn. Unlike FilteredToolset, which hides the entire set, PreparedToolset can add, remove, or reorder individual tools.
constructor(
inner: Toolset<TDeps>,
prepare: PrepareFunction<TDeps>,
)
type PrepareFunction<TDeps> = (
ctx: RunContext<TDeps>,
tools: ToolDefinition<TDeps>[],
) => ToolDefinition<TDeps>[] | Promise<ToolDefinition<TDeps>[]>;
import { PreparedToolset } from "jsr:@vibesjs/sdk";
// Hide the "delete" tool until the user has explicitly confirmed
const safe = new PreparedToolset(
adminTools,
(ctx, tools) =>
ctx.deps.confirmed
? tools
: tools.filter((t) => t.name !== "delete"),
);
| FilteredToolset | PreparedToolset |
|---|
| Granularity | All-or-nothing | Per-tool |
| Predicate receives | (ctx) | (ctx, tools) |
| Use when | Hiding entire toolset by role/flag | Filtering individual tools by state |
Adds a string prefix to every tool name in the wrapped toolset. Useful when composing two toolsets that define tools with the same name.
constructor(inner: Toolset<TDeps>, prefix: string)
import { PrefixedToolset } from "jsr:@vibesjs/sdk";
const prefixed = new PrefixedToolset(searchToolset, "web_");
// A tool named "search" becomes "web_search"
Renames specific tools using a { oldName: newName } mapping. Tools whose names are not in the map are passed through unchanged.
constructor(inner: Toolset<TDeps>, nameMap: Record<string, string>)
import { RenamedToolset } from "jsr:@vibesjs/sdk";
const renamed = new RenamedToolset(myToolset, {
search: "find",
delete: "remove",
});
An abstract base class that intercepts every tool execution in the wrapped toolset. Subclass it and implement callTool to add cross-cutting behaviour: logging, metrics, rate limiting, argument transformation, or result transformation.
abstract class WrapperToolset<TDeps = undefined> implements Toolset<TDeps> {
constructor(inner: Toolset<TDeps>)
abstract callTool(
ctx: RunContext<TDeps>,
toolName: string,
args: Record<string, unknown>,
next: ToolCallNext<TDeps>,
): Promise<unknown>;
}
type ToolCallNext<TDeps> = (
ctx: RunContext<TDeps>,
args: Record<string, unknown>,
) => Promise<unknown>;
Call next(ctx, args) to invoke the underlying tool. You may modify args before forwarding or transform the return value afterward.
import { WrapperToolset } from "jsr:@vibesjs/sdk";
import type { ToolCallNext, RunContext } from "jsr:@vibesjs/sdk";
class LoggingToolset extends WrapperToolset<MyDeps> {
async callTool(
ctx: RunContext<MyDeps>,
toolName: string,
args: Record<string, unknown>,
next: ToolCallNext<MyDeps>,
): Promise<unknown> {
console.log(`[tool] ${toolName}`, args);
const result = await next(ctx, args);
console.log(`[tool] ${toolName} →`, result);
return result;
}
}
const agent = new Agent({
model,
toolsets: [new LoggingToolset(myToolset)],
});
Merges any number of toolsets into one. Tools are collected from all members on each turn. When two toolsets expose a tool with the same name, the last one in the constructor argument list wins.
constructor(...toolsets: Toolset<TDeps>[])
import { CombinedToolset } from "jsr:@vibesjs/sdk";
const combined = new CombinedToolset(searchToolset, fetchToolset, dbToolset);
const agent = new Agent({ model, toolsets: [combined] });
Wraps a toolset and sets requiresApproval: true on every tool inside it. When the model calls any of these tools, the run pauses and throws ApprovalRequiredError with a DeferredToolRequests object.
constructor(inner: Toolset<TDeps>)
import { ApprovalRequiredToolset, ApprovalRequiredError } from "jsr:@vibesjs/sdk";
const gated = new ApprovalRequiredToolset(dangerousToolset);
const agent = new Agent({ model, toolsets: [gated] });
try {
const result = await agent.run("Delete all temporary files");
} catch (err) {
if (err instanceof ApprovalRequiredError) {
// Inspect pending calls
for (const req of err.deferred.requests) {
console.log(`Pending: ${req.toolName}(${JSON.stringify(req.args)})`);
}
// Approve and resume
const result = await agent.resume(err.deferred, {
results: err.deferred.requests.map((r) => ({
toolCallId: r.toolCallId,
result: "approved",
})),
});
}
}
See Human-in-the-Loop for the full approval workflow.
For tools whose execution happens outside the agent process — in the browser, a sandboxed environment, or a remote service. The agent is shown the tools and can call them; each call pauses the run and throws ApprovalRequiredError so the caller can execute the tool externally and return results.
Uses raw JSON Schema instead of Zod for parameter validation.
type ExternalToolDefinition = {
name: string;
description: string;
jsonSchema: Record<string, unknown>;
};
constructor(definitions: ExternalToolDefinition[])
import {
ExternalToolset,
ApprovalRequiredError,
type DeferredToolResults,
} from "jsr:@vibesjs/sdk";
const browserTools = new ExternalToolset([
{
name: "click",
description: "Click a DOM element by CSS selector",
jsonSchema: {
type: "object",
properties: { selector: { type: "string" } },
required: ["selector"],
},
},
]);
const agent = new Agent({ model, toolsets: [browserTools] });
try {
await agent.run("Click the submit button");
} catch (err) {
if (err instanceof ApprovalRequiredError) {
// Execute externally, collect results
const results: DeferredToolResults = {
results: await executeInBrowser(err.deferred.requests),
};
const finalResult = await agent.resume(err.deferred, results);
}
}
Wraps a single MCPClient and adapts it to the Toolset interface. Tools are fetched lazily on the first call to tools() and cached for toolCacheTtlMs milliseconds.
constructor(client: MCPClient, options?: MCPToolsetOptions)
type MCPToolsetOptions = {
toolCacheTtlMs?: number; // default: 60_000
instructions?: boolean; // default: true
};
Methods:
| Method | Description |
|---|
tools(ctx) | Return cached tool list from the MCP server |
getServerInstructions() | Return server-provided instruction text if any |
invalidateCache() | Force tool list re-fetch on next tools() call |
import { MCPStdioClient, MCPToolset, Agent } from "jsr:@vibesjs/sdk";
import { anthropic } from "npm:@ai-sdk/anthropic";
const client = new MCPStdioClient({
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
});
await client.connect();
const toolset = new MCPToolset(client, { toolCacheTtlMs: 30_000 });
const agent = new Agent({ model: anthropic("claude-opus-4-5"), toolsets: [toolset] });
try {
const result = await agent.run("List files in /tmp");
console.log(result.output);
} finally {
await client.disconnect();
}
See MCP Client for the full guide including MCPManager and config-file loading.
MCPManager
Manages multiple MCP server connections, connects them in parallel, and merges their tools into a single flat list. Implements Toolset directly — pass it to an agent without further wrapping.
constructor() // no arguments
Methods:
| Method | Signature | Description |
|---|
addServer(client, opts?) | (MCPClient, opts?) => this | Register an MCP server (chainable) |
connect() | () => Promise<void> | Connect all registered servers in parallel |
disconnect() | () => Promise<void> | Disconnect all servers. Throws AggregateError on partial failure. |
tools(ctx) | (RunContext) => Promise<ToolDefinition[]> | Merged tool list (last-wins on name collision) |
getServerInstructions() | () => string | undefined | Aggregated instructions from all servers |
serverCount | number | Number of registered servers |
import { MCPManager, MCPStdioClient, MCPHttpClient, Agent } from "jsr:@vibesjs/sdk";
import { anthropic } from "npm:@ai-sdk/anthropic";
const manager = new MCPManager()
.addServer(new MCPStdioClient({ command: "npx", args: ["-y", "@mcp/filesystem", "/data"] }), { name: "fs" })
.addServer(new MCPHttpClient({ url: "https://search-mcp.example.com/mcp" }), { name: "search" });
await manager.connect();
const agent = new Agent({
model: anthropic("claude-opus-4-5"),
toolsets: [manager],
});
try {
const result = await agent.run("Search for docs and list /data/results");
console.log(result.output);
} finally {
await manager.disconnect();
}
MCPManager and MCPToolset live in the MCP module but are re-exported from the main jsr:@vibesjs/sdk entry point alongside the other toolsets.