---
title: "ADR-217: Delegated MCP authentication keeps the acting identity"
manual: "LLM Foundation"
version: "0.41"
permalink: "https://docs.typo3.org/permalink/netresearch/nr-llm:adr-217@0.41"
source: "Adr/Adr217McpDelegationKeepsTheActor.rst"
rendered: "2026-10-20T08:48:47+00:00"
---

# ADR-217: Delegated MCP authentication keeps the acting identity {#adr-217}

-   *Status:* Accepted
-   *Date:* 2026-10-09
-   *Amends:*

    [ADR-116](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-116@0.41) (MCP may use an audience-scoped credential
    delegated from the initiating actor, in addition to a server credential)
    and [ADR-190](https://docs.typo3.org/permalink/netresearch/nr-llm:adr-190@0.41) (retain cancellation argument positions while
    appending actor/session context on the final concrete MCP classes)

-   *Authors:* Netresearch DTT GmbH

## Context {#context}

The runtime already carries the initiating actor into builtin and MCP tools,
including queue workers. Resumed backend runs currently reconstruct the owner
from the run row, which loses a named service actor. Suspensions therefore
need an explicit actor snapshot. An MCP server sees the one Vault credential
configured on its server record. That is appropriate for
a shared service identity. It cannot let a ticket, document or calendar service
enforce the individual caller's rights.

