---
title: "Tool extension API"
manual: "LLM Foundation"
version: "main"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:api-tool-extensions@main"
source: "Api/ToolExtensions.rst"
rendered: "2026-11-12T15:41:35+00:00"
---

# Tool extension API {#api-tool-extensions}

Implement `NetresearchNrLlmServiceToolToolInterface` to register a
PHP tool. Its autoconfiguration tag is `nr_llm.tool`; names must be unique.
A custom group must be enabled together with its tool before the model can
receive the declaration. See [Managing tools](https://docs.typo3.org/permalink/netresearch/nr-llm:administration-tools-manage@main).

## Complete minimal tool {#api-tool-extensions-example}

This deliberately small tool returns a fixed greeting and reads no records,
files or external service. A tool that reads user-scoped data must additionally
check page, table and storage permissions inside `execute()`.

**Classes/Tool/GreetingTool.php**

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Tool;

use Netresearch\NrLlm\Domain\ValueObject\ToolResult;
use Netresearch\NrLlm\Domain\ValueObject\ToolSpec;
use Netresearch\NrLlm\Service\Tool\ToolExecutionContext;
use Netresearch\NrLlm\Service\Tool\ToolInterface;

final class GreetingTool implements ToolInterface
{
    public function getSpec(): ToolSpec
    {
        return ToolSpec::function('example_greeting', 'Return a greeting.', [
            'type' => 'object',
            'properties' => [
                'language' => ['type' => 'string', 'enum' => ['en', 'de']],
            ],
            'required' => ['language'],
            'additionalProperties' => false,
        ]);
    }

    public function execute(
        array $arguments,
        ToolExecutionContext $context,
    ): ToolResult {
        if ($context->actingBackendUser() === null) {
            return ToolResult::error('Not permitted.');
        }
        return match ($arguments['language'] ?? null) {
            'en' => ToolResult::text('Hello.'),
            'de' => ToolResult::text('Hallo.'),
            default => ToolResult::error('Unsupported language.'),
        };
    }

    public function isEnabledByDefault(): bool
    {
        return false;
    }

    public function requiresAdmin(): bool
    {
        return false;
    }

    public function getGroup(): string
    {
        return 'my_extension';
    }
}
```

## Results, effects and authorization {#api-tool-extensions-results}

`ToolResult::text($content, ...$artifacts)` sends only `content` to the
model. Artifacts are for the run's backend/audit views and still need the same
redaction and output handling. `ToolResult::error($message)` returns a
controlled error without artifacts or a write target. Do not expose exception
messages, credentials or unrestricted records through either channel.

Use `$context->actingBackendUser()` rather than an ambient global user.
`requiresAdmin()` supplies the coarse runtime gate; it does not replace
per-record authorization for tools available to editors.

Writers also implement `ToolEffectInterface` and the relevant approval
preview contract. A record writer reports its target and write kind through
`ToolResult::withWriteTarget()`. A creator implements
`RecordCreatorInterface::getCreatedTables()` so the generic creator can
step back from its tables. See [Tools, languages and workspaces](https://docs.typo3.org/permalink/netresearch/nr-llm:tool-workspaces@main) and the contracts in
`Classes/Service/Tool/` before adding writes; a plain read-tool example is
not a complete writer implementation.

## Availability and unattended execution {#api-tool-extensions-availability}

`UnavailableToolsResolverInterface::unavailable($configuration, $user)`
returns refused `ToolPolicyDecision` objects in registry order, including
globally disabled tools. It explains unavailable capabilities; it grants none.
Remote tool names are shown only to administrators. Built-in trust checks in
observe mode can still offer a tool; remote checks stay enforced.

`UnattendedToolFilterInterface::unattended($toolNames)` narrows a list to
registered tools that need neither approval nor typed input. First obtain the
policy-allowed names with `ToolCallPolicyInterface::filterOfferable()`.
The unattended filter does not establish authorization. Preserve `[]` as an
empty offer; `null` means resolve the enabled set.

For managed execution and resumable decisions use [Persisted agent runtime](https://docs.typo3.org/permalink/netresearch/nr-llm:api-agent-runtime@main).
For manual protocol handling see [Tool/function calling](https://docs.typo3.org/permalink/netresearch/nr-llm:developer-tool-calling@main).
