Skip to main content
Intelliger
Enterprise Agents

AI Agent Architecture for Auditable MCP Transactions

An AI agent architecture that turns MCP tool calls into auditable transactions using identity, mandates, request binding, approvals and signed receipts.

AI Agent Architecture for Auditable MCP Transactions
Intelliger
14 minute read

An auditable AI agent architecture must turn a tool call into more than an instruction to software. An enterprise transaction is a claim about responsibility: a known agent, acting for an accountable organisation, exercised delegated authority under a particular policy and caused an observed result.

The distinction appears after something goes wrong. A model calls rotate_database_credential. The application returns success. Three months later, an auditor asks who authorized the rotation, which database was in scope, whether a change ticket was open and which policy version allowed execution. A trace containing tool arguments and model messages does not answer those questions reliably.

We will trace one credential rotation through a transaction architecture. The operation is deliberately consequential but familiar. The same flow works for payments, refunds, procurement orders and regulated data release.

Define the AI agent transaction before connecting the model

The tool accepts:

{
  "name": "rotate_database_credential",
  "arguments": {
    "database": "payments-ledger-prod",
    "principal": "reconciliation-worker",
    "change_ticket": "CHG-2026-1842"
  }
}

Before exposing it to an agent, write down the transaction invariants:

  • the database and principal must be registered resources;
  • the change ticket must be open and approved for the maintenance window;
  • the agent needs explicit credential.rotate authority;
  • production rotation requires a separate human approval;
  • the agent must never see the generated secret;
  • the old and new credential states need reconciliation;
  • every decision must bind to the exact arguments.

This list converts a vague goal, "let the agent rotate credentials," into conditions software can enforce.

Step 1: resolve the agent and accountable organisation

The runtime authenticates with a proof-bound token, mTLS certificate, workload identity or signed request. The gateway maps that proof to a stable agent record and organisation.

Do not let the caller choose its tenant by supplying an unverified header. Tenant, actor and roles must come from verified identity. Keep issuer signing keys separate from runtime keys. Compromise of an agent runtime should not grant the ability to issue new enterprise identities or mandates.

The identity result should contain enough information for later policy and evidence:

type VerifiedIdentity = {
  agentId: string
  organisationId: string
  runtimeKeyId: string
  issuerId: string
  assuranceLevel: string
  status: "active" | "suspended" | "revoked"
}

Authentication fails closed if the proof is invalid, the issuer chain does not terminate at an accepted trust anchor, the key is outside its validity period or a current status record revokes the agent, key or issuer.

Step 2: resolve delegated authority

The agent presents a short-lived mandate issued for the change. It should bind the subject, action, resources, purpose, validity period, delegation rules and proof key.

{
  "id": "mandate:db-rotation:1842",
  "subject": "agent:sre:remediation-4",
  "issuer": "org:acme:platform-operations",
  "purpose": "execute_approved_change_CHG-2026-1842",
  "actions": ["credential.rotate"],
  "resources": [
    "database:payments-ledger-prod",
    "principal:reconciliation-worker"
  ],
  "constraints": {
    "change_ticket": "CHG-2026-1842",
    "max_uses": 1,
    "requires_approval": true,
    "max_delegation_depth": 0
  },
  "not_before": "2026-08-10T22:00:00Z",
  "expires_at": "2026-08-10T22:30:00Z"
}

Resolve current status and revocation, not only the signature. If this mandate is a child, verify the full parent chain and prove that no child gained broader actions, resources, time or usage.

Step 3: normalize the call into a transaction envelope

The gateway converts MCP-specific input into a protocol-neutral transaction record. Stable action and resource identifiers matter more than the display name the model saw.

The following object is protocol-neutral pseudodata, not a wire-compatible OATI Transaction Envelope. A production OATI object must use the published versioned schema and proof profile.

{
  "id": "tx:db-rotation:01J5Q1",
  "agent_id": "agent:sre:remediation-4",
  "organisation_id": "org:acme",
  "mandate_id": "mandate:db-rotation:1842",
  "action": "credential.rotate",
  "resource": "database:payments-ledger-prod",
  "purpose": "execute_approved_change_CHG-2026-1842",
  "destination": "vault-broker:prod-eu",
  "protocol": "mcp",
  "request_digest": "sha256:4a7c...",
  "issued_at": "2026-08-10T22:07:14Z",
  "nonce": "01J5Q1E6A0P3",
  "proof_audience": "mcp://operations-gateway.acme.example",
  "operation_idempotency_key": "credential-rotation:payments-ledger-prod:CHG-2026-1842"
}

