Cookbook

Recipe 1 — flow-ai bounded agent acting for the run’s user

laravel-flow-ai ≥ 1.1 has the
DelegatedIdentityResolver seam: the runtime resolves a delegated token before the first tool /
MCP call
and re-exchanges on expiry mid-run.

// Start the run knowing WHO it acts for (flow ≥ 2.2: subject on FlowExecutionOptions)
$run = Flow::run('weekly-orders', $input, FlowExecutionOptions::make(subject: 'user:42'));

Wire a resolver that calls TokenExchanger with the run’s subject; on invalid_grant throw
GrantRevokedException — the run halts before the next tool call (a typed error state, not a
generic failure, visible in flow-admin’s timeline). The LLM never sees any token: credentials reach
MCP servers per-run via the transport factory’s process env.

Recipe 2 — MCP tool server behind audience-scoped tokens

// Orchestrator: mint per tool server
$delegated = app(TokenExchanger::class)->exchange(new TokenExchangeRequest(
    subjectToken: $userToken,
    scopes: ['crm:contacts.read'],
    audience: 'mcp://crm-tools',        // ← the tool server's identity
));
// Tool server (a Laravel app with laravel-iam-client): enforce aud + intersection
Route::post('/mcp', McpController::class)->middleware('iam.can.delegated:crm:contacts.read');

A token minted for mcp://billing-tools is refused at mcp://crm-tools before any scope logic:
one stolen token cannot fan out across tool servers. Keep tools small and bounded; pin the MCP
spec version in your adapter.

Recipe 3 — mobile app backend (the MOBILE-SEC-LLM-001 pairing)

The mobile rule says: the app never holds a provider key; the app calls our backend; actions are
confirmed per-action on screen; the server re-validates
. Delegation is the server half:

  1. The app talks only to your backend orchestrator (the user’s session rides the normal app auth).
  2. The orchestrator holds the agent’s private key and the user’s token, performs the exchange, and
    calls tool APIs with the delegated token — the model, and the app, never see either token.
  3. Per-action confirmation on the phone maps to the consent grant (and, for sensitive single
    actions, a rebel-step-up purpose with dynamic linking on the action’s parameters).
  4. Revocation in the app profile calls DELETE /iam/me/delegations/{grantId} — one tap.

Recipe 4 — JIT elevation instead of a flat deny (v1.1)

When an agent hits invalid_scope (outside the intersection), don’t dead-end: open a JIT
elevation request
on the grant (DelegationElevationService::request() — extra scopes +
reason). The delegating user gets nudged out-of-band (rebel-channels, best-effort), approves
with a step-up re-consent bound to exactly the extra scopes, and the agent re-exchanges with the
widened grant. In flow-ai this maps to pausing the node (approval gate) rather than failing the
run. Full mechanics: Budget & elevation.