# Actions and tools

Choose between an action, a tool object, a built-in toolkit and an MCP server for something the model can call.

All four reach the model the same way: each turns into a function in the tool list sent with a chat turn. They differ in where the code lives and what the framework wraps around it.

## Which one to use

| You have | Use | Register it with |
| --- | --- | --- |
| Work that belongs to a role, and needs contracts, resilience, approvals or typed routing | An `@ActionSpec` method on a `Role` | `AgentBuilder.role(...)` |
| A helper function on a plain object | An `@Tool` method | `AgentBuilder.toolPojos(...)` |
| A toolkit that already ships in `tnsai-tools` | A `BuiltInTool` constant | `AgentBuilder.builtInTools(...)` |
| Tools served by an MCP server | An `MCP_TOOL` action that names the server | `AgentBuilder.role(...)` |

## Actions are tools

An `@ActionSpec` action is sent to the model as a function by default. Its name, description and parameters become the function schema, and the model can call it during a chat turn. Two cases are left out:

* **`excludeFromLLM = true`.** The action stays callable from code, for example with `agent.executeAction(...)`, but the model never sees it.
* **`MCP_TOOL` actions.** The action itself is configuration: it names the server. The tools that server lists are sent instead.

An action or MCP tool that the access gate would refuse is also left out of the list. See [Principal and authority](principal-and-authority.md).

An `LLM` action runs a model call. It does not add tools of its own.

## All four on one agent

<!-- java-contract: src/main/java/com/example/tnsai/docs/ActionsAndToolsExample.java -->
```java
package com.example.tnsai.docs;

import com.tnsai.accountability.AuthorityScope;
import com.tnsai.agents.Agent;
import com.tnsai.agents.AgentBuilder;
import com.tnsai.annotations.ActionSpec;
import com.tnsai.annotations.Tool;
import com.tnsai.annotations.ToolParam;
import com.tnsai.enums.ActionType;
import com.tnsai.enums.BuiltInTool;
import com.tnsai.identity.AgentPrincipal;
import com.tnsai.llm.LLMClient;
import com.tnsai.models.role.RoleIdentity;
import com.tnsai.roles.Role;

import java.util.Map;

public final class ActionsAndToolsExample {
    private ActionsAndToolsExample() {}

    public static final class OrdersRole extends Role {
        @Override
        public RoleIdentity getIdentity() {
            return new RoleIdentity("orders", "Answer questions about orders", "support");
        }

        @ActionSpec(type = ActionType.LOCAL, description = "Look up an order by id")
        public String findOrder(String orderId) {
            return "Order " + orderId + ": shipped";
        }

        @ActionSpec(type = ActionType.LOCAL, description = "Rebuild the order index",
                    excludeFromLLM = true)
        public String reindex() {
            return "reindexed";
        }
    }

    public static final class CurrencyTools {
        @Tool(name = "convert_currency", description = "Convert an amount between currencies")
        public double convert(
                @ToolParam(description = "Amount to convert") double amount,
                @ToolParam(description = "ISO code to convert from, e.g. EUR") String from,
                @ToolParam(description = "ISO code to convert to, e.g. USD") String to) {
            return amount;
        }
    }

    public static Agent build(LLMClient llm, AgentPrincipal principal, AuthorityScope scope) {
        return AgentBuilder.create()
            .llm(llm)
            .role(Role.create(OrdersRole.class))
            .toolPojos(new CurrencyTools())
            .builtInTools(BuiltInTool.WEB_SEARCH_TOOLS)
            .principal(principal)
            .authorityScope(scope)
            .build();
    }

    public static Object reindexFromCode(Agent agent) {
        return agent.executeAction("reindex", Map.of());
    }
}
```

* The model sees `findOrder`, `convert_currency` and the web search tools.
* It does not see `reindex`, which your code runs through `executeAction`.
* `WEB_SEARCH_TOOLS` creates `com.tnsai.tools.search.WebSearchTools` from `tnsai-tools`. Without that module on the classpath, `BuiltInToolInstantiationException` is thrown.

## Keep secrets out of schemas

Names, descriptions and parameter descriptions of actions and tools are sent to the model. Keep API keys and other credentials in the environment, not in any of them. To approve or reject a call at run time, use a tool call filter rather than wording in the schema. See [Tool approval](../server/tool-approval.md).

## Related

- [Actions](actions.md): action types and the action pipeline.
- [Custom tools](../tools/custom.md): write an `@Tool` object.
- [Tool catalog](../tools/catalog.md): every `BuiltInTool` constant.
- [MCP client](../mcp/client.md): connect to an MCP server.
- [Tools](../tools/index.md): registering and running tools.
