Skip to main content
Tools are how a skill does things. Look up an account, lock a card, place an order. A tool is a decorated Python function. Drop it in the skill’s tools/ folder and it’s automatically available; the LLM reads its name, description, and inputs and decides when to call it.

Defining a tool

skills/check_balance/tools.py
The function signature is the schema:
  • Name: the function name (check_balance)
  • Description: the @tool decorator argument
  • Inputs: extracted from the type hints
  • context: injected by the runtime, never visible to the LLM
Tools are auto-discovered from the skill’s tools.py (or a tools/ folder, if you’d rather split them across files). No imports, no registration. The LLM sees the tool and calls it when the instructions call for it. The tool must be async, and the injected parameter must be named exactly context.

Using a tool in instructions

Reference a tool from the instructions body with its plain name:
The tool is already in the model’s schema whenever the skill is active: naming it in prose is guidance about when to use it, not a reference the compiler resolves.

Sharing state with the skill

Tools read and write memory through context, so their results are available to the rest of the skill:
Every key you write must be declared in the skill’s memory.yml or the project-root one. rasa train rejects an undeclared write. A tool can also send a message to the user directly, bypassing the LLM. It is a coroutine, so it must be awaited:

When a turn is cancelled

On voice, a user can start talking while your tool is still running. The runtime ends the turn and raises asyncio.CancelledError inside your tool at whatever await it is suspended on. Read-only tools can ignore this. For a tool that changes something, check context.is_cancelled before the irreversible step. See Cancellation.

Shared tools

Local tools need no declaration. To use a tool defined outside the skill folder — one in the agent-root tools/ folder — add import_tools to the frontmatter:
A shared tool that isn’t declared is not available to the skill at all.

Import MCP tools

To call a tool on a remote MCP server, declare the server in integrations.yml, then allowlist the tool in the skill:
integrations.yml
skills/check_balance/skill.md
import_tools is an explicit allowlist: only the tools you list are attached. The tool arrives under its bare name (get_balance), which is what tool_constraints use. In the body, refer to it as @tool.get_balance, the same form as any other tool. If the same bare name would collide with a local or shared tool in that skill, train fails. If the server cannot be reached when the agent starts, startup fails. Full rules are in the skill.md reference.

Adding guarantees

By default the LLM decides when to call a tool, and it can get it wrong. Gate a tool behind a required value with tool constraints:
Now check_balance is invisible to the LLM until account_number is set. requires: is a string expression using the namespaced memory form: see Conditions. When a tool actually runs, Mantle records tool_executed with source local or mcp. For MCP tools, mcp_server is the MCP server name (the mcp_servers: key in integrations.yml, same id as in mcp/<server>:<tool>) so two servers that expose the same tool name stay distinct. Local tools omit it. See Tracker events.

Handing off to another skill from a tool

A tool can end by activating another skill. Return an ActivateSkill in ToolResult.next:
The handoff runs after the tool’s result is recorded and works like a call step: the current skill waits and continues when the new skill finishes. To start one ordered block of the skill, pass ordered_block="<block_id>". See ActivateSkill for when it runs and which tools honour it. To call another tool from a tool, call it as plain Python: await other_tool(..., context=context).

Reference

For ToolContext, ToolResult, invocation modes, tool resolution order, and the built-in default tools, see the Tools reference.