Skip to navigation

Tool authoring reference

The reference for authoring a custom Tool definition: the fields a Tool needs, how parameters and targets work, the Compiler and Processor contracts, how to validate and register, and a set of worked examples.

Follow Create and publish a Tool for the product workflow. This page describes the definition fields and implementation interfaces.

Authoring capabilities and Object schemas depend on your Method instance. Use its Object schemas and connected authoring guidance to verify the available contracts. Contact your Method representative if Tool authoring is not enabled.

Method open-sources its Tools on GitHub. You can see exactly how Method’s own Tools are built, and if you build something useful, contribute it back.


The Tool definition

The Tool definition describes the metadata, inputs, execution, and output processing for the capability. In the code editor, tool.json holds the definition and can reference separate Compiler and Processor files. The authoring tools resolve those files when submitting the definition to Method.

FieldRequiredWhat to put there
nameYesLowercase alphanumeric name, such as hostinventory. Method strips any non [a-z0-9] characters.
displayNameYesHuman-readable name.
versionYesSemantic version, usually 0.0.1 for a first Tool.
familyYesMITRE tactic family, such as DISCOVERY or CREDENTIAL_ACCESS.
intentYesPurpose, results, effects, required access, cleanup, and failure behavior. This helps operators and Agents assess the capability.
riskLevelYesLOW, MEDIUM, or HIGH. Use HIGH for changing systems, authenticating, exploiting, exfiltrating, or persistence.
techniquesYesList of MITRE ATT&CK technique IDs. An empty list is allowed.
parametersYesDirect and Ontology input definitions. An empty list is allowed.
requirementsYesRequired parameter groups. Use {"oneOfParameters": []} when nothing is required.
compilerYesExactly one compiler: scriptTool, cliTool, or courierTool.
processorYesA customProcessor with one inline restricted Python Processor class.
generatesTypesYesEvery Ontology Object Type the Processor creates.
successCriteriaYesAn Object filter that should match an Object proving the run succeeded.
examplesYesUser-facing examples. An empty list is allowed, but examples improve recommendation.
timeoutInSecondsOptionalExecution timeout. Use a realistic value, such as 300.

Families

A Tool belongs to exactly one MITRE tactic family:

RECONNAISSANCE, RESOURCE_DEVELOPMENT, INITIAL_ACCESS, EXECUTION, PERSISTENCE, PRIVILEGE_ESCALATION, DEFENSE_EVASION, CREDENTIAL_ACCESS, DISCOVERY, LATERAL_MOVEMENT, COLLECTION, COMMAND_AND_CONTROL, EXFILTRATION, IMPACT.

Authoring workflow

The Tool’s output determines its generated Object Types and success criteria. Its inputs determine the Compiler’s parameters and targets. The Processor connects the two by interpreting execution output as Objects and relationships.

For the build sequence, follow Create and publish a Tool.


Parameters

A Tool takes two kinds of input.

CategoryUse whenExamples
DirectThe user types a value or chooses a simple settingdomain, timeout, verbose, ports, username
OntologyThe user selects existing Method Objects, which Method resolves to stringsipAddresses, fqdns, urls, smbApplications

Direct parameters

Direct typePython value in input.tool_parametersGood use
STRINGstrDomain, username, path, mode
STRING_ARRAYlist[str]Resolver list, wordlist entries
BOOLEANboolverbose, includeDisabled
INTEGERintTimeout, retry count, port
FLOATfloatDelay, threshold

Ontology parameters

An Ontology parameter points at an Object Type by its identifier. Common ones:

ObjectIdentifier
IP addressri.ontology..objecttype.ipaddress
FQDNri.ontology..objecttype.fqdn
URLri.ontology..objecttype.url
Portri.ontology..objecttype.port
Hostri.ontology..objecttype.host
Network applicationri.ontology..objecttype.networkapplication
SMB applicationri.ontology..objecttype.smbapplication
SMTP applicationri.ontology..objecttype.smtpapplication
DNS recordri.ontology..objecttype.dnsrecord
Credentialri.ontology..objecttype.credential
Local user accountri.ontology..objecttype.localuseraccount
Active Directory accountri.ontology..objecttype.activedirectoryaccount
CVEri.ontology..objecttype.cve
Web applicationri.ontology..objecttype.webapplication
Certificateri.ontology..objecttype.certificate

Requirements

The definition’s requirements field describes which parameter groups the Compiler requires.

Keep requirements aligned with the parameters and targets used by the Compiler. Validate representative input combinations, including missing required values, before publishing.


Compilers

