AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Build Tool Factory

skill-techymt-claude-code-superpowers-build-tool-factory · by TechyMT

|

No reviews yet
0 installs
4 views
0.0% view→install

Install

$ agentstack add skill-techymt-claude-code-superpowers-build-tool-factory

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-techymt-claude-code-superpowers-build-tool-factory)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Build Tool Factory? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

buildTool() Factory

The pattern

Factory functions for capability objects solve a common problem: a typed interface has many methods, but any given implementation only needs to override a few. Listing all methods explicitly creates noise and makes the "what does this tool actually do" harder to see. A factory fills in safe defaults and lets the author focus on the non-trivial fields.

The pattern has three parts: (1) a minimal definition type (ToolDef) where defaultable methods are optional, (2) a factory (buildTool) that merges your definition with safe defaults, and (3) lazy schema getters so schemas are built on first access, not at module load time.

// DefaultableToolKeys — methods buildTool fills in with safe defaults
type DefaultableToolKeys =
  | 'isEnabled'
  | 'isConcurrencySafe'
  | 'isReadOnly'
  | 'isDestructive'
  | 'checkPermissions'
  | 'toAutoClassifierInput'
  | 'userFacingName'

// ToolDef — what you pass to buildTool (same as Tool but defaultable methods are optional)
export type ToolDef = Omit, DefaultableToolKeys> &
  Partial, DefaultableToolKeys>>

// buildTool fills in safe defaults for all DefaultableToolKeys
export function buildTool(def: D): BuiltTool

| Default key | Safe default value | |---|---| | isConcurrencySafe | () => false | | isReadOnly | () => false | | isDestructive | () => false | | checkPermissions | { behavior: 'allow' } | | isEnabled | () => true | | userFacingName | derived from name |

Why this matters

Claude Code has ~30 tools. Each shares the same set of safety defaults: assume not concurrent-safe, assume not read-only, assume not destructive, assume checkPermissions passes through. Without a factory, every new tool must explicitly declare all of these even when the intent is "no override". The factory encodes the security-safe posture as the default: new tools are restrictive until explicitly opted out.

The alternative — export const X: Tool = { ... } — forces the author to repeat every defaultable method for each tool. It also makes new tools opt-in to safety (you must explicitly set isDestructive: () => false) rather than opt-out (the factory sets it for you and you override only when needed).

How to apply it

  1. Create src/tools/[Name]Tool/[Name]Tool.ts (or .tsx if the tool renders JSX output)
  2. Define inputSchema with lazySchema(() => z.strictObject({...})) — use z.strictObject to reject unknown keys
  3. Define outputSchema the same way when the tool returns structured data the LLM needs to inspect
  4. Call buildTool({ name, inputSchema getter, call, ... }) — only override methods that differ from the defaults
  5. Override isConcurrencySafe and isReadOnly if your tool is side-effect-free (defaults are false)
  6. Implement checkPermissions if your tool accesses files, shell, or network
  7. Register the tool in src/tools.ts

In the source

// Source: src/tools/FileReadTool/FileReadTool.ts
import { buildTool, type ToolDef } from '../../Tool.js'
import { lazySchema } from '../../utils/lazySchema.js'
import { z } from 'zod/v4'

const inputSchema = lazySchema(() =>
  z.strictObject({
    file_path: z.string().describe('Absolute path to the file'),
    limit: z.number().int().positive().optional().describe('Max lines'),
  })
)
type InputSchema = ReturnType
export type Input = z.infer

export const FileReadTool = buildTool({
  name: FILE_READ_TOOL_NAME,
  searchHint: 'read files, images, PDFs, notebooks',  // helps ToolSearch find deferred tools
  strict: true,  // rejects unknown input fields

  async description() { return 'Read a file' },

  // Lazy getter — inputSchema() is not called at module load time
  get inputSchema(): InputSchema { return inputSchema() },

  // Overrides the false default — this tool is safe to run concurrently
  isConcurrencySafe() { return true },
  // Overrides the false default — this tool makes no mutations
  isReadOnly() { return true },

  // checkPermissions is called by the framework BEFORE call() — not inside call()
  async checkPermissions(input, context): Promise {
    return checkReadPermissionForTool(FileReadTool, input, context.getAppState().toolPermissionContext)
  },

  async call({ file_path, limit }, context, _canUseTool?) {
    const content = await readFileInRange(file_path, 1, limit)
    return { data: { type: 'text' as const, file: { filePath: file_path, content } } }
  },
})

Notice isConcurrencySafe and isReadOnly are the only defaultable methods overridden here — everything else (isDestructive, isEnabled, userFacingName, toAutoClassifierInput) is left to buildTool's safe defaults.

Apply it to your code

Before — a new capability added as a plain function (no schema, no factory, no lazy schema):

// No schema, no permission check, no lazy loading
async function summariseAsset(assetId: string, depth: number): Promise {
  const asset = await fetchAsset(assetId)
  return buildSummary(asset, depth)
}

After — same capability as a proper buildTool({...}) with lazySchema, checkPermissions, and { data: result } return:

import { buildTool } from '../../Tool.js'
import { lazySchema } from '../../utils/lazySchema.js'
import { z } from 'zod/v4'

const inputSchema = lazySchema(() =>
  z.strictObject({
    asset_id: z.string().describe('Qualified name or GUID of the asset'),
    depth: z.number().int().min(1).max(3).optional().default(1)
      .describe('How many relationship hops to include'),
  })
)
type InputSchema = ReturnType

export const SummariseAssetTool = buildTool({
  name: SUMMARISE_ASSET_TOOL_NAME,
  async description() { return 'Summarise an Atlan asset and its relationships' },

  get inputSchema(): InputSchema { return inputSchema() },

  // Read-only network call — safe to run concurrently
  isConcurrencySafe: () => true,
  isReadOnly: () => true,

  async checkPermissions(input, context) {
    return checkNetworkPermission(SummariseAssetTool, input, context)
  },

  async call({ asset_id, depth }, context) {
    const asset = await fetchAsset(asset_id, context.getAppState().atlanClient)
    const summary = buildSummary(asset, depth)
    return { data: { summary, assetId: asset_id } }
  },
})

Signals that you need this pattern

  • A new tool is defined as export const X: Tool = { ... } (direct interface implementation — bypasses the factory and forces explicit defaults)
  • A new tool's inputSchema is a bare z.object({...}) at module scope instead of wrapped in lazySchema() — schema is built at import time, hurting startup
  • A new tool implements all 7 defaultable methods explicitly when most are just the default value
  • A new tool uses z.object instead of z.strictObject — unknown input fields will silently pass through validation

Signals that you're over-applying it

  • Don't use buildTool for test helpers or mock tools — it's for production tools registered in tools.ts
  • Don't add outputSchema for tools that return plain strings or simple unstructured data
  • searchHint is only needed for large tools likely to be deferred by ToolSearch; small tools don't need it

Works with

  • tool-definition — the full Tool interface and what each method does
  • permission-system — implementing checkPermissions for tools with side effects
  • hot-paths — why lazySchema() matters for startup performance
  • module-organisation — where to put the new tool file and how to register it

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.