Skip to main content
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

ToolsetPurpose
FunctionToolsetWrap a static array of tool definitions
FilteredToolsetShow or hide an entire toolset based on a per-turn predicate
PreparedToolsetFilter individual tools per turn with a prepare function
PrefixedToolsetAdd a namespace prefix to every tool name
RenamedToolsetRename specific tools with a mapping object
WrapperToolsetIntercept every tool call (middleware / logging / tracing)
CombinedToolsetMerge multiple toolsets into one
ApprovalRequiredToolsetMark every tool in a toolset as requiring human approval
ExternalToolsetExpose tools with raw JSON Schema (no Zod) executed outside the agent
MCPToolsetExpose all tools from one MCP server connection
MCPManagerManage and merge tools from multiple MCP servers

FunctionToolset

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:
MethodSignatureDescription
addTool(t)(ToolDefinition<TDeps>) => voidAppend 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] });

FilteredToolset

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] });

PreparedToolset

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"),
);
FilteredToolsetPreparedToolset
GranularityAll-or-nothingPer-tool
Predicate receives(ctx)(ctx, tools)
Use whenHiding entire toolset by role/flagFiltering individual tools by state

PrefixedToolset

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"

RenamedToolset

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",
});

WrapperToolset

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)],
});

CombinedToolset

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] });

ApprovalRequiredToolset

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.

ExternalToolset

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);
  }
}

MCPToolset

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:
MethodDescription
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:
MethodSignatureDescription
addServer(client, opts?)(MCPClient, opts?) => thisRegister 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 | undefinedAggregated instructions from all servers
serverCountnumberNumber 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.