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 shapes, how to validate and register, and a set of worked examples.

For the guided, UI-level walkthrough of building a Tool, see Operator-defined Tools. This page is the underlying specification.

Operator-defined Tools are in closed alpha and being updated daily. Object Type identifiers and validation endpoints can still change. If you want this feature enabled on your stack, reach out to your Method representative.

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

A submitted Tool is a CreateToolRequest. You do not have to author it as one block of JSON: the builder collects these fields across its tabs. Use this as the checklist of what every Tool needs.

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.
intentYesOne sentence: what the Tool does and why. Method’s AI reads this to decide when to use it.
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 or cliTool.
processorYesUsually customProcessor with inline restricted Python.
generatesTypesYesEvery Ontology Object Type whose factory the Processor uses.
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.

Building from the output backward keeps each piece testable:

  1. Run one successful command by hand.
  2. Save a realistic sample of its stdout or JSON output.
  3. Decide which Ontology Objects that output proves.
  4. List those Object Types in generatesTypes.
  5. Write the Processor and validate it against the sample output.
  6. Write the Compiler and validate the compiled commands.
  7. Validate the whole definition.
  8. Register the Tool once both the compiler output and the resulting graph look right.

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

Parameters required for a Tool to compile are marked with the Required checkbox in the builder.

When multiple Ontology parameters are both marked Required and configured as Targets, compilation succeeds if at least one type is available. For example, if a Tool accepts both FQDNs and URLs as target types, configure them as separate parameters and mark both Required. The Tool can then run whenever either FQDNs or URLs are present, without needing both.


Compilers

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 JackalInstalled paths and populateParameters
cliTool with artifactThe binary is stored as a Method artifact familyArtifact family and version, populateParameters

Script Tool

Script Tools are the most flexible 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 workflow Processor receives stdout 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).

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.compile() returns CommandArguments(env_vars={...}).
Value typeEach substitution must be a plain string.
Replacement behaviorRaw text replacement. Quote the placeholder in the shell command.
Unresolved placeholderValidation fails.
1class PopulateParameters(MethodToolCompiler):
2 def compile(self, input):
3 domain = input.tool_parameters.get("domain")
4 timeout = str(input.tool_parameters.get("timeout", 5))
5 if not domain:
6 return None
7 return MethodToolCompileResult(commands=[
8 CommandArguments(arguments=[], env_vars={"domain": domain, "timeout": timeout})
9 ])
$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, and the resolved string is available to the Compiler as input.target.

1class PopulateParameters(MethodToolCompiler):
2 def compile(self, input):
3 if not input.target:
4 return None
5 return MethodToolCompileResult(commands=[
6 CommandArguments(arguments=[], env_vars={
7 "target": input.target,
8 "timeout": str(input.tool_parameters.get("timeout", 3)),
9 })
10 ])

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 run with input.target as None.
populateParametersRestricted Python deriving from MethodToolCompiler.
toolLocationOne of directUrls, installed, or artifact.
1class PopulateParameters(MethodToolCompiler):
2 def compile(self, input):
3 timeout = input.tool_parameters.get("timeout", 30)
4 verbose = input.tool_parameters.get("verbose", False)
5 args = [
6 Option(flag="--target", value=input.target),
7 Option(flag="--timeout", value=str(timeout)),
8 ]
9 if verbose:
10 args.append(Flag(flag="--verbose"))
11 return MethodToolCompileResult(commands=[CommandArguments(arguments=args)])

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.

Processors

Prefer customProcessor with inline Python. Choose a base class by how your Tool emits output.

Base classInputBest for
MethodToolSignalProcessorOne decoded signal as request.signal_contentCLI Tools emitting one JSON report or blob
MethodToolWorkflowProcessorMap of step name to stdout in request.signalsScript Tools and multi-step workflows

Helpful guide for using restricted Python

The Processor runs in a restricted sandbox:

  • Do not use import. The following are already available: json, re, method_ontology_sdk, ObjectTypeEnum, 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.
  • Every request.object_factories[ObjectTypeEnum.X] you use must have a matching identifier in generatesTypes.