Calculate request_digest over canonical security-relevant input, including all three tool arguments. Sign the envelope or bind it into the runtime proof. At every transformation boundary, verify that the digest still describes the operation about to execute.

The envelope creates a shared vocabulary across MCP, HTTP, gRPC and queue workers. It also prevents business policy from depending on framework-specific message shapes.

Step 4: claim replay and usage state

Check time, audience and the received request digest before consuming state. Then atomically claim the transaction ID and nonce.

await store.transaction(async tx => {
  await tx.replay.claim(envelope.id, envelope.nonce)
})

The claim must reject a duplicate across gateway replicas. A process-local map does not work in a scaled deployment. Define retention long enough to cover the proof lifetime and business retry window.

Do not reserve consumable authority or mark the business action successful yet. Evaluate policy first. After an allow result and any required approval, atomically reserve usage immediately before dispatch. The ledger needs a transaction state machine that can represent authorized, approval_pending, reserved, submitted, succeeded, failed, unknown and any domain-specific settlement states. Expired or abandoned approvals need deterministic release rules.

Step 5: evaluate policy deterministically

The policy decision takes verified identity, effective mandate, canonical transaction and current business facts. A model may extract context or explain a denial, but it should not decide whether a production credential rotation is permitted.

permit (
  principal,
  action == Action::"credential.rotate",
  resource
)
when {
  principal.organisation == resource.owner &&
  context.mandate.purpose == context.change.ticketPurpose &&
  context.change.status == "approved" &&
  context.change.windowOpen == true &&
  context.mandate.maxUses == 1
};

This is illustrative Cedar-style policy. A production policy also needs approval requirements, environment classification, separation of duties, emergency conditions and output controls. Store the compiled policy digest and the facts used for the decision. Recording only the source filename is not enough because that file can change.

The decision might be represented by the following protocol-neutral projection. It is not the normative OATI Decision schema:

{
  "id": "decision:db-rotation:01J5Q1",
  "transaction_id": "tx:db-rotation:01J5Q1",
  "result": "approval_required",
  "reason": "production_credential_rotation",
  "policy_digest": "sha256:91be...",
  "required_approver_role": "production-change-approver"
}

Step 6: bind approval to the exact transaction

Transaction approval in this credential-rotation example is target architecture. The deployed OATI control-plane slice proves independent approval for issuance bundles, not a completed business-transaction approval service.

An approval screen should show the database, principal, ticket, requesting agent, purpose, time window and policy result. The approver signs or confirms the transaction digest, not a loose task description.

If any protected field changes after approval, the approval no longer applies. Recompute the digest and request a new decision. This blocks a substitution attack where an agent gets approval for staging and then changes the target to production.

Separate the issuer from the approver for sensitive authority. OATI's implemented control-plane vertical slice uses independent bundle fingerprint approval so an issuing principal cannot approve its own production bundle. The same separation is useful at transaction level.

Step 7: broker a temporary execution capability

After authorization and approval, the gateway asks Vault, a cloud STS or another credential broker for a task-specific capability. Bind it to the target service, operation and short lifetime where the provider supports those controls.

The model receives no secret. The gateway injects the capability into the call to the existing rotation service:

const capability = await broker.issue({
  subject: envelope.agent_id,
  action: envelope.action,
  resource: envelope.resource,
  audience: "https://vault-broker.acme.example",
  expiresInSeconds: 60
})

await mandateUsage.reserve(mandate.id, envelope.id, 1)

const result = await rotationService.rotate({
  database: call.database,
  principal: call.principal,
  idempotencyKey: envelope.operation_idempotency_key,
  authorization: capability
})

The service returns a reference to the new credential version and rotation event, not the secret value. The gateway filters the response before returning anything to the agent.

Step 8: reconcile what happened

Networks fail at awkward points. The service may complete the rotation while the gateway times out before receiving the response. Retrying with a fresh request could rotate again.

Use a stable business-operation key as the provider idempotency key. It must survive protocol-level retries even if a later attempt receives a new transaction ID or proof. When the outcome is ambiguous, query the provider's status endpoint or reconcile against its event stream. Record unknown until there is evidence for success or failure.

