ADR-116: Central tooling authority — nr_llm owns builtin + MCP tools
- Status
-
Accepted
- Date
-
2026-07-22
- Authors
-
Netresearch DTT GmbH
Context
Two tooling systems exist in the stack today, and only one of them is nr_llm.
nr_llm is already the tooling authority for its own agent runs. It ships 45
builtin tools under Classes/Service/Tool/Builtin/ (among them
FetchLogsTool, GetLastExceptionTool, ListDeprecationsTool,
GetSystemStatusTool, SiteRagQueryTool and GetPageContentTool),
collects them in ToolRegistry (Classes/Service/Tool/ToolRegistry.php),
and executes them through ToolLoopService (ADR-038) behind
the tool gate (ADR-093) and the global availability state
(ADR-039). AgentRuntime (ADR-101,
Classes/Service/Agent/) is the public application service that owns the
run lifecycle — approval (ADR-084), guardrails
(ADR-085) and context-window bounding
(ADR-107) — with the interface
run / enqueue / runQueued / approve / submitInput /
cancel / events / status.
The separate nr_mcp_agent extension is a second, parallel tool stack
that bypasses all of the above. It is itself an MCP client
(Classes/Mcp/McpToolProvider, Classes/Mcp/McpConnection — a stdio
JSON-RPC client over proc_open — and McpServerRepository over the
tx_nrmcpagent_mcp_server table, whose transport field offers stdio
and sse). It re-implements its own agent loop in
ChatService::runAgentLoop and sources tools only from external MCP
servers (e.g. the suggested hn/typo3-mcp-server dependency), mapping the
MCP wire shape onto nr_llm's ToolSpec. It uses nr_llm merely as a
completion provider (ProviderInterface), never its ToolRegistry or
AgentRuntime.
The result is two agent loops with two independently maintained sets of fail-closed, approval, guardrail and context rules, and two disjoint tool sources (builtin-only in nr_llm, MCP-only in nr_mcp_agent) that no consumer can obtain together.
Decision
nr_llm is the single tooling authority for the AI stack. All tooling — builtin and MCP — is aggregated and executed there; consumers never reach an MCP server directly.
- Add an MCP client to nr_llm. nr_llm gains the ability to connect to
external MCP servers (stdio / http / sse), list their tools, and register
those tools into
ToolRegistryalongside the builtin tools. MCP tools then flow through the exact same path as builtins:ToolRegistry→ToolLoopService/AgentRuntime, subject to the same tool gate (ADR-093), the same availability state (ADR-039), the same approval (ADR-084), guardrail (ADR-085) and context-window (ADR-107) enforcement. - Consumers obtain all tooling exclusively via nr_llm. A consumer (for
example a backend AI-chat module) takes its tools from
ToolRegistryand drives runs throughAgentRuntime. It never opens an MCP connection itself. MCP servers are wired only through nr_llm. - One loop, one trust boundary. There is a single agent loop
(
AgentRuntimeoverToolLoopService). Builtin and MCP tools share the one tool-data trust zone (ADR-094) and the one gate, so a tool's origin does not change how it is authorised, approved or audited.
Consequences
- The MCP-client capability moves out of nr_mcp_agent and into nr_llm:
the connection, tool-listing and schema-normalisation logic that today lives
in
McpToolProvider/McpConnectionbecomes an nr_llm concern, and the server-configuration storage (transport, command/arguments, url/auth token) moves with it. ToolRegistrybecomes the aggregation point for builtin and MCP tools; the allow-list, availability toggle and gate apply uniformly regardless of where a tool came from.AgentRuntimeis the single agent loop.nr_mcp_agentdeletesChatService::runAgentLoopand stops assembling its own lifecycle; its divergent fail-closed / approval behaviour disappears with it.nr_mcp_agentis reduced to a thin backend chat UI — module, toolbar and conversation store — drivingAgentRuntime. With MCP gone it is arguably mis-named: a rename (candidatenr_llm_chat) or an outright fold-in to nr_llm are both on the table (see follow-up).hn/typo3-mcp-serverbecomes an MCP server that nr_llm connects to, not a per-consumer composer dependency; any number of external MCP servers attach the same way.- New public surface lands in nr_llm (MCP client configuration + registration). It is a minor-release growth path and will carry its own ADR when the implementation is designed; the public-service count authority (ADR-101) is updated then, not here.
Migration and follow-up
Implementation is separate follow-up work; this ADR records the target only.
- Build the MCP client in nr_llm: transports (stdio / http / sse), the
tools/listhandshake, inputSchema-to-provider-schema normalisation (the concernMcpToolProvideralready solves), and registration of the resulting tools intoToolRegistry. - Move the MCP server configuration model (
transport,command/arguments,url/auth_token) into nr_llm. - Repoint
nr_mcp_agent'sChatServiceontoAgentRuntimeand delete itsClasses/Mcp/client andrunAgentLoop. - Decide rename versus fold-in for
nr_mcp_agentas a discrete step. - Reconcile streaming and context-window parity for MCP-sourced tools per the scope note in ADR-107.