A Compiler deterministically turns typed Tool inputs and selected Ontology Objects into the execution plan a Jackal receives. It is not a place for an AI to improvise commands. Keep parameters minimal, validate every value that changes behavior, and reject unsupported combinations with a clear CompileException before the Tool runs.

A Tool uses exactly one compiler type.

CompilerUse whenAuthor writes
scriptToolYou can express the Tool as shell commandsOS-specific command strings, optional substitution
cliTool with directUrlsYou have a downloadable binary per OS and archFixed args, download URLs, populateParameters
cliTool with installedThe executable already exists on the Jackal HostInstalled paths and populateParameters
cliTool with ArtifactThe binary is stored as a Method Artifact familyArtifact family and version, populateParameters
courierToolThe Tool needs direct workflow and action controlA Courier workflow in populateParameters

Script Tool

Script Tools are the simplest shape and need no packaged binary, only named shell commands. They run on a deployed Jackal.

FieldMeaning
supportedArchitecturesAMD_64, ARM_64.
targetsEmpty for no Ontology target. Non-empty for one run per target.
shellCompilersList of OS-specific command sets.
Command nameStable Signal key. The Processor reads output under this name.
Command stringThe shell command to run.
populateParametersOptional. Required for <<placeholder>> substitution.

OS targeting supports Linux (any distribution or a specific one such as Ubuntu), macOS, and Windows (desktop or Server).

Prefer generic Linux with distribution: null unless the Tool genuinely depends on a narrower distribution. Explicitly list every supported architecture. Most portable Linux Tools support both AMD_64 and ARM_64.

Static script tools

A static Script Tool runs fixed commands with no parameter substitution. Each command has a name and a shell string. The name is how the Processor identifies that command’s stdout output.

Parameterized script tools

To use direct parameters in shell commands, add populateParameters and reference values as <<name>> placeholders.

PieceRule
Placeholder syntaxUse literal <<name>> in commands.
Source of valuespopulateParameters defines one MethodToolScriptCompiler subclass.
Value typeEach substitution must be a plain string.
Replacement behaviorRaw text replacement. Validate values and quote them for the target shell; surrounding quotes alone do not escape untrusted input.
Unresolved placeholderValidation fails.
class ResolveScriptParameters(MethodToolScriptCompiler):
def compile(self, input: MethodToolCompileInput, replacements: ScriptReplacements) -> None:
domain = input.get("domain")
timeout = input.get("timeout") or 5
if not domain or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9.-]*", str(domain)):
raise CompileException("domain must contain only hostname characters")
if not isinstance(timeout, int) or isinstance(timeout, bool) or timeout < 1 or timeout > 60:
raise CompileException("timeout must be an integer from 1 to 60")
replacements.set("domain", str(domain))
replacements.set("timeout", str(timeout))
timeout '<<timeout>>' getent ahosts '<<domain>>' || true

Target-aware script tools

Targets are selected from configured Ontology Parameters. When a target is set, the Tool runs once per resolved Object. Use the target access pattern for the compiler branch you selected, and validate target assumptions before adding them to a command. Use the Compiler validator with representative target Objects to inspect the final command for every supported platform and architecture.

CLI Tool

Use cliTool when execution is a binary plus arguments. Method resolves the binary, then appends fixed commandArgs and the dynamic arguments from populateParameters.

FieldMeaning
supportedPlatformsOS list, same shape as Script Tools.
supportedArchitecturesAMD_64, ARM_64.
commandArgsFixed tokens after the executable. Do not include the executable path.
targetsOntology target definitions. Empty means one untargeted planned execution.
populateParametersRestricted Python deriving from MethodToolCliCompiler.
toolLocationOne of directUrls, installed, or artifact.
class BuildCliArguments(MethodToolCliCompiler):
def compile(self, input: MethodToolCompileInput, arguments: CliArguments) -> None:
target = input.get("targetHost")
timeout = input.get("timeout") or 30
verbose = input.get("verbose") or False
if not target:
raise CompileException("targetHost is required")
arguments.add_argument("--target", str(target))
arguments.add_argument("--timeout", str(timeout))
if verbose:
arguments.add_argument("--verbose", "true")

Tool locations

LocationUse when
Direct URLsYou have a downloadable binary hosted at a URL.
InstalledThe binary is already present on the Jackal host.
ArtifactThe binary is stored in Method’s Artifact service.

Courier Tool

Use courierTool when the Tool needs direct control over a Courier workflow, such as composed actions, explicit outcomes, file transfer, cleanup, or a Jackal-specific action. Prefer Script or CLI when their higher-level primitives express the capability cleanly. Courier is more flexible, but it exposes more execution details and failure modes.

