How Tool Plugins Work#
A tool plugin (called an “agentless module” internally) is a Python class that:
- Declares its protocol, supported operations, and option schema in
metadata() - Implements
execute()to run against a list of targets and return per-target results - Receives decrypted credentials from the credential store
- Optionally contributes discovered credentials back to the store
The server calls execute() with fully-resolved credentials and proxy config. Your module handles all networking directly.
Base Class#
| |
OptionSchema#
Both connection_params and options_schema use OptionSchema from module_base:
| |
Step-by-Step: Writing a Tool Plugin#
Step 1: Create the project structure#
| |
Step 2: Write pyproject.toml#
| |
Step 3: Implement AgentlessModuleBase#
| |
The execute() Contract#
Parameters#
| Parameter | Type | Description |
|---|---|---|
operation | str | One of the strings in metadata().operations. Validated before calling. |
targets | list[AgentlessTarget] | Each has host, optional port, optional credential_id. |
options | dict[str, Any] | Validated per options_schema. Required options are already checked. |
credentials | dict[str, dict[str, str]] | None | Maps credential_id → {"username", "secret", "cred_type", "domain", ...}. Secrets are decrypted by the manager before this call. |
proxy | dict[str, Any] | None | Proxy configuration (see below). |
Return value#
Return exactly one AgentlessResult per target — even on failure. The manager expects len(results) == len(targets). Never raise from execute() itself; catch exceptions per-target.
| |
Credential Integration#
Reading Credentials#
When a target has a credential_id, the manager decrypts the credential and passes it in credentials:
| |
Contributing Credentials#
Return ExtractedCredential objects in results to populate the credential store:
| |
cred_type values: plaintext, hash, ticket, token, ssh_key, api_key, certificate.
Proxy Support#
When a proxy is configured, the proxy dict contains:
| |
Route your connections through the proxy using PySocks:
| |
Make proxy support optional — declare PySocks as an optional dependency:
| |
Reference Implementation: SSH Tool#
Source: tools/ssh/src/tantoc2_tool_ssh/ssh_command.py
| |
Key points from the SSH reference:
| |
The _load_private_key method tries RSA, Ed25519, ECDSA, and DSS key types:
| |
Testing#
Unit Tests#
| |
Integration Testing#
| |
Deployment#
Method 1: Standalone Package (Recommended)#
| |
Method 2: File Drop#
| |
Common Pitfalls#
Raising from execute() — the manager does not catch exceptions from execute(). Always wrap per-target logic in a try/except and return AgentlessResult(success=False, error=str(exc)).
Returning wrong number of results — return exactly one result per target. Missing results cause the manager to skip auto-credential extraction for those targets.
Blocking forever — set socket timeouts. A hung execute() blocks the entire tools execution queue.
dependencies mismatch — declare dependencies in both pyproject.toml (for installation) and AgentlessMetadata.dependencies (for auto-install at discovery time). They don’t need to be identical — metadata dependencies are installed if the module is discovered via file-drop without pip.
Accessing secret before checking credentials — credentials is None when no credential IDs are on any target. Always check before subscripting.
See Plugin Packaging for the full packaging reference.