How Tool Plugins Work#
A tool plugin (called an “agentless module” internally) is a Python class that:
- Declares its protocol, operations, and argument schemas as class attributes
- Implements
handle_<operation>methods — one per supported operation - Receives decrypted credentials from the credential store
- Optionally contributes discovered credentials back to the store
The base class auto-discovers commands from class attributes and auto-dispatches execute() to the correct handle_* method. You do not need to override metadata() or execute().
Base Class#
| |
__init_subclass__ scans for Command class attributes and populates _discovered_commands. The metadata() method builds AgentlessMetadata from class attributes and discovered commands automatically.
Step-by-Step: Writing a Tool Plugin#
Step 1: Create the project structure#
| |
Step 2: Write pyproject.toml#
| |
Step 3: Implement AgentlessModuleBase#
Use class attributes for metadata, Command class attributes for operations, and handle_<operation> methods for execution.
| |
The handle_<operation> Contract#
The base class execute() method dispatches to handle_<operation> based on the operation name. Each handler receives the same parameters:
| Parameter | Type | Description |
|---|---|---|
targets | list[AgentlessTarget] | Each has host, optional port, optional credential_id. |
options | dict[str, Any] | Validated per the Command 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. |
Return exactly one AgentlessResult per target — even on failure. Never raise from a handler; catch exceptions per-target and return AgentlessResult(success=False, error=str(exc)).
AgentlessMetadata#
The base class builds AgentlessMetadata automatically from class attributes and discovered Command instances. You only need to inspect it if you have special requirements.
| |
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 (uses the v2 class-attribute pattern):
| |
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 a handler — the manager does not catch exceptions from handle_* methods unless you wrap them. Always use try/except per-target inside each handler.
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 handler blocks the entire tools execution queue.
dependencies mismatch — declare dependencies in both pyproject.toml (for installation) and the class-level dependencies attribute (for auto-install at discovery time).
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.