[F13](https://f13-os.de/), an open source AI system for public
administration, exchanges the caller's token for a token addressed to the
selected service.
TYPO3's backend actor is not itself an OAuth token. Delegation therefore needs
an explicit actor-to-identity-provider mapping and a credential resolver, not
an assumption that the current HTTP request contains a portable identity.

## Decision {#decision}

1.  **Add delegation as an opt-in authentication mode.** Existing anonymous and
    server-Vault authentication keep their behaviour. Delegated servers name an
    exchange profile, audience and requested scopes. Configuration never stores
    a plaintext token. A malformed explicit mode is refused, not converted to
    anonymous or machine authentication.
1.  **Resolve the initiating actor, not the approver.** A credential-resolver
    extension point maps the explicit execution context and exchange profile to
    a Vault subject-token identifier. The default mapping is operator supplied;
    absent or revoked mappings deny. Queue workers and resumes resolve again
    from the persisted actor. No access/refresh token enters a run request,
    suspended state, transcript or event. A service actor needs an explicit
    mapping too; it never inherits a backend user's ambient session.

    Installation profiles map `backendUsers[uid]` and
    `serviceAccounts[name]` explicitly. Each grant declares `enabled: true`,
    a `credentialIdentifier`, `allowedAudiences` and `allowedScopes`.
    Requested scopes are explicit and nonempty. The requested audience and
    scopes must satisfy both the profile's and the
    actor grant's bounds; an absent or disabled grant denies.

    Subject, client-secret and delegated discovery references must be canonical
    Vault UUIDv7 values or ASCII aliases of 3 to 255 characters. An alias starts
    with a letter and contains only letters, digits and underscores. Controls,
    reference wrappers and other UUID versions deny before IdP contact. This
    validation belongs to the new delegated contracts; existing static server
    authentication keeps its behaviour.

    New queued and suspended payloads bind the actor to the run UUID inside the
    authenticated state envelope. A backend UID must match the stored owner; a
    service actor keeps its original name and owner zero. Validate before and
    after claiming a suspension. Malformed explicit actors and copied states
    deny; older payloads without actor bindings keep their stored backend owner.
    State clones and instruction-pin updates preserve both actor and UUID.
1.  **Exchange rather than forward.** The RFC 8693 grant requests an access
    token with an explicit audience and bounded scopes. The subject token is
    injected into the form by nr-vault's body-field placement. Optional client
    authentication is Vault backed too. The original token never authenticates
    a request to the MCP endpoint. The response must declare an acceptable
    issued-token type, Bearer type and a usable finite lifetime; an invalid or
    refused response stops the call with a generic, secret-free error.
1.  **Keep the existing network boundary.** Both the token endpoint and the MCP
    target pass the Vault host gate before any credential is injected, and use
    its DNS, redirect and audit protections. Exchange work spends from the MCP
    operation's existing deadline. Cancellation and exhausted budget stop
    before another token or tool request is dispatched.
1.  **Use expiring Vault references downstream.** Exchange access-token values
    are stored under random, short-lived Vault identifiers with expiry and
    context metadata. Their ACL binds the initiating owner without inheriting
    backend groups or frontend access. Verify the stored ACL and delete the
    reference before denying if Vault changes its owner or grants. A background
    execution identity requires explicit Vault technical-actor access.
    The MCP transport only receives the reference, and Vault
    injects the Bearer value. Cleanup deletes temporary references when the
    operation ends; expiry bounds a crashed worker's leftover record.
    Subject and client secrets are never retrieved into nr-llm to build forms.
    Parsing the successful exchange response is the bounded point at which
    nr-llm handles the new access-token value before storing it in Vault.
1.  **Check before every leg.** A handshake does not authorise a later request
    with an expired token. A request-scoped credential session checks remaining
    lifetime and exchanges again when needed. Its identity includes actor,
    exchange profile, audience and scopes. It is not cached on a shared MCP
    transport singleton, and never reused for another server/actor combination.
    Credential resolution and storage spend the operation budget before the
    transport checks cancellation and chooses the remaining HTTP timeout.

    Existing interfaces retain their signatures. Optional actor/session context
    is appended only to final concrete MCP classes; cancellation retains its
    existing positional index, type and nullable default (ADR-190).
1.  **Discovery has a separate authority.** Catalogue import and connection
    probes without an execution actor do not fabricate one. A delegated server
    may explicitly configure a discovery-only Vault credential; otherwise an
    actorless operation is refused. That credential cannot authenticate calls
    executed by an agent, and failure never falls back to it.

## Alternatives considered {#alternatives-considered}

Forwarding an incoming user token would give the target a credential intended
for another recipient and make queue behaviour depend on an HTTP session.
Putting a token in the persisted actor would turn a run record into a secret
store. Always using a machine credential preserves the current mode but cannot
provide individual downstream authorisation.

nr-vault's OAuth configuration supports client-credentials and refresh grants,
not token exchange. Treating its additional parameters as a grant override is
not a supported substitute. This change uses its checked secret placements
and temporary secret lifecycle, not a raw HTTP workaround.

## Consequences {#consequences}

The additive surface is an authentication mode/profile, a subject-credential
resolver and delegated-credential session. Existing MCP method parameters keep
their position; optional execution context is added at the end where needed.
New schema columns use defaults that preserve old server records. The API
snapshot records the additions.

The implementation must verify the installed nr-vault body-field injection,
expiry/store/delete and confidential-client composition contracts against the
supported dependency range. If a required combination is unavailable, it is a
dependency requirement to resolve; secrets must not be manually copied into
an unguarded request to make a test pass.

Public OAuth clients use the existing Vault body-field injection for their
subject token. Confidential clients additionally require Vault's additive
`AdditionalSecretHttpClientInterface::withAdditionalBodyField()` capability
to combine their client credential with the subject credential in one checked
request. That capability is proposed separately in nr-vault PR 409. Its absence
denies confidential exchanges before an IdP request, while existing static
authentication and public-client exchanges remain compatible. No unreleased
version number is invented as a Composer constraint; installation instructions
name the capability requirement until a release supplies it.

Specification: `specs/015-delegated-mcp-auth/spec.md`. Tests exercise the wire
against fixture IdP and MCP clients, including actor isolation, renewal and
queue/resume. They do not exchange real personal credentials or imply that an
operator's identity-provider configuration has been certified.

## Implementation clarification (2026-10-11) {#implementation-clarification-2026-10-11}

During tool execution, an operation may already have raised a cancellation,
budget, protocol or caller-argument serialization failure when the credential
session is closed. The operation owner, `McpClient::callTool()`, keeps that
original throwable and still attempts its existing session close once. A
subordinate close failure cannot replace or attach itself to the original
failure. This applies to both exceptions and PHP errors.

Without an escaping operation failure, closing the session retains its
existing failure behavior. A returned remote `isError` result is a successful
JSON-RPC response, so its cleanup failure is still reported. Explicit session
close, renewal, discovery and the existing normal Vault deletion policy remain
unchanged. Failed cleanup can leave a credential until expiry; this adds no
retry, physical-erasure, atomicity or concurrency guarantee.