1class JsonReportProcessor(MethodToolSignalProcessor):
2 def process(self, request):
3 data = json.loads(request.signal_content)
4 ip_factory = request.object_factories[ObjectTypeEnum.IP_ADDRESS]
5 success = False
6 for item in data.get("addresses") or []:
7 ip = item.get("ip")
8 if not ip:
9 continue
10 ip_factory.set_param(ip_address=ip)
11 ip_factory.build_object()
12 success = True
13 return MethodToolSignalProcessResult(success=success)

Factory cookbook

Use one factory per Object Type. Factories validate properties and create links.

ObjectPattern
IP addressip_factory.set_param(ip_address="192.0.2.10")
FQDNfqdn_factory.set_param(fqdn="host.example.com")
URLurl_factory.set_param(url_str="https://example.com/path")
PortBuild IP first, then port_factory.set_param(port_number=443, ip_address=ip_obj)
Hosthost_factory.set_param(hostname="HOST1", ip_addresses=[ip_obj])
DNS recordBuild FQDN/IP, then set dns_record_name, dns_record_type, data, links
CredentialLoad CredentialTypeEnum, then set credential_type, value, optional alias

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

Validate in this order before registering. Each step has a corresponding Validate button in the Tool builder.

  1. Validate the Compiler. The builder dry-runs your commands against mock target data. Confirm the compiled commands look exactly right.
  2. Validate the Processor. The builder runs your Processor against sample output and shows the resulting Object graph. Confirm it contains the Objects and links you expect.
  3. Validate the Metadata. The builder checks the definition as a whole. Confirm all checks pass.
  4. Create. Click Create to register the Tool. It is now available across the platform.

To publish a new version of an existing Tool, open it from the Tools app and save changes as a new version. Previous versions remain intact.


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
1class HostInventoryProcessor(MethodToolWorkflowProcessor):
2 def process(self, request):
3 host_factory = request.object_factories[ObjectTypeEnum.HOST]
4 ip_factory = request.object_factories[ObjectTypeEnum.IP_ADDRESS]
5 host_lines = (request.signals.get("hostname") or "").strip().splitlines()
6 host_name = host_lines[0].strip() if host_lines else None
7 ip_objects = []
8 for ip in re.findall(r"(?:\d{1,3}\.){3}\d{1,3}", request.signals.get("addresses") or ""):
9 ip_factory.set_param(ip_address=ip)
10 ip_objects.append(ip_factory.build_object().obj)
11 if not host_name and not ip_objects:
12 return MethodToolWorkflowProcessResult(success=False)
13 host_factory.set_param(hostname=host_name, ip_addresses=ip_objects or None)
14 host_factory.build_object()
15 return MethodToolWorkflowProcessResult(success=True)

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>>'
1class ReachabilityProcessor(MethodToolWorkflowProcessor):
2 def process(self, request):
3 ip_factory = request.object_factories[ObjectTypeEnum.IP_ADDRESS]
4 success = False
5 for output in request.signals.values():
6 for match in re.findall(r"REACHABLE target=([^\s]+)", output or ""):
7 ip_factory.set_param(ip_address=match)
8 ip_factory.build_object()
9 success = True
10 return MethodToolWorkflowProcessResult(success=success)

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)

1class DnsForwardProcessor(MethodToolSignalProcessor):
2 def process(self, request):
3 data = json.loads(request.signal_content)
4 ip_factory = request.object_factories[ObjectTypeEnum.IP_ADDRESS]
5 success = False
6 for lookup in (data.get("result") or {}).get("lookups") or []:
7 ip = lookup.get("ipAddress")
8 if not ip:
9 continue
10 ip_factory.set_param(ip_address=ip)
11 ip_factory.build_object()
12 success = True
13 return MethodToolSignalProcessResult(success=success)

Common pitfalls

SymptomLikely causeFix
Validation says a factory key is missinggeneratesTypes omits an Object TypeAdd every Object Type used in request.object_factories.
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 dunder accessRemove imports, file IO, subprocesses, and introspection.

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 placeholders are quoted and fully substituted.
  • CLI commandArgs do not include the binary path.
  • Processor defines exactly one subclass.
  • generatesTypes includes every factory the Processor uses.
  • 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 step-by-step walkthrough of building a Tool in the UI, see Operator-defined Tools. For how Tools fit into the platform, see Tools.