Define one MethodToolCourierCompiler subclass in populateParameters. Its compile method adds one or more workflows to the injected CourierWorkflows collection. Do not add imports: Method injects Workflow, CourierWorkflows, MethodToolCompileInput, and CompileException into the sandbox.

class GatherFileCompiler(MethodToolCourierCompiler):
def compile(self, input: MethodToolCompileInput, workflows: CourierWorkflows) -> None:
file_identifier = input.get("fileIdentifier")
if not file_identifier:
raise CompileException("fileIdentifier is required")
workflow = Workflow()
steps = [
workflow.actions.download_file(
file_identifier=file_identifier,
target_path="/tmp/tool-input",
signal="downloaded",
),
workflow.actions.exfil_file(
path="/tmp/tool-input",
signal="exfiltrated",
base64_contents="file_base64",
),
]
workflows.add_workflow(workflow.build(steps=steps))

Each output that a Processor needs must be a named Signal. First-class actions declare the Signal when you pass signal="name". For custom actions, explicitly declare the Signal in the enclosing step’s outcomes. Otherwise the action can run successfully but the Processor cannot receive its output.

workflow = Workflow()
action = workflow.actions.custom(action_type_id=9001, action_contents=b"tool-input")
outcomes = workflow.outcomes.create(generates_signals=["custom_output"])
step = workflow.steps.create(action=action, outcomes=outcomes)
workflows.add_workflow(workflow.build(steps=[step]))

Processors

A Processor turns raw Signals into durable, reusable Ontology knowledge. It is not an execution log. Model what the Tool discovered, such as a host, service, account, credential, vulnerability, configuration, or finding. Keep commands, wrappers, timestamps, exit codes, and raw diagnostics in Signals, where Method retains them for auditability.

Define exactly one MethodToolProcessor subclass and return generated Ontology Objects in ProcessResult.

Working in restricted Python

Inline Processor code runs in a restricted sandbox. Repository source files can include SDK imports supplied by starter templates; follow the authoring environment’s source-loading and validation guidance for those files. For code passed directly to the restricted runtime:

  • Do not use import. Method injects json, re, method_ontology_sdk, generated Ontology constructors, and the SDK base and result classes.
  • Define exactly one Processor subclass.
  • Do not access private or dunder attributes such as __dict__.
  • Do not perform file, network, subprocess, eval, dynamic import, or package installation.
  • Do not define helpers whose names begin with _.
  • Every Object Type your Processor creates must appear in generatesTypes.
  • Use the connected Method development tools to look up Object schemas, constructor fields, and link helper names.
class JsonReportProcessor(MethodToolProcessor):
def process(self, output: MethodToolOutput) -> ProcessResult:
raw = output.get_raw_output()
if not raw:
return ProcessResult(success=False, error_message="missing report output")
try:
data = json.loads(raw)
except ValueError as exc:
return ProcessResult(success=False, error_message=f"invalid report JSON: {exc}")
addresses = [
IpAddress(ip_address=item["ip"])
for item in data.get("addresses") or []
if item.get("ip")
]
return ProcessResult(success=bool(addresses), new_ontology=addresses)

When the Tool emits exactly one output, call output.get_raw_output(). For multiple named Signals or workflow steps, call output.get_raw_output(step="signal_name"). Use generated constructors and link helpers to create Objects and their relationships:

ip = IpAddress(ip_address="192.0.2.10")
host = Host(hostname="WEB01").with_ip_address(ip)
return ProcessResult(success=True, new_ontology=[ip, host])

Handle output according to the Tool’s semantics. Empty output can be a valid result for an enumeration Tool. Malformed output should fail or warn intentionally. Never silently create placeholder Objects from an incomplete report.


Success criteria

successCriteria is an Object search filter that should match the Object that best proves the Tool worked.

Tool kindGood success Object
DNS lookupipaddress or dnsrecord
Port discoveryport
Service enumerationsmtpapplication, smbapplication, or networkapplication
Host inventoryhost
Credential capturecredential
Web discoveryurl, webapplication, or webendpoint

Keep the Processor’s success=True aligned with the success criteria you set. A run that reports success but creates no matching Object makes the UI and recommendation logic inconsistent.


Validation and registration

The authoring tools validate three parts of a Tool:

ValidationWhat it checksWhat to inspect
DefinitionMetadata, parameters, target types, tags, generated types, and success criteriaA consistent definition of the Tool’s behavior
CompilerThe execution plan for representative inputs and mock targetsCommands, arguments, platform selection, and workflow actions
ProcessorThe interpretation of sample Signals or payloadExpected Objects, relationships, and failure behavior

