| Know what Tool API is and why it is not core Actions |
What Tool API Is |
Use a Tool API plugin when one operation must be callable from Drush, PHP, AI function calling, ECA and MCP. A tool extends ToolBase with the #[Tool] attribute. Gotcha: the module is beta and ships no tools of its own. |
| Tell a Tool API tool from FunctionCall, MCP and ECA tools |
Tool API vs FunctionCall, MCP and ECA Tools |
The word tool names five Drupal mechanisms. A Tool API tool uses the #[Tool] attribute from the tool module and extends ToolBase. Gotcha: mcp_server ships its own #[Tool] attribute, and ECA 3.1 removed eca_base.tool. |
| Install the module and pick submodules |
Installation and Submodules |
Require drupal/tool pinned to the beta, enable the base module, then only the submodules whose callers you need. Gotcha: the two permissions gate only Tool Explorer; each tool's own access rule gates execution. |
Write a tool plugin and fill in #[Tool] |
Defining a Tool |
A tool lives in src/Plugin/tool/Tool, carries #[Tool] and extends ToolBase; inject services by overriding create(). Gotcha: one tool with no permission and no checkAccess() override breaks discovery for every tool. |
Pick the right operation and decide on destructive |
Operation and Destructive |
Set operation by the tool's worst side effect: Write or Trigger for anything that modifies state, plus destructive: TRUE for deletes. Gotcha: both are metadata; Tool API enforces neither, so enforce safety in access and code. |
| Declare inputs: types, required, constraints, lists, maps |
Input Definitions |
Declare inputs with the typed-data definition classes; they drive validation, forms and the JSON Schema callers see. Gotcha: required defaults to TRUE, and an empty list or map still passes required; add NotBlank or Count. |
| Declare outputs and know what reaches the caller |
Output Definitions |
Declare every returned key with an output definition class. Gotchas: required defaults to TRUE, so a missing output turns success into a Runtime failure, and undeclared result keys still reach Drush, AI and MCP callers raw. |
| Take or return an entity |
Entity Inputs and Handles |
An entity input receives the loaded entity; AI and MCP invokers with EntitiesAsHandles pass handle: strings instead. Gotcha: Drush cannot pass entities, so a tool for scripts should take an entity type and ID. |
Write doExecute() and return a result |
doExecute and ExecutableResult |
doExecute() receives validated values and returns ExecutableResult::success() or failure() with a FailureCategory. Gotcha: failure() defaults to Input, which tells a model to retry; never return exception messages to callers. |
| Control who may run a tool |
Access Control |
Declare permission for account-level rules and override checkAccess() for rules on input values; access() runs permission, then validation, then checkAccess(). Gotcha: the override must keep the return_as_object parameter. |
| Report missing configuration |
checkRequirements |
Throw RequirementsException from checkRequirements() for missing deployment config such as a module or API key. Gotcha: it is a configuration-time signal; execute() and access() never call it, so re-check blockers in doExecute(). |
| Scaffold a tool with Drush |
Scaffolding with drush generate |
Run drush generate plugin:tool to write the plugin plus unit and kernel tests. Gotcha: the generated checkAccess() throws LogicException until you define access, and the generated catch returns exception messages to callers. |
| Call a tool from Drush |
Calling a Tool from Drush |
Use tool:list, tool:search and tool:info to inspect tools, and tool:run with --input and --json to run one. Gotcha: tool:run runs as anonymous unless you pass --uid, and the drush invoker cannot pass entity inputs. |
| Call a tool from PHP or a controller |
Calling a Tool from PHP |
Call checkPermission(), setInputValue(), validateInputs(), access() and execute() in that order, then read getResult() or getFormattedResult(). Gotcha: execute() does not check access; your code is the gate. |
| Expose tools to the AI module |
Calling a Tool from the AI Module |
Enable tool_ai_connector to turn every tool into an AI function named tool__ plus the ID with colons replaced by __. Gotcha: it exposes every tool and ignores operation, destructive and requirements; scope agent tool lists. |
| Run tools from ECA |
Calling a Tool from ECA |
Install drupal/eca_tool to run tools as ECA actions (eca_tool:) and to expose ECA models as tools through its Tool event. Gotcha: it needs core 11.3 or later, and its Tool event defaults to a destructive write. |
| Make a tool ready for MCP |
Calling a Tool over MCP |
MCP exposure is opt-in per tool through an mcp_tool_config entity, and content entities travel as handles. Gotcha: the bridge does not pre-check the declared permission, so keep refiners and input transforms free of side effects. |
| Browse and run tools in the admin UI |
Tool Explorer |
Enable tool_explorer to browse tools at /admin/config/tool/explorer and run one from a form. Gotcha: the execute form skips validateInputs(), so invalid input reads as access denied, and it never shows outputs. |
| Use ready-made tools instead of writing my own |
Tool Belt Catalog |
Check tool_belt 1.0.0-alpha6 before writing a tool for a common core task; it ships 58 tools in eight submodules. Gotcha: tool_belt:entity_save saves without entity validation; use entity_create or entity_update. |
| Prepare for 1.0.0-rc1 |
Preparing for Tool API 1.0.0-rc1 |
Before updating drupal/tool, replace multiple: TRUE with List definitions, declare outputs with output classes, rename the adapter alter hook and drop ExecutableResultInterface. Gotcha: rc1 removes the conversion shims. |
| Review a tool for security |
Security Checklist |
Before exposing tools to AI, MCP or scripts, check access rules, failure messages, undeclared outputs, entity validation, honest operation labels and who holds administer tool. Gotcha: operation Read is not enforced. |
| Keep tools fast |
Performance Checklist |
Keep create() and checkRequirements() cheap, filter catalogs with static checkPermission(), cache expensive Read tools inside doExecute() and page list tools. Gotcha: Tool API never caches tool results. |
| Check sources and versions |
Sources & Maintenance Manifest |
Source references and maintenance manifest for the tool api guides — web sources, code sources, and version history |