Tools
Add a capability with a typed schema
A tool is a typed function the agents can call through the MCP server — a validated input schema, a capability gate, usage guidance, and a run function with a rich context.
defineTool({
name: "repo:stale-branches",
description: "List branches with no commits in the last N days.",
capability: "repo:read", // gated per role
input: z.object({
days: z.number().default(30),
exclude: z.array(z.string()).default(["main", "release/*"]),
}),
guidance: { when: "before a cleanup pass", notWhen: "mid-release" },
run: async ({ days, exclude }, ctx) => {
const stale = await ctx.git.branchesOlderThan(days, exclude)
return { count: stale.length, branches: stale }
},
})
And agents compose existing tools into new ones — calling them exactly the way they call any tool — so the harness extends itself.
// a composed tool: one new capability built from existing ones,
// publishable to the Cupboard like any other tool
defineTool({
name: "repo:cleanup",
description: "Prune stale branches and orphaned files in one pass.",
capability: "repo:write",
input: z.object({ days: z.number().default(30) }),
run: async ({ days }, { tools }) => {
const stale = await tools.repo.staleBranches({ days })
const orphans = await tools.repo.orphanFiles()
return { pruned: stale.count + orphans.length }
},
})
Every tool declares its effect — and a write tool can be dry-run first, so an agent (or you) previews exactly what it would change before anything commits.
defineTool({
name: "repo:cleanup",
effect: "write", // read | write — write tools can preview
capability: "repo:write",
input: z.object({ days: z.number().default(30) }),
run: async ({ days }, ctx) => { /* … */ },
})
// preview the mutation without touching anything:
code:run { script: "return repo.cleanup({ days: 60 })", dryRun: true }
// → the writes it WOULD make, nothing committed
Agents orchestrate many tools in one code:run script — one round-trip instead of many — and a script worth keeping is saved as a recipe any other agent can find and re-run. With more than 900 tools in the catalog, an agent finds the right one by intent.
// one script, many tools, one round-trip — kept as a reusable recipe
code:run { script: `
const stale = await tools.repo.staleBranches({ days: 60 })
const orphans = await tools.repo.orphanFiles()
return { pruned: stale.count + orphans.length }
` }
// → runs now, and is saved: recipes:search { query: "prune repo" }
// don't know the tool's name? find it by what you want to do:
tools:find { query: "delete stale git branches" }
// → ranks the 900+ verb catalog by intent; tools:invoke runs the match