Changes to any of these parts can affect the others. The guides describe the full validation sequence to repeat before publication.

Published versions are immutable. Saving a draft, editing source, committing, pushing, and validating are separate from publishing a runnable version. The Method extension’s Publish action saves and commits pending changes, pushes the release, and publishes the version. See Create and publish a Tool for the procedure.

Validation checks a definition, a compiled plan, and sample output. It does not execute the Tool in a target environment. Test the published version separately to verify its real execution and resulting Objects.


Examples

Script Tool, no target: host inventory

The Jackal host itself is the target, so the Tool needs no Ontology input. A good first Tool.

OS: Linux, any distribution

Commands:

StepNameCommand
1hostnamehostname
2addressesip -o -4 addr show | awk '{print $4}' || true
class HostInventoryProcessor(MethodToolProcessor):
def process(self, output: MethodToolOutput) -> ProcessResult:
host_lines = (output.get_raw_output(step="hostname") or "").strip().splitlines()
host_name = host_lines[0].strip() if host_lines else None
ip_objects = [
IpAddress(ip_address=ip)
for ip in re.findall(
r"(?:\d{1,3}\.){3}\d{1,3}", output.get_raw_output(step="addresses") or ""
)
]
if not host_name and not ip_objects:
return ProcessResult(success=False)
if not host_name:
return ProcessResult(success=True, new_ontology=ip_objects)
host = Host(hostname=host_name)
for ip in ip_objects:
host = host.with_ip_address(ip)
return ProcessResult(success=True, new_ontology=[*ip_objects, host])

Script Tool, IP targets: reachability check

The user selects IP Address Objects, and the script runs once per target.

OS: Linux, any distribution Target: IP Address (one run per selected Object)

Commands:

StepNameCommand
1pingtimeout '<<timeout>>' ping -c 1 '<<target>>' >/dev/null 2>&1 && echo 'REACHABLE target=<<target>>' || echo 'UNREACHABLE target=<<target>>'
class ReachabilityProcessor(MethodToolProcessor):
def process(self, output: MethodToolOutput) -> ProcessResult:
raw = output.get_raw_output(step="ping") or ""
addresses = [
IpAddress(ip_address=match)
for match in re.findall(r"(?m)^REACHABLE target=([^\s]+)$", raw)
]
return ProcessResult(success=bool(addresses), new_ontology=addresses)

CLI Tool, direct URLs: JSON report

The binary downloads per OS and architecture and emits one JSON report.

Location: Direct URLs (Linux x86_64 and arm64) Fixed arguments: discover dns forward Target: FQDN (one run per selected Object)

class DnsForwardProcessor(MethodToolProcessor):
def process(self, output: MethodToolOutput) -> ProcessResult:
data = json.loads(output.get_raw_output() or "{}")
addresses = []
for lookup in (data.get("result") or {}).get("lookups") or []:
ip = lookup.get("ipAddress")
if not ip:
continue
addresses.append(IpAddress(ip_address=ip))
return ProcessResult(success=bool(addresses), new_ontology=addresses)

Common pitfalls

SymptomLikely causeFix
Validation says a generated type is missinggeneratesTypes omits an Object TypeAdd every Object Type the Processor creates.
Compiler says mockTarget is requiredTool has targets but validation omitted oneSupply a realistic target string.
Command has unresolved placeholdersenv_vars key does not match <<placeholder>>Match names exactly and return plain strings.
Processor returns success but UI shows nothingThe success-criteria Object was not createdAlign successCriteria with the Processor output.
Tool does not appear for selected ObjectsTarget parameter type cannot resolve to a stringUse an Object Type that resolves, such as IP Address or FQDN.
Python compiles locally but fails validationSandbox blocks imports or private-style helpersRemove imports, file IO, subprocesses, introspection, and names beginning with _.

Final review checklist

  • Tool name is lowercase alphanumeric and versioned.
  • Risk level accurately reflects expected operational impact.
  • Required parameters match the Compiler’s assumptions.
  • Every Ontology target parameter appears in Compiler targets.
  • Script substitution values are validated, quoted for the target shell, and fully substituted.
  • CLI commandArgs do not include the binary path.
  • Processor defines exactly one MethodToolProcessor subclass.
  • generatesTypes includes every Object Type the Processor creates.
  • Success criteria matches a created Object Type.
  • A valid sample output and an empty or negative output were both tested.
  • Compiler validation passed for every OS and architecture you support.

For the product walkthrough, see Create and publish a Tool. For how Tools fit into the platform, see Tools.