Tool authoring reference

The reference for authoring a custom Tool definition: the fields a Tool needs, how parameters and targets work, the Compiler and V3 Processor contracts, 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 Method instance, 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, cliTool, or courierTool.
processorYesA customProcessor with one inline restricted Python V3 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.

Building from the output backward keeps each piece testable:

  1. Run one successful command or workflow by hand.
  2. Save realistic output, including an empty result and a malformed or failed result where relevant.
  3. Decide which durable Ontology Objects and links the output proves.
  4. Look up their exact schemas, properties, and link names in the Object Reference or Developer MCP.
  5. Define the Tool’s metadata, parameters, targets, generatesTypes, and success criteria.
  6. Validate the definition.
  7. Write and validate the Compiler against representative typed inputs.
  8. Write and validate the Processor against the sample output.
  9. Register the Tool only after its planned execution and resulting graph both 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 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 V3 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. Quote the placeholder in the shell command.
Unresolved placeholderValidation fails.
1class ResolveScriptParameters(MethodToolScriptCompiler):
2 def compile(self, input: MethodToolCompileInput, replacements: ScriptReplacements) -> None:
3 domain = input.get("domain")
4 timeout = input.get("timeout") or 5
5 if not domain:
6 raise CompileException("domain is required")
7 replacements.set("domain", str(domain))
8 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.
1class BuildCliArguments(MethodToolCliCompiler):
2 def compile(self, input: MethodToolCompileInput, arguments: CliArguments) -> None:
3 target = input.get("targetHost")
4 timeout = input.get("timeout") or 30
5 verbose = input.get("verbose") or False
6 if not target:
7 raise CompileException("targetHost is required")
8 arguments.add_argument("--target", str(target))
9 arguments.add_argument("--timeout", str(timeout))
10 if verbose:
11 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.

1class GatherFileCompiler(MethodToolCourierCompiler):
2 def compile(self, input: MethodToolCompileInput, workflows: CourierWorkflows) -> None:
3 file_identifier = input.get("fileIdentifier")
4 if not file_identifier:
5 raise CompileException("fileIdentifier is required")
6 workflow = Workflow()
7 steps = [
8 workflow.actions.download_file(
9 file_identifier=file_identifier,
10 target_path="/tmp/tool-input",
11 signal="downloaded",
12 ),
13 workflow.actions.exfil_file(
14 path="/tmp/tool-input",
15 signal="exfiltrated",
16 base64_contents="file_base64",
17 ),
18 ]
19 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.

1workflow = Workflow()
2action = workflow.actions.custom(action_type_id=9001, action_contents=b"tool-input")
3outcomes = workflow.outcomes.create(generates_signals=["custom_output"])
4step = workflow.steps.create(action=action, outcomes=outcomes)
5workflows.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.

Use the V3 Processor contract for every new Tool: set ontologySdkVersion to ONTOLOGY_SDK_V2, define exactly one MethodToolProcessor subclass, and return generated Ontology Objects in ProcessResult. The enum name selects the newer method-tool SDK V3 contract.

Working in restricted Python

The Processor runs in a restricted sandbox:

  • 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 Object Reference or Developer MCP Ontology definition lookup to confirm constructor fields and link helper names.
1class JsonReportProcessor(MethodToolProcessor):
2 def process(self, output: MethodToolOutput) -> ProcessResult:
3 raw = output.get_raw_output()
4 if not raw:
5 return ProcessResult(success=False, error_message="missing report output")
6 try:
7 data = json.loads(raw)
8 except ValueError as exc:
9 return ProcessResult(success=False, error_message=f"invalid report JSON: {exc}")
10 addresses = [
11 IpAddress(ip_address=item["ip"])
12 for item in data.get("addresses") or []
13 if item.get("ip")
14 ]
15 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:

1ip = IpAddress(ip_address="192.0.2.10")
2host = Host(hostname="WEB01").with_ip_address(ip)
3return 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

Validate in this order before registering. Each step has a corresponding Validate action in the Tool builder and Developer MCP.

  1. Validate the definition. Confirm the metadata, parameter types, target types, tag RIDs, generated types, and success criteria form a valid Tool contract.
  2. Validate the Compiler. Dry-run the planned execution against representative typed parameters and mock target data. Confirm the compiled commands or workflow look exactly right.
  3. Validate the Processor. Run the Processor against representative Signals or payload and inspect the resulting Object graph. Confirm it contains the expected durable Objects and links.
  4. Revalidate after changes. After fixing any failure, rerun the failed validator and then the full definition, Compiler, and Processor sequence.
  5. Create. Register the Tool only after every validator passes and the developer has reviewed the final behavior.

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(MethodToolProcessor):
2 def process(self, output: MethodToolOutput) -> ProcessResult:
3 host_lines = (output.get_raw_output(step="hostname") or "").strip().splitlines()
4 host_name = host_lines[0].strip() if host_lines else None
5 ip_objects = [
6 IpAddress(ip_address=ip)
7 for ip in re.findall(
8 r"(?:\d{1,3}\.){3}\d{1,3}", output.get_raw_output(step="addresses") or ""
9 )
10 ]
11 if not host_name and not ip_objects:
12 return ProcessResult(success=False)
13 if not host_name:
14 return ProcessResult(success=True, new_ontology=ip_objects)
15 host = Host(hostname=host_name)
16 for ip in ip_objects:
17 host = host.with_ip_address(ip)
18 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>>'
1class ReachabilityProcessor(MethodToolProcessor):
2 def process(self, output: MethodToolOutput) -> ProcessResult:
3 raw = output.get_raw_output(step="ping") or ""
4 addresses = [
5 IpAddress(ip_address=match)
6 for match in re.findall(r"REACHABLE target=([^\s]+)", raw)
7 ]
8 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)

1class DnsForwardProcessor(MethodToolProcessor):
2 def process(self, output: MethodToolOutput) -> ProcessResult:
3 data = json.loads(output.get_raw_output() or "{}")
4 addresses = []
5 for lookup in (data.get("result") or {}).get("lookups") or []:
6 ip = lookup.get("ipAddress")
7 if not ip:
8 continue
9 addresses.append(IpAddress(ip_address=ip))
10 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 placeholders are quoted and fully substituted.
  • CLI commandArgs do not include the binary path.
  • Processor uses ontologySdkVersion: ONTOLOGY_SDK_V2 and 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 step-by-step walkthrough of building a Tool in the UI, see Operator-defined Tools. For how Tools fit into the platform, see Tools.