This distinction matters in audit. A decision to allow an operation does not prove execution. A 200 observed by a proxy may not prove the durable state changed. Keep authorization, submission and observed outcome as separate events.

Step 9: issue a signed receipt

After reconciliation, canonicalize and sign a receipt. This is an illustrative evidence projection, not the normative OATI Receipt schema; the proof object is omitted for readability:

{
  "id": "receipt:db-rotation:01J5Q1",
  "transaction_id": "tx:db-rotation:01J5Q1",
  "agent_id": "agent:sre:remediation-4",
  "organisation_id": "org:acme",
  "mandate_id": "mandate:db-rotation:1842",
  "decision": "allow",
  "outcome": "succeeded",
  "request_digest": "sha256:4a7c...",
  "policy_digest": "sha256:91be...",
  "approval_id": "approval:change:1842-7",
  "external_reference": "vault:event:rot-76391",
  "occurred_at": "2026-08-10T22:07:23Z",
  "issuer": "issuer:acme:operations"
}

The signed proof should carry its verification method, creation time, audience, nonce and expiry according to the chosen profile. Retain the canonical receipt, request and response digests, decision, approval reference and correlation ID.

An independent verifier can validate the schema, resolve the issuer and key, recreate the canonical payload, verify the signature, check trust and revocation, then confirm the receipt references the expected transaction and mandate.

The receipt is strong evidence of the issuer's record. It is not a truth machine. It cannot prove the change ticket contained a sound business justification or that an external provider reported honestly. Make the assurance level explicit, especially when only one party signs.

Failure cases worth designing first

The trust resolver is down. Material actions should fail closed unless a policy permits sufficiently fresh signed local records. Set cache limits by risk class.

The replay store is unavailable. Do not execute a one-time operation if uniqueness cannot be enforced. Return a structured retryable error without consuming the business action.

Approval arrives after mandate expiry. Reevaluate the whole transaction. An approval cannot revive expired authority.

The policy changes while approval is pending. Bind the approval to the policy digest, then define whether a policy update invalidates pending work. High-risk systems usually reevaluate.

Execution succeeds and receipt storage fails. Mark the evidence path as an operational exception. Do not tell downstream systems that absence of a durable receipt means the action did not happen.

The agent retries with a new transaction ID. Business-level idempotency must also include the target operation and change ticket. Protocol replay protection alone cannot detect semantic duplicates.

The response contains a secret. Filter by schema and data classification. Record that filtering occurred without placing the secret in central telemetry.

A parent mandate is revoked mid-flow. Recheck at the final execution boundary for operations where the approval delay is material.

An implementation checklist

  • Define transaction invariants before publishing the MCP tool.
  • Derive tenant, agent and roles from verified runtime identity.
  • Resolve issuer chains, keys, status and revocation.
  • Require a short-lived, task-specific mandate.
  • Normalize the call into a protocol-neutral envelope.
  • Canonicalize and digest every protected argument.
  • Verify audience, time, nonce and proof binding.
  • Claim replay and mandate usage atomically.
  • Run deterministic policy and store its digest.
  • Bind human approval to the exact transaction digest.
  • Obtain execution credentials only after authorization.
  • Keep credentials and sensitive outputs outside model context.
  • Pass a stable idempotency key to the external provider.
  • Reconcile ambiguous and delayed results.
  • Sign a receipt that separates decision from observed outcome.
  • Test outages and partial failures, not only denied inputs.

Related engineering guides

What OATI implements and what remains

OATI's public developer-preview framework implements the core objects used in this flow: Passport, Mandate, Transaction Envelope, Decision and Receipt. The TypeScript reference SDK includes middleware and adapters. Python and Go implement the portable core. All three run 73 shared conformance cases covering schema validity, canonical JSON, signatures, trust, replay, delegation, Commerce and RWA profile rules, plus discovery binding.

The deployed vertical slice covers private registry, issuance, approval, publication, public lookup, revocation by target and key lifecycle. Several target components are not complete. The policy compiler and durable evidence and dispute worker remain scaffolds. The Hub is primarily an application shell, and customer gateway enforcement is a reference integration rather than a fully operated commercial fleet. Independent security review is still pending.

Those boundaries matter because an audit architecture cannot be sold on diagrams alone. Build the transaction record at the same time as the tool. Then test whether a verifier who never saw the model conversation can reconstruct who acted, under what authority, against which request, with which policy and what observed result.