Delegated MCP authentication 

An MCP server can select delegated authentication (ADR-217). The runtime exchanges the explicitly mapped initiating actor's Vault credential for a Bearer token restricted to the server's audience and requested scopes. Existing servers retain legacy authentication after the schema update.

Server configuration 

Set the server's authentication mode, delegation profile, audience and space-separated scopes. Catalogue import and connection checks require a separate discovery credential. Tool execution requires an initiating actor; it cannot use the discovery credential or the server's legacy credential.

Profiles and explicit identity mappings live in the installation configuration. Store every credential in nr-vault and use its canonical reference here. Subject, client-secret and delegated discovery references accept UUIDv7 or an ASCII alias with 3 to 255 characters. An alias starts with a letter and contains only letters, digits and underscores. Controls, reference wrappers and other UUID versions are refused before contacting the identity provider. These bounds also apply to McpSubjectCredential and McpDelegationProfile::$clientSecretIdentifier.

Installation configuration with an explicit actor grant
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['nr_llm']
    ['mcpDelegationProfiles']['company'] = [
        'tokenEndpoint' => 'https://identity.example.org/oauth/token',
        'clientId' => 'nr-llm',
        'allowedAudiences' => ['mcp-api'],
        'allowedScopes' => ['records.read', 'records.write'],
        'backendUsers' => [
            7 => [
                'enabled' => true,
                'credentialIdentifier' => $subjectCredentialUuid,
                'allowedAudiences' => ['mcp-api'],
                'allowedScopes' => ['records.read'],
            ],
        ],
    ];
Copied!

A service actor uses an explicit serviceAccounts[name] entry with the same grant fields. Administrator status does not create an identity mapping. The requested audience and scopes must fit both the profile and the actor's grant. Disabled, missing or revoked mappings refuse execution.

The default resolver reads these mappings on every new operation and renewal. An installation can bind McpSubjectResolverInterface to its own resolver. The resolver receives AiActorContext, McpDelegationProfile, the audience and scopes; it returns McpSubjectCredential containing a Vault reference and its allowed audience and scope sets. It must never infer identity from the ambient backend user, the approver or request headers.

Vault capability 

A public client uses the existing Vault BodyField injection for the subject_token. A confidential client additionally configures clientSecretIdentifier and uses client_secret_post. That combination requires the additive AdditionalSecretHttpClientInterface::withAdditionalBodyField() capability from nr-vault PR 409. Until that capability is installed, confidential exchange fails before the identity provider is contacted. Existing authentication and public clients remain usable with the current stable Vault dependency.

The extension never retrieves subject or client secrets. Vault injects them through its secured, audited HTTP transport. Configure the Vault outbound host policy for both the identity provider and MCP endpoint, and grant the execution identity the Vault permissions required to use the configured references and create, use and delete temporary credentials.

Operation lifetime 

Each operation opens its own McpCredentialSessionInterface. The session pins the actor, profile, server, audience and scopes. Exchange, initialization, confirmation and tool calls spend the same operation deadline. Cancellation prevents subsequent legs and is forwarded to the supported Vault transport.

The exchanged token is stored under a random expiring Vault identifier. Every MCP leg checks freshness; an expired token triggers a fresh exchange with the original actor. The operation attempts normal Vault deletion of temporary identifiers in finally. If storage throws after persisting a token, the exchange also attempts deletion of its locally allocated identifier after checking the stored actor and delegation metadata. Missing, unreadable or mismatched metadata prevents this destructive attempt. A cleanup failure cannot replace the original sanitized storage failure.

If the exchange already rejects a stored credential because cancellation, the operation deadline or binding validation failed, its deletion attempt cannot replace that rejection either. Cancellation retains its cancelled outcome; an exhausted deadline retains its budget failure; a stored-binding failure retains its existing sanitized exchange outcome. An explicit session close() still reports cleanup_failed when its own deletion fails.

The same precedence applies to an already-failed McpClient::callTool() operation: closing its credential session is still attempted, but a secondary close failure cannot replace the original cancellation, budget, protocol or caller-argument serialization failure. The original throwable is retained. Without an escaping operation failure, close errors still propagate, including after a returned remote isError=true result. That flag describes a tool result inside a successful JSON-RPC response.

Deletion remains subject to Vault permissions and audit. This is best-effort cleanup, not atomic allocation, concurrency protection or physical erasure. Vault expiry bounds residual credentials after a process crash or failed cleanup. A discovery session keeps its configured reference and never deletes that operator-owned secret.

Queue and resume payloads carry actor identity and server references. They carry no subject, access or refresh tokens. Approval and human-input resumes resolve credentials afresh for the initiating actor. Authentication failures expose stable reason codes without identity-provider response bodies or token values.

Persisted actor binding 

New queued and suspended payloads retain the initiating actor and bind it to the run UUID within the authenticated state envelope. Backend UIDs must match the run owner; service actors keep their name and owner zero. Approval and input resumes validate the binding before and after the claim. Older states without this binding retain their stored backend owner. An explicitly malformed actor or a state copied from another new run is refused before MCP execution.