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 PyPI:
pip 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.
from toolclad.manifest import load_manifest
from toolclad.validator import validate_arg
manifest = load_manifest("tools/whois_lookup.clad.toml")
print(f"Tool: {manifest.tool.name} v{manifest.tool.version}")
print(f"Binary: {manifest.tool.binary}")
print(f"Risk tier: {manifest.tool.risk_tier}")
print(f"Timeout: {manifest.tool.timeout_seconds}s")
# Inspect arguments
for arg in manifest.args_sorted:
print(f" {arg.name}: type={arg.type}, required={arg.required}")
# Validate individual argument values
cleaned = validate_arg(manifest.args["target"], "example.com")
print(f"Validated: {cleaned}")
# This will raise ValidationError:
# validate_arg(manifest.args["target"], "example.com; rm -rf /")
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 json
from toolclad.manifest import load_manifest
from toolclad.executor import execute
manifest = load_manifest("tools/whois_lookup.clad.toml")
args = {"target": "example.com"}
envelope = execute(manifest, args)
print(json.dumps(envelope, indent=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 json
from toolclad.manifest import load_manifest
manifest = load_manifest("tools/whois_lookup.clad.toml")
# Build MCP schema from manifest args
properties = {}
required = []
for arg in manifest.args_sorted:
prop = {"type": "string", "description": arg.description}
if arg.type in ("integer", "port"):
prop["type"] = "integer"
elif arg.type == "boolean":
prop["type"] = "boolean"
if arg.allowed:
prop["enum"] = arg.allowed
properties[arg.name] = prop
if arg.required:
required.append(arg.name)
mcp_schema = {
"name": manifest.tool.name,
"description": manifest.tool.description,
"inputSchema": {
"type": "object",
"properties": properties,
"required": required,
},
}
print(json.dumps(mcp_schema, indent=2))
Step 6: CLI Usage
The toolclad CLI provides four commands for working with manifests:
Validate a manifest
toolclad validate whois_lookup.clad.toml
Dry-run with argument validation
toolclad test whois_lookup.clad.toml --arg target=example.com
Execute a tool
toolclad run whois_lookup.clad.toml --arg target=example.com
Generate MCP schema
toolclad schema whois_lookup.clad.toml
All commands accept -a key=value as a shorthand for --arg key=value. Multiple arguments can be passed by repeating the flag.