View on GitHub →
← Back to Home

Python: Tool Contracts with ToolClad

Define .clad.toml manifests, validate typed arguments, execute tools safely, and generate MCP schemas with ToolClad.

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.