The Problem: Unconstrained Tool Execution
Agentic runtimes let LLMs invoke CLI tools, but without constraints the model can generate arbitrary shell commands. Deny-list approaches are fragile — one missed metacharacter and you have command injection.
ToolClad inverts the model: instead of blocking dangerous commands, it constrains the LLM to fill typed parameters validated against a declarative manifest (allow-list). The dangerous action cannot be expressed because the interface doesn't permit it.
- Typed parameters: string, integer, port, enum, scope_target, url, path, ip_address, cidr, boolean
- Shell metacharacter rejection by default
- Command template rendering with value mappings
- Evidence envelopes with execution metadata
- MCP schema generation from manifests
- Full CLI for validation, testing, and execution
Step 1: Install ToolClad
Install from npm:
npm install toolclad
Step 2: Create a Manifest
A .clad.toml manifest defines everything about a tool: its binary, typed arguments, command template, output format, and risk tier. Save this as whois_lookup.clad.toml:
[tool]
name = "whois_lookup"
version = "1.0.0"
binary = "whois"
description = "WHOIS domain/IP registration lookup"
timeout_seconds = 30
risk_tier = "low"
[tool.cedar]
resource = "PenTest::ScanTarget"
action = "execute_tool"
[args.target]
position = 1
required = true
type = "scope_target"
description = "Domain name or IP address to query"
[command]
template = "whois {target}"
[output]
format = "text"
envelope = true
[output.schema]
type = "object"
[output.schema.properties.raw_output]
type = "string"
description = "Raw WHOIS registration data"
Step 3: Load and Validate Programmatically
Load a manifest and inspect its contents. ToolClad validates the manifest structure on load.
import { loadManifest } from "toolclad";
const manifest = loadManifest("tools/whois_lookup.clad.toml");
console.log(`Tool: ${manifest.tool.name} v${manifest.tool.version}`);
console.log(`Binary: ${manifest.tool.binary}`);
console.log(`Risk tier: ${manifest.tool.risk_tier}`);
console.log(`Timeout: ${manifest.tool.timeout_seconds}s`);
// Inspect arguments
for (const [name, arg] of Object.entries(manifest.args)) {
console.log(` ${name}: type=${arg.type}, required=${arg.required}`);
}
Step 4: Execute a Tool
Execute a tool by passing argument values. ToolClad validates every argument, renders the command template, runs the binary with the configured timeout, and wraps the result in an evidence envelope.
import { loadManifest, execute } from "toolclad";
const manifest = loadManifest("tools/whois_lookup.clad.toml");
const args = { target: "example.com" };
const envelope = execute(manifest, args);
console.log(JSON.stringify(envelope, null, 2));
The executor constructs the command whois example.com from the template, runs it with a 30-second timeout, and returns a structured evidence envelope with status, timing, and output hash.
Step 5: Generate MCP Schema
Generate a Model Context Protocol-compatible JSON schema directly from a manifest. This bridges ToolClad manifests to any MCP-compatible agent runtime.
import { loadManifest, generateMcpSchema } from "toolclad";
const manifest = loadManifest("tools/whois_lookup.clad.toml");
const schema = generateMcpSchema(manifest);
console.log(JSON.stringify(schema, null, 2));
The generated schema includes inputSchema with typed properties and required fields, plus an outputSchema that wraps your declared results in the evidence envelope format when envelope = true.
Step 6: CLI Usage
The toolclad CLI provides four commands for working with manifests:
Validate a manifest
npx toolclad validate whois_lookup.clad.toml
Dry-run with argument validation
npx toolclad test whois_lookup.clad.toml --arg target=example.com
Execute a tool
npx toolclad run whois_lookup.clad.toml --arg target=example.com
Generate MCP schema
npx toolclad schema whois_lookup.clad.toml
Multiple arguments can be passed by repeating the --arg flag: --arg target=10.0.0.1 --arg scan_type=service.