Configuration
All settings are managed via Admin Tools > Settings > Extension Configuration > nr_mcp_agent.
LLM connection
llmTaskUid
-
- Type
- int
- Default
- 0
UID of an nr-llm Task record. This Task defines which LLM provider and model to use (e.g. OpenAI GPT-4, Anthropic Claude). Required -- the extension will not work without a valid Task UID.
Create the Task record in the nr-llm backend module first, then enter its UID here.
With groupTaskMapping set, this is the Task of the users no mapping applies to; it can then stay
0if every user of the chat is in a mapped group.
groupTaskMapping
-
- Type
- string
- Default
- (empty)
A different nr-llm Task per backend user group, written as comma-separated
groupUid:taskUidpairs, for example3:12,5:14. The first pair whose group the user belongs to decides -- the order of the setting, not the order of the user's groups. Membership includes subgroups, so mapping a parent group covers the groups below it. Users in none of the mapped groups use llmTaskUid. A malformed pair is skipped; the others still apply.The Task decides the provider, the model and the prompts; what the assistant may do is still decided per user by nr-llm, so a mapping never widens a user's permissions. Before each turn the chat checks the Task's Configuration: it must be active, and if it is restricted to backend groups, the user must be in one of them (administrators always are). Otherwise the turn fails and says why. This applies to llmTaskUid too. See ADR-015.
processRunMaxIterations
-
- Type
- int
- Default
- 12
How many model rounds a run of a guided process (a skill marked as a process, such as the SEO tour) may take before it stops. A round is one answer of the model, with or without tool calls. The chat asks nr-llm for this cap on every run that starts or continues a process; nr-llm caps it at its maximum, the nr-llm setting
agent.maxIterations(20 unless changed). Ordinary chat runs takechatRunMaxIterations. A value below 1 uses the default.A run that reaches the cap says so in the chat, and a tour offers Weiter to go on. See ADR-023.
chatRunMaxIterations
-
- Type
- int
- Default
- 0
How many model rounds an ordinary chat run, one without a guided process, may take before it stops.
0, an empty value or anything that is not a whole number above 0 asks nr-llm for no number, so its default applies: the nr-llm settingagent.defaultMaxIterations(5 unless changed). nr-llm caps every value atagent.maxIterations. A run that resumes after an approval or an answer gets the number again, capped at the maximum in force then. More rounds make a turn longer; a worker ends a turn after--turn-limitseconds, so a large value may need a larger limit.The overview module lists the rounds each kind of run gets, as nr-llm applies them. A worker that started before a change stops before its next turn and the next worker reads the new value. See ADR-023.
Processing
maxLiveStreams
-
- Type
- int
- Default
- 3
How many conversations may stream their answer at the same time. A stream holds a PHP-FPM worker while a turn runs, so keep this well below
pm.max_children. A further conversation shows the answer when it is complete, through polling, as does every conversation with0.The stream needs a web server that passes a response on while it is written. It sends
X-Accel-Buffering: nofor nginx. A proxy in front that buffers responses makes the chat show the text in larger pieces. See ADR-028.
processingStrategy
-
- Type
- string
- Default
- exec
How chat messages are processed in the background:
exec-
Forks a CLI process per request (
ai-chat:process). Simple, no extra setup. Best for development and low-traffic sites.The process runs the
typo3binary from the Composerbin-dirof the project (defaultvendor/bin), ortypo3/sysext/core/bin/typo3in a classic installation. If the binary is not there, the conversation fails with a message and the path that was checked is written to the TYPO3 log. worker-
Uses a long-running worker process (
ai-chat:worker) that polls for new messages. Better for production -- lower latency, no process forking overhead.A site set to
workermust runai-chat:worker(see below); without it, conversations stay inprocessinguntilai-chat:cleanupmarks them failed.With either strategy, a turn whose process crashes stays
lockeduntilai-chat:cleanupmarks it failed after five minutes, so scheduleai-chat:cleanup.
Access control
allowedGroups
-
- Type
- string
- Default
- (empty)
Comma-separated list of backend user group UIDs that are allowed to use the AI chat. Leave empty to allow all backend users. Only direct membership counts, not subgroups; administrators always have access.
Chat panel
When llmTaskUid is configured, a chat button appears
automatically in the TYPO3 backend toolbar (top right).
Clicking it opens a floating bottom panel that stays visible
across module navigation.
The panel supports four states:
- Hidden -- Default. Only the toolbar button is visible.
- Collapsed -- Minimal header bar showing the active conversation title and status.
- Expanded -- Resizable panel with chat messages, input, and a compact conversation switcher.
- Maximized -- Full-height panel with a sidebar for conversation management (search, pin, archive).
The panel height and state are persisted per user in the browser's localStorage.
A site's own AI configuration
A site can name its own nr-llm configuration in the site setting
LLM configuration (nrllm.configuration, set netresearch/nr-llm,
nr-llm ADR-244), for example because its content may only go to one
provider. The chat honours it: a conversation about a page of that site
runs on the site's configuration -- its provider, model and system prompt
-- instead of the configuration of the chat's Task. The Task's prompt
template still applies. Sites without the setting, conversations about no
page, and pages the user may not show keep the Task's configuration. The
choice is made again on every turn, so a changed setting applies to the
next message. See ADR-033.
The chat reads and changes pages only through tools, so it refuses a site configuration it cannot run on, before any model call, and says why in the reader's language:
- the configuration does not exist, is disabled, or is restricted to backend groups the user is not in;
- it selects its model by criteria instead of naming a fixed model;
- its model cannot call tools: the model record lacks the capability Tool Use (AI > Models), or its provider does not support tool calling.
It never falls back to the Task's configuration for that site, because that would send the site's content to a provider the site was set up to avoid. Administrators also get a link to the LLM configurations.
Model discovery ticks Tool Use for the providers that report it per model (Mistral, OpenRouter, Ollama) and for the curated OpenAI, Anthropic and Gemini models. Groq reports no capabilities: tick it by hand on a Groq model that supports tool calling.
What else follows the site's configuration:
- Term lists of both configurations apply: those bound to the chat Task's configuration and those bound to the site's. A ban of either one blocks, and a list bound to both reports its terms once (nr-llm ADR-290). Site-bound lists keep applying as before. The same holds for the rule-set judge: where both configurations have one, both judge, and the stricter mode decides.
- Privacy level is read from the run's configuration, so the level of the site's provider applies.
- Tool groups and excluded tools are the site configuration's. An empty group list allows every group.
- Approvals waiting when the setting changes to another configuration, or to one the chat cannot run on, are not carried out on the old configuration: the chat hands the card or question back and asks for a new message. When the setting is cleared, a waiting run continues on the configuration it started with.
The run of each turn records its configuration; nr-llm's Agent Runs module (AI > Operation) shows it on the run.
System prompt
The system prompt sent to the LLM is not configured in the extension configuration itself, but in the nr-llm records:
- Configuration record (
tx_nrllm_configuration.system_prompt) -
The primary system prompt. Set this to define the AI assistant's persona, language, and behavior. Also use this field for instructions on how the tools registered in nr-llm are to be used.
Example:
Du bist ein TYPO3-Assistent. ## Tool-Nutzung - Vor jeder Aussage über den Seitenbaum erst get_pagetree aufrufen, nie aus dem Verlauf raten. - Datensatzfelder über read_records lesen, nicht aus einer früheren Antwort zitieren. - create_content_element_draft legt ein verstecktes Element an; sag danach, wo es liegt und dass es noch freigeschaltet werden muss.Copied!Name the tools your installation actually has -- AI > Operation > Tools in nr-llm lists them, and AI > Operation > MCP Servers is where an external server's tools come from.
- Task record (
tx_nrllm_task.prompt_template) - Additional instructions appended after the Configuration prompt. Use this for task-specific context.
When both fields are set, they are combined (separated by a blank line). If neither is set, a locale-based default prompt is used. For a page of a site with its own AI configuration (see A site's own AI configuration), the Configuration record is the site's.
- Instructions for a conversation (
tx_nrmcpagent_conversation.system_prompt) - Set by the user in the chat (the sliders button, see Usage). They are added after the two prompts above, which stay in force: the assistant is told to follow the user's instructions only where they do not contradict the configured ones. The assistant's identity, the site-language rules and the user's own context are added either way.
Every prompt ends with the user's context:
- Answer language -- the language the user's backend is set to. The assistant answers in it whatever language the message is written in, unless the message explicitly asks for another language.
- Where the user is -- the open backend module and, when the module shows a page, that page's uid and title. The page is included only if the user may see it; the check runs with the user's page permissions when the turn is processed. It lets "summarise this page" work without naming a uid.
User interface
autoArchiveDays
-
- Type
- int
- Default
- 30
Automatically archive conversations that have been inactive for this many days. Set to
0to disable auto-archiving.Auto-archiving runs via the
ai-chat:cleanupcommand.
File attachments
File attachments are always available — no special provider configuration required. Text is extracted server-side for document formats, so they work with any LLM provider.
Always supported (server-side text extraction):
- PDF:
application/pdf— requiressmalot/pdfparser(hard dependency) - DOCX:
application/vnd.openxmlformats-officedocument.wordprocessingml.document— requiresphpoffice/phpword(hard dependency) - TXT:
text/plain— no dependencies - XLSX:
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet— requiresphpoffice/phpspreadsheet(optional; install viacomposer require phpoffice/phpspreadsheet:^3.0)
Additionally available for vision-capable providers (Claude 3+, Gemini, GPT-4o, etc.):
- Images:
image/png,image/jpeg,image/webp
When the provider natively handles a document format (e.g. Claude
natively processes PDFs via DocumentCapableInterface), the file is
sent as binary instead of being extracted. The file picker automatically
restricts to formats the active provider can process.
Storage: An uploaded file is a managed TYPO3 file from the moment it
arrives: it is written into the default storage, indexed in sys_file
and given a sys_file_metadata record, like any other file in the file
module. It is then read at LLM call time and sent as Base64-encoded
multimodal content, and the assistant is told its sys_file uid and
path — so it can reference the attachment from a content element with
nr-llm's file tools instead of asking for it to be uploaded again.
Nothing is ever overwritten or deleted. A name already taken in the folder
yields report_01.pdf; if the file of that name has identical content,
the existing one is returned instead of a copy. Where the user may not
write, the upload is refused with 403 rather than failing as a server
error — the file mounts and permissions of the logged-in backend user
apply throughout.
attachmentFolder
-
- Type
- string
- Default
- ai-chat
Folder for chat attachments in the default storage, relative to its root —
ai-chatmeansfileadmin/ai-chat/. A per-user subfolder (<be_user_uid>) is created inside it and is not configurable: it is what keeps one user's attachments out of another's. An empty value falls back to the default rather than writing into the storage root.
Limits:
- Maximum 5 files per conversation.
- Maximum file size: 20 MB per file.
- File count is enforced both in the frontend (before upload) and in the backend API.
Security: Decide deliberately whether the attachment folder is publicly readable, because the two things it is used for pull in opposite directions.
An attachment is material a backend user hands to the assistant, and it is stored under a path that is guessable by name. Where attachments are only ever read by the assistant, deny direct HTTP access to the folder:
# fileadmin/ai-chat/.htaccess
Require all denied
Where editors are meant to place an attached image or PDF into a content element — the file is already in FAL, so this needs no second upload — the folder must stay publicly readable, or the reference renders as a broken image in the frontend. Denying access to the folder and referencing files out of it is a contradiction, not a hardened setup. Point attachmentFolder at a folder your editors publish from in that case, and treat what is uploaded through the chat as publishable.
Security
maxMessageLength
-
- Type
- int
- Default
- 10000
Maximum length of a single user message in characters. Set to
0for unlimited (not recommended).Messages exceeding this limit are rejected with an error.
maxActiveConversationsPerUser
-
- Type
- int
- Default
- 3
Maximum number of simultaneously active (processing) conversations per user. Prevents a single user from overloading the system. Set to
0for unlimited.A new or edited message, the start of a guided process, the turns on an image from the list of missing alt texts, and Retry of a failed turn are refused while the user is at the limit. A decision on a proposal and an answer to a question carry on a run that is already there; the limit does not apply to them.
Choices in guided processes
A guided process asks its plain choices -- which point first, whether to
continue, how to go on when a source is missing -- through nr-llm's tool
ask_choice. The chat shows its answers as reply buttons above the
input. nr-llm ships ask_choice switched off: enable it under
AI > Operation > Tools in nr-llm before a process relies on it.
Without it a process cannot ask such a choice and has to put it into its
answer instead. ask_choice needs an nr-llm that has it (nr-llm PR 1029).
The answers are the options the process passes, shown as they are. A "go on without" option, such as "Ohne Bild weiter", is worded by the process's skill, not by the chat.
Dashboard
These settings fill the AI assistant dashboard widgets (see
Dashboard widget). The chat's skill catalogue names a skill by nr-llm's
<source uid>:<path>, such as 3:seo-optimieren, so the same skill has
a different identifier on every installation. A setting names a skill in one
of two ways:
- a bare slug without
:, such asseo-optimieren: the catalogue skill whose identifier is<source uid>:seo-optimieren. Where several sources carry the slug, the source with the highest trust level wins (nr-llm's skill trust: first party, verified, community, untrusted), then the one with the lowest source uid; - a catalogue identifier, such as
3:seo-optimieren: exactly that skill.
Letters, digits and ._:/- are accepted, starting with a letter or digit
and at most 100 characters; any other entry is ignored.
What an editor reads for a skill comes from
EXT:nr_mcp_agent/Resources/Private/Language/locallang_dashboard.xlf:
skill.<key>.title, skill.<key>.description and, for a frequent task,
task.<key>. <key> is the identifier with every character other than
a letter, digit, - or _ replaced by _: 3:skills/seo is
labelled by skill.3_skills_seo.title. A catalogue identifier without
labels of its own uses those of its slug, so 3:seo-optimieren reads
skill.seo-optimieren.title. For a skill of your own, add
these labels through a language override
($GLOBALS['TYPO3_CONF_VARS']['SYS']['locallangXMLOverride']). An entry
without a title is left out rather than shown by its identifier.
An entry is offered only when the chat can start its skill for the user looking at the dashboard: the skill is in the chat's skill catalogue for that user, nr-llm can pass a skill to a run, and the configuration does not require a second person to approve every change. Otherwise the entry is left out, and a widget left with nothing to offer says that nothing is set up yet. A configured skill that does not exist therefore never leads to a link the chat would refuse.
dashboardGuidedSkills
-
- Type
- string
- Default
- seo-optimieren,inhalt-verbessern
The guided tours the Improve a page widget offers, comma-separated, in this order.
dashboardQuickTasks
-
- Type
- string
- Default
- (empty)
The entries of the Frequent tasks widget, comma-separated. Append
|pageto a task that works on one page: its row then opens the page module and the chat asks for the page there. Without it the row starts the task in the chat module. Empty by default, so the preset does not repeat the guided tours; the widget then says that no task is set up yet.A skill that declares
pagein its frontmatter (see Whether a skill works on a page) decides this itself:page: requiredasks for the page,optionalandnonedo not, whatever the suffix says. The suffix applies to skills that declare nothing.
Missing alt texts
The check for images without an alt text (see Decorative images
and ADR-026: A Site-Wide Check for Missing Alt Texts) reads every image field of type file in the TCA, so
the image fields of a newly installed extension are checked without any
setting. These two settings leave fields and tables out.
altTextExcludedTables
-
- Type
- string
- Default
- (empty)
Tables whose images are not checked, comma-separated, such as records that are never shown in the frontend. Content elements inside such a record are left out with it. By default every table is checked.
altTextExcludedFields
-
- Type
- string
- Default
- pages.og_image,pages.twitter_image,pages.nav_icon,be_users.avatar,backend_layout.icon
Image fields that are not checked, as
table.field, comma-separated: fields whose images the frontend does not output as an image with an alt text. The default leaves out the page's social-media images, the navigation icon (bootstrap_package renders it as an icon without the alt text), the backend user's avatar and the backend layout's icon, which only the backend shows. A value set here replaces the default, so keep the entries you still want.
Image analysis (privacy)
Whether images are sent to the AI provider (see ADR-026: A Site-Wide Check for Missing Alt Texts, slice 3). Whether an image shows people cannot be told before it is analysed, so the setting applies to every image and to every feature of the assistant that sends one: the scaled copy for an alt text proposal (Optimise an alt text with the assistant) and a file attached in the chat (Attaching files), where a document the provider would read as a whole (a PDF carries its photos along) goes as its extracted text instead. Sending images needs a data processing agreement with the operator that covers the AI provider's processing, images of people included.
A change applies from the next turn of a conversation, also with
processingStrategy worker: each turn runs with the setting of
the backend request that started it, which the request stores with the
conversation. One exception keeps the image card and the model in
agreement: where the card of an alt text conversation already says the
image is not sent, the image stays unsent after analysis is switched back
on, until the chat moves on to another image. A turn that continues after
an approval or an answer resumes the stored run of the AI provider, so an
image that run already sent goes to the same provider again; the next turn
follows the setting.
It is part of the extension configuration (category privacy), which only administrators with system maintainer rights can change, as every setting on this page.
imageAnalysis
-
- Type
- options
- Default
- analyse
analyse(Analyse images): a scaled copy of the image is sent; proposals consider the image and its context.contextOnly(Context only): no image is sent. An alt text proposal rests on the page or record, the content element or field and the file name, and the image card in the chat says that image analysis is switched off. The chat offers no image format for attachments, nor a document format only the provider reads, and refuses such an upload; an image attached before is not sent, and a document attached before (a PDF) goes as its extracted text.Any other value sends no image, so a value written by hand that is not one of the two never sends images by mistake.
imageAnalysisExcludedFolders
-
- Type
- string
- Default
- (empty)
Folders whose images are never sent, comma-separated, for example
fileadmin/portraits/. A folder includes its subfolders. Write a folder asfileadmin/portraits/(the base path of a local storage in the web root, in any case, and the folder) or as1:/portraits/(the storage uid and the folder); use the second form for a storage outside the web root. A folder written without a storage, such asuser_upload/team/orportraits, is a folder of the default storage, as the file list shows it..and..are resolved within the storage. A folder belongs to its storage:fileadmin/team/does not exclude/team/in another storage, norfileadmin/teamwork/. An entry that names no folder of a storage, such as one that climbs above the storage's root, or a bare folder on an installation without a default storage, excludes nothing. It is logged as a warning, once per process and configured value; so is a folder that does not exist (yet), which is excluded all the same. A document attached in the chat from such a folder goes as its extracted text only. Applies while imageAnalysis isanalyse; the image card names the folder.
Worker mode production setup
For production use with processingStrategy = worker,
set up a systemd service to keep the worker running:
# /etc/systemd/system/typo3-ai-chat-worker.service
[Unit]
Description=TYPO3 AI Chat Worker
After=mysql.service
StartLimitIntervalSec=0
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/html
ExecStart=/var/www/html/vendor/bin/typo3 \
ai-chat:worker --poll-interval=100 --max-turns=1
Restart=always
RestartSec=0
TimeoutStopSec=610
[Install]
WantedBy=multi-user.target
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable typo3-ai-chat-worker
sudo systemctl start typo3-ai-chat-worker
A worker ends by itself and expects to be started again:
--max-turns- After this many turns.
1starts every turn in a fresh process that is already booted, which is the recommended value: nothing of a turn reaches the next one, and a configuration change counts from the next turn.0keeps the process running, without a boot between turns. --time-limit,--memory-limit- After this many seconds, above this many megabytes. Defaults: 3600 and 256.
--turn-limit- A turn that runs longer than this many seconds, 600 by default, ends the process and fails, and the reader gets a Retry. It is never run a second time by itself, because it may have written.
- A changed setting
- When the system settings file was written since the worker started, it stops before its next turn, so the next worker reads the new values.
SIGTERM and SIGINT stop a worker after its current turn, so give it the
time (TimeoutStopSec, or stop_grace_period in Compose). This and
--turn-limit need the PHP extension pcntl; without it the process
dies on SIGTERM and a stuck turn waits for ai-chat:cleanup.
One worker runs one turn at a time. Run as many as turns should run at the
same time. A turn that finds no free worker starts a process of its own, as
with exec, so a worker that is down costs the time of that start and
nothing else. See ADR-029.
Scheduled cleanup
Add the cleanup command to your cron or TYPO3 scheduler to handle stuck conversations, auto-archiving, and deletion of old data. A conversation whose worker never finished stays in processing until this command marks it failed after five minutes — without the schedule it can stay there indefinitely:
# Run cleanup daily at 3:00 AM
0 3 * * * /var/www/html/vendor/bin/typo3 \
ai-chat:cleanup --delete-after-days=90