Skip to main content

Extend Vanta with a tool

Add a new capability that's kernel-gated like every built-in. (To add a whole external toolset instead, mount an MCP server — no code.)

1. Create the tool

vanta-ts/src/tools/word-count.ts:

import { z } from "zod";
import type { Tool } from "./types.js";
import { resolveInScope } from "../scope.js";

const Args = z.object({ path: z.string() });

export const wordCount: Tool = {
schema: {
name: "word_count",
description: "Count the words in a file.",
parameters: { type: "object", properties: { path: { type: "string" } }, required: ["path"] },
},
describeForSafety: (a) => `read file ${a.path}`, // only the path goes to the kernel
async execute(rawArgs, ctx) {
const parsed = Args.safeParse(rawArgs);
if (!parsed.success) return { ok: false, output: "word_count: path is required" };
const inScope = resolveInScope(parsed.data.path, ctx.root);
if (!inScope.ok) return { ok: false, output: `out of scope: ${parsed.data.path}` };
const text = await ctx.readFile(inScope.path);
return { ok: true, output: `${text.trim().split(/\s+/).filter(Boolean).length} words` };
},
};

2. Register it

Add it to the ALL_TOOLS array in vanta-ts/src/tools/all-tools.ts (not index.ts).

3. Test it

Co-locate word-count.test.ts, then:

cd vanta-ts && npm test -- word-count
npm run typecheck # must be clean

The size gate runs on every write — keep the file ≤300 lines, the function ≤50.

4. Use it

Restart Vanta; the model now sees word_count in its scoped catalog (and can always reach it via tool_search). The kernel classifies the describeForSafety string before each call.

Rules of the road

  • Parse args with zod — it's an LLM boundary.
  • Path args → resolveInScope; return {ok:false} if outside.
  • Return errors as values; never throw across the boundary.
  • Rule Zero still applies — destructive/out-of-scope actions are gated by the kernel regardless of your tool.

For providers, search backends, plugins, and MCP, see Extending Vanta.