Creating a tool¶
A tool is a plugin. Put the class under src/Plugin/tool/Tool/ in your module, give
it the #[Tool] attribute, and extend ToolBase.
A minimal tool¶
<?php
declare(strict_types=1);
namespace Drupal\my_module\Plugin\tool\Tool;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\tool\Attribute\Tool;
use Drupal\tool\ExecutableResult;
use Drupal\tool\Tool\ToolBase;
use Drupal\tool\Tool\ToolOperation;
use Drupal\tool\TypedData\InputDefinition;
#[Tool(
id: 'greeting_tool',
label: new TranslatableMarkup('Greeting Tool'),
description: new TranslatableMarkup('Generates a greeting message.'),
operation: ToolOperation::Transform,
input_definitions: [
'name' => new InputDefinition(
data_type: 'string',
label: new TranslatableMarkup('Name'),
description: new TranslatableMarkup('The name to greet.'),
),
'greeting' => new InputDefinition(
data_type: 'string',
label: new TranslatableMarkup('Greeting'),
description: new TranslatableMarkup('The greeting to use.'),
required: FALSE,
),
],
output_definitions: [
'message' => new ContextDefinition(
data_type: 'string',
label: new TranslatableMarkup('Message'),
description: new TranslatableMarkup('The generated greeting message.'),
),
],
)]
final class GreetingTool extends ToolBase {
protected function doExecute(array $values): ExecutableResult {
$greeting = $values['greeting'] ?? 'Hello';
return ExecutableResult::success(
new TranslatableMarkup('Greeting generated.'),
['message' => "$greeting, {$values['name']}!"],
);
}
protected function checkAccess(array $values, AccountInterface $account, bool $return_as_object = FALSE): bool|AccessResultInterface {
return $return_as_object ? AccessResult::allowed() : TRUE;
}
}
The attribute¶
#[Tool] accepts:
| Argument | Required | Purpose |
|---|---|---|
id |
yes | Plugin ID. Must equal the group or be prefixed with group:. For a plain tool, keep the ID and group equal (for example send_email). For a derived tool, prefix it (for example entity:node). |
label |
yes | Human readable name. |
description |
yes | What the tool does. Callers and models rely on this. |
operation |
yes | A ToolOperation case. See below. |
destructive |
no | Whether the tool is destructive. Callers use it to prompt for confirmation. |
input_definitions |
no | Inputs the tool accepts. See Input and output definitions. |
input_definition_refiners |
no | Config for dynamically refining inputs. Requires the class to implement InputDefinitionRefinerInterface. |
output_definitions |
no | Outputs the tool returns. |
deriver |
no | A deriver class, to produce many tools from one definition. |
forms |
no | Form class overrides keyed by operation. Defaults are provided for configure and execute. |
Operations¶
The operation describes the nature of the tool. It drives whether the tool modifies
state and whether it is idempotent, which callers use to decide how to treat it.
| Operation | Modifies state | Idempotent | Use for |
|---|---|---|---|
Explain |
no | yes | Returning structure or schema, not data. |
Read |
no | yes | Fetching existing data without changing it. |
Transform |
no | no | Deriving a result from input, without persisting. |
Trigger |
yes | no | Starting a process, queue, cron, or webhook. |
Write |
yes | no | Creating, updating, or deleting stored data. |
Implementing the tool¶
ToolBase requires two methods:
doExecute(array $values): ExecutableResultruns the tool. It receives the input values keyed by input name and returns a result. UseExecutableResult::success($message, $outputValues)on success andExecutableResult::failure($message)on failure. Output values are keyed by output name and must match your declaredoutput_definitions. The base class catches exceptions fromdoExecute()and turns them into a failure result, so you do not have to wrap everything in try/catch.checkAccess(array $values, AccountInterface $account, bool $return_as_object): bool|AccessResultInterfacedecides access. It is abstract, so every tool must implement it and access is always an explicit opt in.
You can also override checkRequirements(): void to assert static prerequisites,
such as an API key being configured. Throw a
Drupal\tool\Exception\RequirementsException with a message describing what is
missing. This is for static configuration, not runtime state like network
availability.
Discovery¶
Tools are discovered by the plugin.manager.tool manager from the
Plugin/tool/Tool namespace. After adding or changing a tool, rebuild caches:
drush cr
Then confirm it is registered:
drush tool:info greeting_tool