Operation and Destructive
When to Use
Use this when filling in
operationanddestructiveon#[Tool]. Both are declarations that callers read; Tool API enforces neither.Version: applies to
drupal/tool1.0.0-beta11 (beta; no security advisory coverage). Paths are undermodules/contrib/tool/.
ToolOperation Cases
ToolOperation is a string-backed enum (tool 1.0.0-beta11 src/Tool/ToolOperation.php). Meanings are the enum's own getDescription() text, shortened.
| Case | Value | Use for | isModifying() |
isIdempotent() |
|---|---|---|---|---|
Explain |
explain |
Structure, schema or definitions, not data | FALSE | TRUE |
Read |
read |
Fetching existing data or resources without changing them | FALSE | TRUE |
Transform |
transform |
Deriving a result from input without persisting | FALSE | TRUE |
Trigger |
trigger |
Starting a process: queue worker, cron, webhook | TRUE | FALSE |
Write |
write |
Creating, updating or deleting stored entities or config | TRUE | FALSE |
// tool 1.0.0-beta11 src/Tool/ToolOperation.php
public function isIdempotent(): bool {
return match ($this) {
self::Explain, self::Read, self::Transform => TRUE,
self::Trigger, self::Write => FALSE,
};
}
The module's docs table lists Transform as not idempotent (tool 1.0.0-beta11 docs/developers/creating-a-tool.md). The code says idempotent, citing #3582962. Trust the code.
Decision
| If the tool... | Set... | Why |
|---|---|---|
| Changes stored data | operation: ToolOperation::Write |
Callers treat it as modifying and not idempotent |
| Starts a process but does not store data itself | ToolOperation::Trigger |
Same modifying signal, different intent |
| Deletes or cannot be undone | Add destructive: TRUE |
MCP clients get a destructive hint; callers may confirm |
| Only computes from its input | ToolOperation::Transform |
Marked non-modifying and idempotent |
What Consumes These Values
Nothing in Tool API blocks a Read tool from writing, and nothing prompts for destructive: TRUE. The module's docs say "Callers use it to prompt for confirmation" (tool 1.0.0-beta11 docs/developers/creating-a-tool.md); no caller in Tool API, Tool Belt or the MCP bridge prompts. Consumers found in code:
- Drush
tool:list --operation=filters by operation;tool:infoand JSON output showdestructive(tool 1.0.0-beta11src/Drush/Commands/ToolListCommand.php,src/Drush/Commands/ToolOutputTrait.php). - The MCP bridge maps
operationanddestructiveto MCP tool hints; Tool API Bridge owns the mapping. eca_toolappends "This tool is destructive" to the ECA action description (eca_tool 1.0.0-beta1src/Plugin/Action/ToolDeriver.php).tool_ai_connectorreads neither (tool 1.0.0-beta11modules/tool_ai_connector/src/Plugin/AiFunctionCall/Derivative/ToolPluginDeriver.php).
Common Mistakes
- Labelling a tool
Readbecause it "mostly reads" → an MCP client may then auto-run it without confirmation; label by the worst side effect - Relying on
destructive: TRUEas a safety net → it is metadata only; enforce safety in access and in the tool - Leaving
operationout → the attribute requires it;ToolDefinition::getOperation()falls back toTransformonly for definitions built without the attribute
See Also
- Defining a Tool → the rest of the attribute
- Tool API Bridge → how the hints reach MCP clients
- Reference:
modules/contrib/tool/src/Tool/ToolOperation.php,modules/contrib/tool/src/Tool/ToolDefinition.php